org.nuiton.jaxx.runtime.swing.BlockingLayerUI Maven / Gradle / Ivy
The newest version!
/*
* #%L
* JAXX :: Runtime
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.jaxx.runtime.swing;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* A JXLayer ui implementation that permits to block a component but still
* allow an action when clicking on the right-top icon painted on the layer.
*
* You can change the blocking and accepting icon.
*
* To hook an click on the layer's icon, you can :
*
* - pass an Action via method {@link #setAcceptAction(Action)}
* - override the method {@link #acceptEvent(MouseEvent, JXLayer)}
*
*
* @author Tony Chemit - [email protected]
* @since 1.2
*/
public class BlockingLayerUI extends AbstractLayerUI {
public static final String CAN_CLICK_PROPERTY = "canClick";
public static final String ACCEPT_ICON_PROPERTY = "acceptIcon";
public static final String BLOCK_ICON_PROPERTY = "blockIcon";
public static final String BLOCK_PROPERTY = "block";
private static final long serialVersionUID = 7520066137574942937L;
// private static final long serialVersionUID = 1L;
/** Action to be treated when click on icon */
protected Action acceptAction;
/** Icon when you can not click */
protected BufferedImage blockIcon;
/** Icon when you can click */
protected BufferedImage acceptIcon;
/** Optinal color to put fill background when blocking */
protected Color blockingColor;
/** Internal state to known when we can accept click */
protected boolean canClick;
/**
* A flag to enable or disable the use of the icon.
*
* If set to false, no icon will be displayed and no action
* will be possible.
*
* By default, this is active.
*/
protected boolean useIcon = true;
/** Internal state when should block event and paint layer */
protected boolean block;
/** Extra components names to accept even in block mode */
protected final Set acceptedComponentNames;
/** Extra components types to accept even in block mode */
protected final Set> acceptedComponentTypes;
public BlockingLayerUI() {
acceptedComponentNames = new HashSet<>();
acceptedComponentTypes = new HashSet<>();
}
public boolean isBlock() {
return block;
}
public void setAcceptedComponentNames(String... acceptedComponentNames) {
this.acceptedComponentNames.clear();
this.acceptedComponentNames.addAll(Arrays.asList(acceptedComponentNames));
setDirty(true);
}
public void setAcceptedComponentTypes(Class>... acceptedComponentTypes) {
this.acceptedComponentTypes.clear();
this.acceptedComponentTypes.addAll(Arrays.asList(acceptedComponentTypes));
setDirty(true);
}
public void setAcceptAction(Action acceptAction) {
this.acceptAction = acceptAction;
}
public void setAcceptIcon(ImageIcon acceptIcon) {
this.acceptIcon = prepareIcon(acceptIcon);
firePropertyChange(ACCEPT_ICON_PROPERTY, null, acceptIcon);
setDirty(true);
}
public void setBlockIcon(ImageIcon blockIcon) {
this.blockIcon = prepareIcon(blockIcon);
firePropertyChange(BLOCK_ICON_PROPERTY, null, blockIcon);
setDirty(true);
}
public void setCanClick(boolean canClick) {
boolean oldvalue = this.canClick;
this.canClick = canClick;
firePropertyChange(CAN_CLICK_PROPERTY, oldvalue, canClick);
if (oldvalue != canClick) {
setDirty(true);
}
}
public void setBlock(boolean block) {
boolean oldvalue = this.block;
this.block = block;
firePropertyChange(BLOCK_PROPERTY, oldvalue, block);
if (oldvalue != block) {
setDirty(true);
}
}
@Override
public void setDirty(boolean isDirty) {
super.setDirty(isDirty);
}
public void setBlockIcon(BufferedImage blockIcon) {
this.blockIcon = blockIcon;
}
public void setBlockingColor(Color blockingColor) {
this.blockingColor = blockingColor;
}
public BufferedImage getBlockIcon() {
return blockIcon;
}
protected BufferedImage getAcceptIcon() {
return acceptIcon;
}
public boolean isCanClick() {
return canClick;
}
public void setUseIcon(boolean useIcon) {
boolean oldvalue = this.useIcon;
this.useIcon = useIcon;
if (oldvalue != useIcon) {
setDirty(true);
}
}
@Override
public BlockingLayerUI clone() throws CloneNotSupportedException {
BlockingLayerUI clone = (BlockingLayerUI) super.clone();
clone.acceptAction = acceptAction;
clone.acceptIcon = acceptIcon;
clone.blockIcon = blockIcon;
clone.useIcon = useIcon;
clone.block = block;
clone.blockingColor = blockingColor;
clone.setCanClick(false);
return clone;
}
@Override
protected void processKeyEvent(KeyEvent e, JXLayer extends JComponent> l) {
if (useIcon) {
e.consume();
} else if (block) {
acceptEventOrConsumeIt(e);
}
}
@Override
protected void processMouseMotionEvent(MouseEvent e, JXLayer extends JComponent> l) {
if (useIcon) {
updateCanClickState(l, e);
e.consume();
} else if (block) {
acceptEventOrConsumeIt(e);
}
}
@Override
protected void processMouseEvent(MouseEvent e, JXLayer extends JComponent> l) {
if (useIcon) {
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
updateCanClickState(l, e);
break;
case MouseEvent.MOUSE_EXITED:
setCanClick(false);
break;
case MouseEvent.MOUSE_CLICKED:
if (canClick) {
acceptEvent(e, l);
}
break;
}
e.consume();
} else if (block) {
if (acceptEventOrConsumeIt(e)) {
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
break;
case MouseEvent.MOUSE_EXITED:
break;
case MouseEvent.MOUSE_CLICKED:
acceptEvent(e, l);
break;
}
}
}
}
@Override
protected void processMouseWheelEvent(MouseWheelEvent e, JXLayer extends JComponent> l) {
if (useIcon) {
e.consume();
} else if (block) {
if (acceptEventOrConsumeIt(e)) {
acceptEvent(e, l);
}
}
}
protected boolean acceptEventOrConsumeIt(InputEvent e) {
Object source = e.getSource();
boolean accept = source instanceof JComponent;
if (accept) {
JComponent component = (JComponent) source;
accept = acceptedComponentNames.contains(component.getName());
if (!accept && component instanceof AbstractButton) {
AbstractButton button = (AbstractButton) component;
String actionCommand = button.getActionCommand();
accept = actionCommand != null && acceptedComponentNames.contains(actionCommand);
}
if (!accept) {
for (Class> acceptedComponentType : acceptedComponentTypes) {
accept = acceptedComponentType.isAssignableFrom(component.getClass());
if (accept) {
break;
}
}
}
}
if (!accept) {
e.consume();
}
return accept;
}
public boolean acceptEventOrConsumeIt(ActionEvent e) {
if (!block) {
return true;
}
Object source = e.getSource();
String actionCommand = e.getActionCommand();
boolean accept = source instanceof JComponent;
if (accept) {
JComponent component = (JComponent) source;
accept = acceptedComponentNames.contains(component.getName());
if (!accept) {
accept = acceptedComponentNames.contains(actionCommand);
}
if (!accept) {
for (Class> acceptedComponentType : acceptedComponentTypes) {
accept = acceptedComponentType.isAssignableFrom(component.getClass());
if (accept) {
break;
}
}
}
}
return accept;
}
@Override
protected void paintLayer(Graphics2D g2, JXLayer extends JComponent> l) {
super.paintLayer(g2, l);
if (block && blockingColor != null) {
// to be in sync with the view if the layer has a border
/*Insets layerInsets = l.getInsets();
g2.translate(layerInsets.left, layerInsets.top);
JComponent view = l.getView();
// To prevent painting on view's border
Insets insets = view.getInsets();
g2.clip(new Rectangle(insets.left, insets.top,
view.getWidth() - insets.left - insets.right,
view.getHeight() - insets.top - insets.bottom));
*/
g2.setColor(blockingColor);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .1f));
g2.fillRect(0, 0, l.getWidth(), l.getHeight());
}
if (useIcon && getCurrentIcon() != null) {
g2.drawImage(getCurrentIcon(), l.getWidth() - getCurrentIcon().getWidth() - 1, 0, null);
}
}
protected void acceptEvent(MouseEvent e, JXLayer extends JComponent> l) {
if (acceptAction != null) {
acceptAction.putValue("layer", l);
Component source = l.getView();
acceptAction.actionPerformed(new ActionEvent(source, 0, "accept"));
}
}
protected BufferedImage getCurrentIcon() {
return canClick ? acceptIcon : blockIcon;
}
protected BufferedImage prepareIcon(ImageIcon image) {
BufferedImage icon = new BufferedImage(image.getIconWidth(), image.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) icon.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2.drawImage(image.getImage(), 0, 0, null);
g2.dispose();
return icon;
}
protected void updateCanClickState(JXLayer extends JComponent> l, MouseEvent e) {
// udpate toolTipText
Point layerLocation = l.getView().getLocation();
Point mousePoint = e.getPoint();
BufferedImage currentIcon = getCurrentIcon();
if (currentIcon == null) {
setCanClick(false);
return;
}
int minX = (int) layerLocation.getX() + l.getWidth() - currentIcon.getWidth();
int maxX = (int) layerLocation.getX() + l.getWidth();
int minY = 0;
int maxY = currentIcon.getHeight();
boolean accept = minX <= mousePoint.getX() && mousePoint.getX() <= maxX;
accept &= minY <= mousePoint.getLocation().getY() && mousePoint.getLocation().getY() <= maxY;
setCanClick(accept);
}
}