/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.chooser; import com.mucommander.ui.icon.IconManager; import javax.swing.*; import java.awt.*; import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.WeakHashMap; /** * @author Maxence Bernard */ public class ColorPicker extends JButton implements ActionListener, AWTEventListener { private Robot robot; private boolean isActive; private WeakHashMap<ColorChangeListener, ?> listeners = new WeakHashMap<ColorChangeListener, Object>(); /** True if this component is supported (java.awt.Robot can be used) */ private static boolean isSupported; static { try { new Robot(); isSupported = true; } catch(Exception e) { // java.awt.Robot constructor throws an AWTException "if the platform configuration does not allow low-level input control." // In this case, isSupported will be false } } public ColorPicker() { super(IconManager.getIcon(IconManager.COMMON_ICON_SET, "picker.png")); addActionListener(this); } public static boolean isSupported() { return isSupported; } public void setActive(boolean active) { if(active==isActive) return; final Toolkit toolkit = Toolkit.getDefaultToolkit(); if(active) { if(!isVisible()) return; try { // Create a java.awt.Robot operating on the screen device that contains the window this component is in. // Not sure what happens if the window spawns across 2 screens... robot = new Robot(getTopLevelAncestor().getGraphicsConfiguration().getDevice()); } catch(Exception e) { // If Robot is not available, ColorPicker will simply be ineffective, clicking it won't do anything return; } // Change the cursor to the 'eye dropper' icon filled with white setPickerCursor(Color.WHITE); // These are invoked after all pending events are processed SwingUtilities.invokeLater(new Runnable() { public void run() { // Listen to all mouse events on the window that contains this button toolkit.addAWTEventListener(ColorPicker.this, AWTEvent.MOUSE_MOTION_EVENT_MASK|AWTEvent.MOUSE_EVENT_MASK); // Leave this button selected until a color is picked or this button is pressed again (to cancel) setSelected(true); } }); } else { // Stop listening to mouse events toolkit.removeAWTEventListener(this); // Restore default cursor setCustomCursor(Cursor.getDefaultCursor()); // Make the button unselected setSelected(false); } this.isActive = active; } public boolean isActive() { return isActive; } public void addColorChangeListener(ColorChangeListener listener) { listeners.put(listener, null); } public void removeColorChangeListener(ColorChangeListener listener) { listeners.remove(listener); } private void setPickerCursor(Color fillColor) { ImageIcon cursorIcon = (ImageIcon)getIcon(); int iconWidth = cursorIcon.getIconWidth(); int iconHeight = cursorIcon.getIconHeight(); int colorRGB = fillColor.getRGB(); // Retrieve the cursor icon fill mask as an alpha-enabled BufferedImage BufferedImage iconMaskBi = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB); Graphics g = iconMaskBi.getGraphics(); g.drawImage(IconManager.getIcon(IconManager.COMMON_ICON_SET, "picker_mask.png").getImage(), 0, 0, null); // Replace solid (non-transparent) pixels with specified fill color for(int y=0; y<iconHeight; y++) { for(int x=0; x<iconWidth; x++) { int rgba = iconMaskBi.getRGB(x, y); if((rgba>>24)!=0) iconMaskBi.setRGB(x, y, colorRGB); } } // Retrieve the cursor icon as an alpha-enabled BufferedImage and paint the fill mask on it BufferedImage iconBi = new BufferedImage(cursorIcon.getIconWidth(), cursorIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); g = iconBi.getGraphics(); g.drawImage(cursorIcon.getImage(), 0, 0, null); g.drawImage(iconMaskBi, 0, 0, null); setCustomCursor(Toolkit.getDefaultToolkit().createCustomCursor(iconBi, new Point(0,15), getClass().getName())); } private void setCustomCursor(Cursor cursor) { getTopLevelAncestor().setCursor(cursor); } private void fireColorPicked(Color color) { // Iterate on all listeners for(ColorChangeListener listener : listeners.keySet()) listener.colorChanged(new ColorChangeEvent(this, color)); } /////////////////////////////////// // ActionListener implementation // /////////////////////////////////// public void actionPerformed(ActionEvent actionEvent) { setActive(!isActive); } ///////////////////////////////////// // AWTEventListener implementation // ///////////////////////////////////// public void eventDispatched(AWTEvent awtEvent) { if(awtEvent instanceof MouseEvent) { MouseEvent mouseEvent = (MouseEvent)awtEvent; Point mousePoint = mouseEvent.getPoint(); Component source = (Component)mouseEvent.getSource(); // Convert the mouse X/Y into screen coordinates SwingUtilities.convertPointToScreen(mousePoint, source); int x = (int)mousePoint.getX(); int y = (int)mousePoint.getY(); // Retrieve the color of the pixel the mouse is currently over Color color = robot.getPixelColor(x, y); int button = mouseEvent.getButton(); if(button!=MouseEvent.NOBUTTON) { // If left button was clicked (not released) if(button==MouseEvent.BUTTON1 && (mouseEvent.getModifiers()&MouseEvent.MOUSE_CLICKED)!=0) { // If this color picker was clicked, cancel the color picking without firing an event if(source!=this) fireColorPicked(color); // Consume the event so that it doesn't get caught by a clicked component mouseEvent.consume(); // We're done setActive(false); } // If any other button was clicked or if this is not a MOUSE_CLICKED event, do nothing return; } setPickerCursor(color); } } }