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