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.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Dimension;
036import java.awt.Graphics;
037import java.awt.Insets;
038import java.awt.LayoutManager;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.awt.event.FocusAdapter;
042import java.awt.event.FocusEvent;
043import java.awt.event.KeyAdapter;
044import java.awt.event.KeyEvent;
045import java.awt.event.KeyListener;
046import java.awt.event.MouseAdapter;
047import java.awt.event.MouseEvent;
048import java.awt.event.MouseListener;
049import java.beans.PropertyChangeEvent;
050import java.beans.PropertyChangeListener;
051import java.util.ArrayList;
052import java.util.List;
053
054import javax.accessibility.AccessibleContext;
055import javax.swing.Icon;
056import javax.swing.JColorChooser;
057import javax.swing.JLabel;
058import javax.swing.JPanel;
059import javax.swing.UIManager;
060import javax.swing.colorchooser.AbstractColorChooserPanel;
061
062import org.jdesktop.beans.AbstractBean;
063
064/**
065 * This has been essentially ripped out of the (wonderful) GNU Classpath
066 * project. Initial implementation of persistable recent colors courtesy of
067 *
068 * http://stackoverflow.com/a/11080701
069 *
070 * (though I had to hack things up a bit)
071 */
072public class PersistableSwatchChooserPanel extends AbstractColorChooserPanel implements PropertyChangeListener {
073
074//    private static final Logger logger = LoggerFactory.getLogger(PersistableSwatchChooserPanel.class);
075
076    /** The main panel that holds the set of choosable colors. */
077    MainSwatchPanel mainPalette;
078
079    /** A panel that holds the recent colors. */
080    RecentSwatchPanel recentPalette;
081
082    /** The mouse handlers for the panels. */
083    MouseListener mouseHandler;
084
085    /** Main Palette {@code KeyListener}. */
086    KeyListener mainSwatchKeyListener;
087
088    /** Recent palette {@code KeyListener}. */
089    KeyListener recentSwatchKeyListener;
090
091    ColorTracker tracker;
092
093    /**
094     * This the base class for all swatch panels. Swatch panels are panels that
095     * hold a set of blocks where colors are displayed.
096     */
097    abstract static class SwatchPanel extends JPanel {
098
099        /** Width of each block. */
100        protected int cellWidth = 10;
101
102        /** Height of each block. */
103        protected int cellHeight = 10;
104
105        /** Gap between blocks. */
106        protected int gap = 1;
107
108        /** Number of rows in the swatch panel. */
109        protected int numRows;
110
111        /** Number of columns in the swatch panel. */
112        protected int numCols;
113
114        /** Row of the selected color's cell. */
115        protected int selRow;
116
117        /** Column of the selected color's cell. */
118        protected int selCol;
119
120        /**
121         * Creates a new SwatchPanel object.
122         */
123        SwatchPanel() {
124            selRow = 0;
125            selCol = 0;
126            setBackground(Color.WHITE);
127            setFocusable(true);
128
129            addFocusListener(new FocusAdapter() {
130                @Override public void focusGained(FocusEvent e) {
131                    repaint();
132                }
133
134                @Override public void focusLost(FocusEvent e) {
135                    repaint();
136                }
137            });
138
139            addKeyListener(new KeyAdapter() {
140                @Override public void keyPressed(KeyEvent e) {
141                    int code = e.getKeyCode();
142                    switch (code) {
143                        case KeyEvent.VK_UP:
144                            if (selRow > 0) {
145                                selRow--;
146                                repaint();
147                            }
148                            break;
149                        case KeyEvent.VK_DOWN:
150                            if (selRow < numRows - 1) {
151                                selRow++;
152                                repaint();
153                            }
154                            break;
155                        case KeyEvent.VK_LEFT:
156                            if (selCol > 0 && SwatchPanel.this.getComponentOrientation().isLeftToRight()) {
157                                selCol--;
158                                repaint();
159                            } else if (selCol < numCols -1 && !SwatchPanel.this.getComponentOrientation().isLeftToRight()) {
160                                selCol++;
161                                repaint();
162                            }
163                            break;
164                        case KeyEvent.VK_RIGHT:
165                            if (selCol < numCols - 1
166                                && SwatchPanel.this.getComponentOrientation().isLeftToRight()) {
167                                selCol++;
168                                repaint();
169                            } else if (selCol > 0 && !SwatchPanel.this.getComponentOrientation().isLeftToRight()) {
170                                selCol--;
171                                repaint();
172                            }
173                            break;
174                        case KeyEvent.VK_HOME:
175                            selCol = 0;
176                            selRow = 0;
177                            repaint();
178                            break;
179                        case KeyEvent.VK_END:
180                            selCol = numCols - 1;
181                            selRow = numRows - 1;
182                            repaint();
183                            break;
184                    }
185                }
186            });
187        }
188
189        /**
190         * This method returns the preferred size of the swatch panel based on the
191         * number of rows and columns and the size of each cell.
192         *
193         * @return Preferred size of the swatch panel.
194         */
195        @Override public Dimension getPreferredSize() {
196            int height = (numRows * cellHeight) + ((numRows - 1) * gap);
197            int width = (numCols * cellWidth) + ((numCols - 1) * gap);
198            Insets insets = getInsets();
199
200            return new Dimension(width + insets.left + insets.right,
201                height + insets.top + insets.bottom);
202        }
203
204        /**
205         * Return the {@literal "selected"} color.
206         *
207         * @return The color at {@code selRow} and {@code selCol}.
208         */
209        public Color getSelectedColor() {
210            return getColorForCell(selRow, selCol);
211        }
212
213        /**
214         * This method returns the color for the given position.
215         *
216         * @param x X coordinate of the position.
217         * @param y Y coordinate of the position.
218         *
219         * @return The color at the given position.
220         */
221        public abstract Color getColorForPosition(int x, int y);
222
223        /**
224         * Return the color at a given cell.
225         *
226         * @param row Cell row.
227         * @param column Cell column.
228         *
229         * @return Color of the cell at {@code row} and {@code column}.
230         */
231        public abstract Color getColorForCell(int row, int column);
232
233        /**
234         * This method initializes the colors for the swatch panel.
235         */
236        protected abstract void initializeColors();
237
238        /**
239         * Set the {@literal "selected"} cell using screen location.
240         *
241         * @param x X coordinate of the position.
242         * @param y Y coordinate of the position.
243         */
244        protected abstract void setSelectedCellFromPosition(int x, int y);
245    }
246
247    /**
248     * This is the main swatch panel. This panel sits in the middle and allows a
249     * set of colors to be picked which will move to the recent swatch panel.
250     */
251    static class MainSwatchPanel extends SwatchPanel {
252        /** The color describing (204, 255, 255) */
253        public static final Color C204255255 = new Color(204, 204, 255);
254
255        /** The color describing (255, 204, 204) */
256        public static final Color C255204204 = new Color(255, 204, 204);
257
258        /** The color describing (204, 255, 204) */
259        public static final Color C204255204 = new Color(204, 255, 204);
260
261        /** The color describing (204, 204, 204) */
262        public static final Color C204204204 = new Color(204, 204, 204);
263
264        /** The color (153, 153, 255). */
265        public static final Color C153153255 = new Color(153, 153, 255);
266
267        /** The color (51, 51, 255). */
268        public static final Color C051051255 = new Color(51, 51, 255);
269
270        /** The color (153, 0, 153). */
271        public static final Color C153000153 = new Color(153, 0, 153);
272
273        /** The color (0, 51, 51). */
274        public static final Color C000051051 = new Color(0, 51, 51);
275
276        /** The color (51, 0, 51). */
277        public static final Color C051000051 = new Color(51, 0, 51);
278
279        /** The color (51, 51, 0). */
280        public static final Color C051051000 = new Color(51, 51, 0);
281
282        /** The color (102, 102, 0). */
283        public static final Color C102102000 = new Color(102, 102, 0);
284
285        /** The color (153, 255, 153). */
286        public static final Color C153255153 = new Color(153, 255, 153);
287
288        /** The color (102, 255, 102). */
289        public static final Color C102255102 = new Color(102, 255, 102);
290
291        /** The color (0, 102, 102). */
292        public static final Color C000102102 = new Color(0, 102, 102);
293
294        /** The color (102, 0, 102). */
295        public static final Color C102000102 = new Color(102, 0, 102);
296
297        /** The color (0, 153, 153). */
298        public static final Color C000153153 = new Color(0, 153, 153);
299
300        /** The color (153, 153, 0). */
301        public static final Color C153153000 = new Color(153, 153, 0);
302
303        /** The color (204, 204, 0). */
304        public static final Color C204204000 = new Color(204, 204, 0);
305
306        /** The color (204, 0, 204). */
307        public static final Color C204000204 = new Color(204, 0, 204);
308
309        /** The color (0, 204, 204). */
310        public static final Color C000204204 = new Color(0, 204, 204);
311
312        /** The color (51, 255, 51). */
313        public static final Color C051255051 = new Color(51, 255, 51);
314
315        /** The color (255, 51, 51). */
316        public static final Color C255051051 = new Color(255, 51, 51);
317
318        /** The color (255, 102, 102). */
319        public static final Color C255102102 = new Color(255, 102, 102);
320
321        /** The color (102, 102, 255). */
322        public static final Color C102102255 = new Color(102, 102, 255);
323
324        /** The color (255, 153, 153). */
325        public static final Color C255153153 = new Color(255, 153, 153);
326        static Color[] colors =
327            {
328                // Row 1
329                Color.WHITE, new Color(204, 255, 255), C204255255, C204255255, C204255255,
330                C204255255, C204255255, C204255255, C204255255,
331                C204255255, C204255255, new Color(255, 204, 255),
332                C255204204, C255204204, C255204204, C255204204,
333                C255204204, C255204204, C255204204, C255204204,
334                C255204204, new Color(255, 255, 204), C204255204,
335                C204255204, C204255204, C204255204, C204255204,
336                C204255204, C204255204, C204255204, C204255204,
337
338                // Row 2
339                C204204204, new Color(153, 255, 255), new Color(153, 204, 255), C153153255,
340                C153153255, C153153255, C153153255, C153153255,
341                C153153255, C153153255, new Color(204, 153, 255),
342                new Color(255, 153, 255),
343                new Color(255, 153, 204), C255153153, C255153153,
344                C255153153, C255153153, C255153153, C255153153,
345                C255153153, new Color(255, 204, 153),
346                new Color(255, 255, 153),
347                new Color(204, 255, 153), C153255153, C153255153,
348                C153255153, C153255153, C153255153, C153255153,
349                C153255153, new Color(153, 255, 204),
350
351                // Row 3
352                C204204204, new Color(102, 255, 255), new Color(102, 204, 255),
353                new Color(102, 153, 255), C102102255, C102102255,
354                C102102255, C102102255, C102102255,
355                new Color(153, 102, 255),
356                new Color(204, 102, 255),
357                new Color(255, 102, 255),
358                new Color(255, 102, 204),
359                new Color(255, 102, 153), C255102102, C255102102,
360                C255102102, C255102102, C255102102,
361                new Color(255, 153, 102),
362                new Color(255, 204, 102),
363                new Color(255, 255, 102),
364                new Color(204, 255, 102),
365                new Color(153, 255, 102), C102255102, C102255102,
366                C102255102, C102255102, C102255102,
367                new Color(102, 255, 153),
368                new Color(102, 255, 204),
369
370                // Row 4
371                new Color(153, 153, 153), new Color(51, 255, 255), new Color(51, 204, 255),
372                new Color(51, 153, 255), new Color(51, 102, 255),
373                C051051255, C051051255, C051051255,
374                new Color(102, 51, 255), new Color(153, 51, 255),
375                new Color(204, 51, 255), new Color(255, 51, 255),
376                new Color(255, 51, 204), new Color(255, 51, 153),
377                new Color(255, 51, 102), C255051051, C255051051,
378                C255051051, new Color(255, 102, 51),
379                new Color(255, 153, 51), new Color(255, 204, 51),
380                new Color(255, 255, 51), new Color(204, 255, 51),
381                new Color(153, 255, 51), new Color(102, 255, 51),
382                C051255051, C051255051, C051255051,
383                new Color(51, 255, 102), new Color(51, 255, 153),
384                new Color(51, 255, 204),
385
386                // Row 5
387                new Color(153, 153, 153), new Color(0, 255, 255), new Color(0, 204, 255),
388                new Color(0, 153, 255), new Color(0, 102, 255),
389                new Color(0, 51, 255), new Color(0, 0, 255),
390                new Color(51, 0, 255), new Color(102, 0, 255),
391                new Color(153, 0, 255), new Color(204, 0, 255),
392                new Color(255, 0, 255), new Color(255, 0, 204),
393                new Color(255, 0, 153), new Color(255, 0, 102),
394                new Color(255, 0, 51), new Color(255, 0, 0),
395                new Color(255, 51, 0), new Color(255, 102, 0),
396                new Color(255, 153, 0), new Color(255, 204, 0),
397                new Color(255, 255, 0), new Color(204, 255, 0),
398                new Color(153, 255, 0), new Color(102, 255, 0),
399                new Color(51, 255, 0), new Color(0, 255, 0),
400                new Color(0, 255, 51), new Color(0, 255, 102),
401                new Color(0, 255, 153), new Color(0, 255, 204),
402
403                // Row 6
404                new Color(102, 102, 102), C000204204, C000204204, new Color(0, 153, 204),
405                new Color(0, 102, 204), new Color(0, 51, 204),
406                new Color(0, 0, 204), new Color(51, 0, 204),
407                new Color(102, 0, 204), new Color(153, 0, 204),
408                C204000204, C204000204, C204000204,
409                new Color(204, 0, 153), new Color(204, 0, 102),
410                new Color(204, 0, 51), new Color(204, 0, 0),
411                new Color(204, 51, 0), new Color(204, 102, 0),
412                new Color(204, 153, 0), C204204000, C204204000,
413                C204204000, new Color(153, 204, 0),
414                new Color(102, 204, 0), new Color(51, 204, 0),
415                new Color(0, 204, 0), new Color(0, 204, 51),
416                new Color(0, 204, 102), new Color(0, 204, 153),
417                new Color(0, 204, 204),
418
419                // Row 7
420                new Color(102, 102, 102), C000153153, C000153153, C000153153,
421                new Color(0, 102, 153), new Color(0, 51, 153),
422                new Color(0, 0, 153), new Color(51, 0, 153),
423                new Color(102, 0, 153), C153000153, C153000153,
424                C153000153, C153000153, C153000153,
425                new Color(153, 0, 102), new Color(153, 0, 51),
426                new Color(153, 0, 0), new Color(153, 51, 0),
427                new Color(153, 102, 0), C153153000, C153153000,
428                C153153000, C153153000, C153153000,
429                new Color(102, 153, 0), new Color(51, 153, 0),
430                new Color(0, 153, 0), new Color(0, 153, 51),
431                new Color(0, 153, 102), C000153153, C000153153,
432
433                // Row 8
434                new Color(51, 51, 51), C000102102, C000102102, C000102102, C000102102,
435                new Color(0, 51, 102), new Color(0, 0, 102),
436                new Color(51, 0, 102), C102000102, C102000102,
437                C102000102, C102000102, C102000102, C102000102,
438                C102000102, new Color(102, 0, 51),
439                new Color(102, 0, 0), new Color(102, 51, 0),
440                C102102000, C102102000, C102102000, C102102000,
441                C102102000, C102102000, C102102000,
442                new Color(51, 102, 0), new Color(0, 102, 0),
443                new Color(0, 102, 51), C000102102, C000102102,
444                C000102102,
445
446                // Row 9.
447                Color.BLACK, C000051051, C000051051, C000051051, C000051051, C000051051,
448                new Color(0, 0, 51), C051000051, C051000051,
449                C051000051, C051000051, C051000051, C051000051,
450                C051000051, C051000051, C051000051,
451                new Color(51, 0, 0), C051051000, C051051000,
452                C051051000, C051051000, C051051000, C051051000,
453                C051051000, C051051000, new Color(0, 51, 0),
454                C000051051, C000051051, C000051051, C000051051,
455                new Color(51, 51, 51)
456            };
457
458        /**
459         * Creates a new MainSwatchPanel object.
460         */
461        MainSwatchPanel() {
462            numCols = 31;
463            numRows = 9;
464            initializeColors();
465            // incredibly, this setToolTipText call is how you register to
466            // listen for events
467            setToolTipText("");
468            revalidate();
469        }
470
471        /**
472         * This method returns the color for the given position.
473         *
474         * @param x X location for the position.
475         * @param y Y location for the position.
476         *
477         * @return {@link Color} for the given position.
478         */
479        @Override public Color getColorForPosition(int x, int y) {
480            if (((x % (cellWidth + gap)) > cellWidth) || ((y % (cellHeight + gap)) > cellHeight)) {
481                // position is located in gap.
482                return null;
483            }
484
485            int row = y / (cellHeight + gap);
486            int col = x / (cellWidth + gap);
487            return colors[row * numCols + col];
488        }
489
490        @Override protected void setSelectedCellFromPosition(int x, int y) {
491            if (((x % (cellWidth + gap)) > cellWidth) || ((y % (cellHeight + gap)) > cellHeight)) {
492                // position is located in gap.
493                return;
494            }
495            selRow = y / (cellHeight + gap);
496            selCol = x / (cellWidth + gap);
497        }
498
499        @Override public Color getColorForCell(int row, int column) {
500            return colors[row * numCols + column];
501        }
502
503        /**
504         * This method initializes the colors for the main swatch panel.
505         */
506        @Override protected void initializeColors() {
507            // Unnecessary
508        }
509
510        /**
511         * This method paints the main graphics panel with the given Graphics
512         * object.
513         *
514         * @param graphics The Graphics object to paint with.
515         */
516        @Override public void paint(Graphics graphics) {
517            int index = 0;
518            Insets insets = getInsets();
519            int currX = insets.left;
520            int currY = insets.top;
521            Color saved = graphics.getColor();
522
523            for (int i = 0; i < numRows; i++) {
524                for (int j = 0; j < numCols; j++) {
525                    Color current = colors[index++];
526                    graphics.setColor(current);
527                    graphics.fill3DRect(currX, currY, cellWidth, cellHeight, true);
528                    if ((selRow == i) && (selCol == j) && this.isFocusOwner()) {
529                        Color cursorColor = new Color(current.getRed() < 125 ? 255 : 0,
530                            current.getGreen() < 125 ? 255 : 0,
531                            current.getBlue() < 125 ? 255 : 0);
532
533                        graphics.setColor(cursorColor);
534                        graphics.drawLine(currX, currY, currX + cellWidth - 1, currY);
535                        graphics.drawLine(currX, currY, currX, currY + cellHeight - 1);
536                        graphics.drawLine(currX + cellWidth - 1, currY, currX + cellWidth- 1, currY + cellHeight - 1);
537                        graphics.drawLine(currX, currY + cellHeight - 1, currX + cellWidth - 1, currY + cellHeight - 1);
538                        graphics.drawLine(currX, currY, currX + cellWidth - 1, currY + cellHeight - 1);
539                        graphics.drawLine(currX, currY + cellHeight - 1, currX + cellWidth - 1, currY);
540                    }
541                    currX += gap + cellWidth;
542                }
543                currX = insets.left;
544                currY += gap + cellHeight;
545            }
546            graphics.setColor(saved);
547        }
548
549        /**
550         * This method returns the tooltip text for the given MouseEvent.
551         *
552         * @param e The MouseEvent to find tooltip text for.
553         *
554         * @return The tooltip text.
555         */
556        @Override public String getToolTipText(MouseEvent e) {
557            Color c = getColorForPosition(e.getX(), e.getY());
558            String tip = null;
559            if (c != null) {
560                tip = c.getRed() + ", " + c.getGreen() + ", " + c.getBlue();
561            }
562            return tip;
563        }
564    }
565
566    /**
567     * This class is the recent swatch panel. It holds recently selected colors.
568     */
569    static class RecentSwatchPanel extends SwatchPanel {
570
571        /** The array for storing recently stored colors. */
572        Color[] colors;
573
574        /** The index of the array that is the start. */
575        int start = 0;
576
577        /**
578         * Creates a new RecentSwatchPanel object.
579         */
580        RecentSwatchPanel() {
581            numCols = 5;
582            numRows = 7;
583            initializeColors();
584            // incredibly, this setToolTipText call is how you register to
585            // listen for events
586            setToolTipText("");
587            revalidate();
588        }
589
590        /**
591         * This method returns the color for the given position.
592         *
593         * @param x The x coordinate of the position.
594         * @param y The y coordinate of the position.
595         *
596         * @return The color for the given position.
597         */
598        @Override public Color getColorForPosition(int x, int y) {
599            if (((x % (cellWidth + gap)) > cellWidth) || ((y % (cellHeight + gap)) > cellHeight)) {
600                // position is located in gap.
601                return null;
602            }
603
604            int row = y / (cellHeight + gap);
605            int col = x / (cellWidth + gap);
606
607            return colors[getIndexForCell(row, col)];
608        }
609
610        @Override protected void setSelectedCellFromPosition(int x, int y) {
611            if (((x % (cellWidth + gap)) > cellWidth) || ((y % (cellHeight + gap)) > cellHeight)) {
612                // position is located in gap.
613                return;
614            }
615            selRow = y / (cellHeight + gap);
616            selCol = x / (cellWidth + gap);
617        }
618
619        /**
620         * This method initializes the colors for the recent swatch panel.
621         */
622        @Override protected void initializeColors() {
623            final Color defaultColor =
624                UIManager.getColor("ColorChooser.swatchesDefaultRecentColor", getLocale());
625            colors = new Color[numRows * numCols];
626            for (int i = 0; i < colors.length; i++) {
627                colors[i] = defaultColor;
628            }
629        }
630
631        /**
632         * This method returns the array index for the given row and column.
633         *
634         * @param row The row.
635         * @param col The column.
636         *
637         * @return The array index for the given row and column.
638         */
639        private int getIndexForCell(int row, int col) {
640            return ((row * numCols) + col + start) % (numRows * numCols);
641        }
642
643        public Color getColorForCell(int row, int column) {
644            return colors[getIndexForCell(row, column)];
645        }
646
647        /**
648         * This method adds the given color to the beginning of the swatch panel.
649         * Package-private to avoid an accessor method.
650         *
651         * @param c The color to add.
652         */
653        void addColorToQueue(Color c) {
654            if (--start == -1) {
655                start = numRows * numCols - 1;
656            }
657            colors[start] = c;
658        }
659
660        void addColorsToQueue(List<Color> colorsToAdd) {
661            if ((colorsToAdd != null) && !colorsToAdd.isEmpty()) {
662                for (int i = colorsToAdd.size() - 1; i >= 0; i--) {
663                    addColorToQueue(colorsToAdd.get(i));
664                }
665            }
666        }
667
668        /**
669         * This method paints the panel with the given Graphics object.
670         *
671         * @param g The Graphics object to paint with.
672         */
673        @Override public void paint(Graphics g) {
674            Color saved = g.getColor();
675            Insets insets = getInsets();
676            int currX = insets.left;
677            int currY = insets.top;
678
679            for (int i = 0; i < numRows; i++) {
680                for (int j = 0; j < numCols; j++) {
681                    Color current = colors[getIndexForCell(i, j)];
682                    g.setColor(current);
683                    g.fill3DRect(currX, currY, cellWidth, cellHeight, true);
684                    if ((selRow == i) && (selCol == j) && this.isFocusOwner()) {
685                        Color cursorColor = new Color(current.getRed() < 125 ? 255 : 0,
686                            current.getGreen() < 125 ? 255 : 0,
687                            current.getBlue() < 125 ? 255 : 0);
688                        g.setColor(cursorColor);
689                        g.drawLine(currX, currY, currX + cellWidth - 1, currY);
690                        g.drawLine(currX, currY, currX, currY + cellHeight - 1);
691                        g.drawLine(currX + cellWidth - 1, currY, currX + cellWidth- 1, currY + cellHeight - 1);
692                        g.drawLine(currX, currY + cellHeight - 1, currX + cellWidth - 1, currY + cellHeight - 1);
693                        g.drawLine(currX, currY, currX + cellWidth - 1, currY + cellHeight - 1);
694                        g.drawLine(currX, currY + cellHeight - 1, currX + cellWidth - 1, currY);
695                    }
696                    currX += cellWidth + gap;
697                }
698                currX = insets.left;
699                currY += cellWidth + gap;
700            }
701        }
702
703        /**
704         * This method returns the tooltip text for the given MouseEvent.
705         *
706         * @param e The MouseEvent.
707         *
708         * @return The tooltip text.
709         */
710        @Override public String getToolTipText(MouseEvent e) {
711            Color c = getColorForPosition(e.getX(), e.getY());
712//            logger.trace("x={} y={} c={}", e.getX(), e.getY(), c);
713            String tip = null;
714            if (c != null) {
715                tip = c.getRed() + ", " + c.getGreen() + ", " + c.getBlue();
716            }
717            return tip;
718        }
719    }
720
721    /**
722     * This class handles mouse events for the two swatch panels.
723     */
724    class MouseHandler extends MouseAdapter {
725        /**
726         * This method is called whenever the mouse is pressed.
727         *
728         * @param e The MouseEvent.
729         */
730        @Override public void mousePressed(MouseEvent e) {
731            if (isEnabled()) {
732                SwatchPanel panel = (SwatchPanel)e.getSource();
733                Color c = panel.getColorForPosition(e.getX(), e.getY());
734                panel.setSelectedCellFromPosition(e.getX(), e.getY());
735                // yes, the "!=" is intentional.
736                if (panel != recentPalette) {
737                    recentPalette.addColorToQueue(c);
738                    if (tracker != null) {
739                        tracker.addColor(c);
740                    }
741                }
742                PersistableSwatchChooserPanel.this.getColorSelectionModel().setSelectedColor(c);
743                PersistableSwatchChooserPanel.this.repaint();
744                panel.requestFocusInWindow();
745            }
746        }
747    }
748
749    /**
750     * This class handles the user {@literal "selecting"} a recently used
751     * color using the space key.
752     */
753    private class RecentSwatchKeyListener extends KeyAdapter {
754        public void keyPressed(KeyEvent e) {
755            if (KeyEvent.VK_SPACE == e.getKeyCode()) {
756                Color color = recentPalette.getSelectedColor();
757                PersistableSwatchChooserPanel.this.getColorSelectionModel().setSelectedColor(color);
758                PersistableSwatchChooserPanel.this.repaint();
759            }
760        }
761    }
762
763    /**
764     * This class handles the user {@literal "selecting"} a color using the
765     * space key.
766     */
767    private class MainSwatchKeyListener extends KeyAdapter {
768        public void keyPressed(KeyEvent e) {
769            if (KeyEvent.VK_SPACE == e.getKeyCode()) {
770                Color color = mainPalette.getSelectedColor();
771                recentPalette.addColorToQueue(color);
772                if (tracker != null) {
773                    tracker.addColor(color);
774                }
775                PersistableSwatchChooserPanel.this.getColorSelectionModel().setSelectedColor(color);
776                PersistableSwatchChooserPanel.this.repaint();
777            }
778        }
779    }
780
781    /**
782     * This is the layout manager for the main panel.
783     */
784    static class MainPanelLayout implements LayoutManager {
785        /**
786         * This method is called when a new component is added to the container.
787         *
788         * @param name The name of the component.
789         * @param comp The added component.
790         */
791        @Override public void addLayoutComponent(String name, Component comp) {
792            // Nothing to do here.
793        }
794
795        /**
796         * This method is called to set the size and position of the child
797         * components for the given container.
798         *
799         * @param parent The container to lay out.
800         */
801        @Override public void layoutContainer(Container parent) {
802            Component[] comps = parent.getComponents();
803            Insets insets = parent.getInsets();
804            Dimension[] pref = new Dimension[comps.length];
805
806            int xpos = 0;
807            int ypos = 0;
808            int maxHeight = 0;
809            int totalWidth = 0;
810
811            for (int i = 0; i < comps.length; i++) {
812                pref[i] = comps[i].getPreferredSize();
813                if (pref[i] == null) {
814                    return;
815                }
816                maxHeight = Math.max(maxHeight, pref[i].height);
817                totalWidth += pref[i].width;
818            }
819
820            ypos = (parent.getSize().height - maxHeight) / 2 + insets.top;
821            xpos = insets.left + (parent.getSize().width - totalWidth) / 2;
822
823            for (int i = 0; i < comps.length; i++) {
824                if (pref[i] == null) {
825                    continue;
826                }
827                comps[i].setBounds(xpos, ypos, pref[i].width, pref[i].height);
828                xpos += pref[i].width;
829            }
830        }
831
832        /**
833         * This method is called when a component is removed from the container.
834         *
835         * @param comp The component that was removed.
836         */
837        @Override public void removeLayoutComponent(Component comp) {
838            // Nothing to do here.
839        }
840
841        /**
842         * This methods calculates the minimum layout size for the container.
843         *
844         * @param parent The container.
845         *
846         * @return The minimum layout size.
847         */
848        @Override public Dimension minimumLayoutSize(Container parent) {
849            return preferredLayoutSize(parent);
850        }
851
852        /**
853         * This method returns the preferred layout size for the given container.
854         *
855         * @param parent The container.
856         *
857         * @return The preferred layout size.
858         */
859        @Override public Dimension preferredLayoutSize(Container parent) {
860            int xmax = 0;
861            int ymax = 0;
862
863            Component[] comps = parent.getComponents();
864
865            for (Component comp : comps) {
866                Dimension pref = comp.getPreferredSize();
867                if (pref == null) {
868                    continue;
869                }
870                xmax += pref.width;
871                ymax = Math.max(ymax, pref.height);
872            }
873
874            Insets insets = parent.getInsets();
875
876            return new Dimension(insets.left + insets.right + xmax,
877                insets.top + insets.bottom + ymax);
878        }
879    }
880
881    /**
882     * This is the layout manager for the recent swatch panel.
883     */
884    static class RecentPanelLayout implements LayoutManager {
885        /**
886         * This method is called when a component is added to the container.
887         *
888         * @param name The name of the component.
889         * @param comp The added component.
890         */
891        @Override public void addLayoutComponent(String name, Component comp) {
892            // Nothing needs to be done.
893        }
894
895        /**
896         * This method sets the size and position of the child components of the
897         * given container.
898         *
899         * @param parent The container to lay out.
900         */
901        @Override public void layoutContainer(Container parent) {
902            Component[] comps = parent.getComponents();
903            Dimension parentSize = parent.getSize();
904            Insets insets = parent.getInsets();
905            int currY = insets.top;
906
907            for (Component comp : comps) {
908                Dimension pref = comp.getPreferredSize();
909                if (pref == null) {
910                    continue;
911                }
912                comp.setBounds(insets.left, currY, pref.width, pref.height);
913                currY += pref.height;
914            }
915        }
916
917        /**
918         * This method calculates the minimum layout size for the given container.
919         *
920         * @param parent The container.
921         *
922         * @return The minimum layout size.
923         */
924        @Override public Dimension minimumLayoutSize(Container parent) {
925            return preferredLayoutSize(parent);
926        }
927
928        /**
929         * This method calculates the preferred layout size for the given
930         * container.
931         *
932         * @param parent The container.
933         *
934         * @return The preferred layout size.
935         */
936        @Override public Dimension preferredLayoutSize(Container parent) {
937            int width = 0;
938            int height = 0;
939            Insets insets = parent.getInsets();
940            Component[] comps = parent.getComponents();
941            for (Component comp : comps) {
942                Dimension pref = comp.getPreferredSize();
943                if (pref != null) {
944                    width = Math.max(width, pref.width);
945                    height += pref.height;
946                }
947            }
948
949            return new Dimension(width + insets.left + insets.right,
950                height + insets.top + insets.bottom);
951        }
952
953        /**
954         * This method is called whenever a component is removed from the
955         * container.
956         *
957         * @param comp The removed component.
958         */
959        @Override public void removeLayoutComponent(Component comp) {
960            // Nothing needs to be done.
961        }
962    }
963
964    /**
965     * Creates a new DefaultSwatchChooserPanel object.
966     */
967    PersistableSwatchChooserPanel() {
968        super();
969    }
970
971    /**
972     * This method updates the chooser panel with the new value from the
973     * JColorChooser.
974     */
975    @Override public void updateChooser() {
976        // Nothing to do here yet.
977    }
978
979    /**
980     * This method builds the chooser panel.
981     */
982    @Override protected void buildChooser() {
983        // The structure of the swatch panel is:
984        // One large panel (minus the insets).
985        // Inside that panel, there are two panels, one holds the palette.
986        // The other holds the label and the recent colors palette.
987        // The two palettes are two custom swatch panels.
988        setLayout(new MainPanelLayout());
989
990        JPanel mainPaletteHolder = new JPanel();
991        JPanel recentPaletteHolder = new JPanel();
992
993        mainPalette = new MainSwatchPanel();
994        mainPalette.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, getDisplayName());
995        mainPalette.setInheritsPopupMenu(true);
996
997        String recentLabel = UIManager.getString("ColorChooser.swatchesRecentText", getLocale());
998        recentPalette = new RecentSwatchPanel();
999        recentPalette.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, recentLabel);
1000        recentPalette.setInheritsPopupMenu(true);
1001
1002        JLabel label = new JLabel(recentLabel);
1003        label.setLabelFor(recentPalette);
1004
1005        mouseHandler = new MouseHandler();
1006        mainPalette.addMouseListener(mouseHandler);
1007        recentPalette.addMouseListener(mouseHandler);
1008
1009        mainSwatchKeyListener = new MainSwatchKeyListener();
1010        mainPalette.addKeyListener(mainSwatchKeyListener);
1011
1012        recentSwatchKeyListener = new RecentSwatchKeyListener();
1013        recentPalette.addKeyListener(recentSwatchKeyListener);
1014
1015        mainPaletteHolder.setLayout(new BorderLayout());
1016        mainPaletteHolder.add(mainPalette, BorderLayout.CENTER);
1017        mainPaletteHolder.setInheritsPopupMenu(true);
1018
1019        recentPaletteHolder.setLayout(new RecentPanelLayout());
1020        recentPaletteHolder.add(label);
1021        recentPaletteHolder.add(recentPalette);
1022        recentPaletteHolder.setInheritsPopupMenu(true);
1023
1024        JPanel main = new JPanel();
1025        main.add(mainPaletteHolder);
1026        main.add(recentPaletteHolder);
1027
1028        this.add(main);
1029    }
1030
1031    /**
1032     * This method removes the chooser panel from the JColorChooser.
1033     *
1034     * @param chooser The JColorChooser this panel is being removed from.
1035     */
1036    @Override public void uninstallChooserPanel(JColorChooser chooser) {
1037        mainPalette.removeMouseListener(mouseHandler);
1038        mainPalette.removeKeyListener(mainSwatchKeyListener);
1039
1040        recentPalette.removeMouseListener(mouseHandler);
1041        recentPalette.removeKeyListener(recentSwatchKeyListener);
1042
1043        recentPalette = null;
1044        mainPalette = null;
1045        mouseHandler = null;
1046        mainSwatchKeyListener = null;
1047        recentSwatchKeyListener = null;
1048        removeAll();
1049        super.uninstallChooserPanel(chooser);
1050    }
1051
1052    /**
1053     * This method returns the JTabbedPane displayed name.
1054     *
1055     * @return The name displayed in the JTabbedPane.
1056     */
1057    @Override public String getDisplayName() {
1058        return "Swatches";
1059    }
1060
1061    /**
1062     * This method returns the small display icon.
1063     *
1064     * @return The small display icon.
1065     */
1066    @Override public Icon getSmallDisplayIcon() {
1067        return null;
1068    }
1069
1070    /**
1071     * This method returns the large display icon.
1072     *
1073     * @return The large display icon.
1074     */
1075    @Override public Icon getLargeDisplayIcon() {
1076        return null;
1077    }
1078
1079    /**
1080     * This method paints the chooser panel with the given Graphics object.
1081     *
1082     * @param g The Graphics object to paint with.
1083     */
1084    @Override public void paint(Graphics g) {
1085        super.paint(g);
1086    }
1087
1088    /**
1089     * This method returns the tooltip text for the given MouseEvent.
1090     *
1091     * @param e The MouseEvent.
1092     *
1093     * @return The tooltip text.
1094     */
1095    @Override public String getToolTipText(MouseEvent e) {
1096        return null;
1097    }
1098
1099    /**
1100     * Set the color tracking object.
1101     *
1102     * @param tracker
1103     */
1104    public void setColorTracker(ColorTracker tracker) {
1105        this.tracker = tracker;
1106        if (tracker != null) {
1107            tracker.addPropertyChangeListener("colors", this);
1108        }
1109        updateRecentSwatchPanel();
1110    }
1111
1112    @Override public void propertyChange(PropertyChangeEvent evt) {
1113//        logger.trace("old='{}' new='{}'", evt.getOldValue(), evt.getNewValue());
1114//        updateRecentSwatchPanel();
1115    }
1116
1117    /**
1118     * A method updating the recent colors in the swatchPanel
1119     * This is called whenever necessary, specifically after building the panel,
1120     * on changes of the tracker, from the mouseListener
1121     */
1122    protected void updateRecentSwatchPanel() {
1123        if (recentPalette != null) {
1124            recentPalette.addColorsToQueue(tracker != null ? tracker.getColors() : null);
1125        }
1126    }
1127
1128    /**
1129     * This class is used to save and restore the recent color choices..
1130     */
1131    public static class ColorTracker extends AbstractBean implements ActionListener {
1132
1133        /** The list of recent {@link Color Colors}. */
1134        private List<Color> colors = new ArrayList<>();
1135
1136        /**
1137         * Add a {@link Color} to the list of recent color choices. This method
1138         * will fire off a {@literal "colors"} property change.
1139         *
1140         * @param color {@code Color} to be added.
1141         */
1142        public void addColor(Color color) {
1143            List<Color> old = getColors();
1144            colors.add(0, color);
1145            firePropertyChange("colors", old, getColors());
1146        }
1147
1148        /**
1149         * Set the list of recent color choices. This method is what should be
1150         * called when {@literal "restoring"} the recent colors panel.
1151         *
1152         * <p>This method will fire off a {@literal "colors"} property change.
1153         * </p>
1154         *
1155         * @param colors {@code List} of recent color choices. {@code null}
1156         * is allowed, but will result in {@link #colors} being empty.
1157         */
1158        public void setColors(List<Color> colors) {
1159            List<Color> old = getColors();
1160            this.colors = new ArrayList<>(colors);
1161            firePropertyChange("colors", old, getColors());
1162        }
1163
1164        /**
1165         * Get the recent color choices.
1166         *
1167         * @return {@link ArrayList} containing the recently picked colors.
1168         * May be empty.
1169         */
1170        public List<Color> getColors() {
1171            return new ArrayList<>(colors);
1172        }
1173
1174        /**
1175         * Returns the user's last {@link Color} selection.
1176         *
1177         * @return Either the last {@code Color} that was selected, or
1178         * {@code null} if no colors have been selected.
1179         */
1180        public Color getMostRecentColor() {
1181            Color c = null;
1182            if (!colors.isEmpty()) {
1183                c = colors.get(0);
1184            }
1185            return c;
1186        }
1187
1188        /**
1189         * This method currently does nothing.
1190         *
1191         * @param e Ignored.
1192         */
1193        @Override public void actionPerformed(ActionEvent e) {
1194            // noop
1195        }
1196    }
1197}