biskuteri-cafe-JKomasto2/TwoToggleButton.java

509 lines
13 KiB
Java

/* copyright
This file is part of JKomasto2.
Written in 2022 by Usawashi <usawashi16@yahoo.co.jp>
This program 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.
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 Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
copyright */
import javax.swing.AbstractButton;
import javax.swing.DefaultButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.Component;
import java.awt.image.BufferedImage;
import java.awt.geom.Ellipse2D;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import java.net.URL;
import javax.swing.JFrame;
class
TwoToggleButton extends AbstractButton
implements KeyListener, MouseListener, FocusListener {
private String
primaryName,
secondaryName;
// - -%- -
private boolean
primaryToggled = false,
secondaryToggled = false;
private Image
primaryToggledIcon,
secondaryToggledIcon,
primaryUntoggledIcon,
secondaryUntoggledIcon;
private int
nextEventID = ActionEvent.ACTION_FIRST;
// - -%- -
private static Image
button,
selectedOverlay,
disabledOverlay;
// ---%-@-%---
public void
togglePrimary()
{
setPrimaryToggled(!primaryToggled);
}
public void
toggleSecondary()
{
setSecondaryToggled(!secondaryToggled);
}
public void
setPrimaryToggled(boolean toggled)
{
primaryToggled = toggled;
repaint();
announce(primaryName, toggled);
}
public void
setSecondaryToggled(boolean toggled)
{
secondaryToggled = toggled;
repaint();
announce(secondaryName, toggled);
}
public boolean
getPrimaryToggled()
{
return primaryToggled;
}
public boolean
getSecondaryToggled()
{
return secondaryToggled;
}
// - -%- -
private void
announce(String name, boolean toggled)
{
ActionEvent eA = new ActionEvent(
this, nextEventID++,
name + (toggled ? "On" : "Off")
);
for (ActionListener listener: getActionListeners())
listener.actionPerformed(eA);
}
protected void
paintComponent(Graphics g)
{
g.drawImage(button, 0, 0, this);
if (!isEnabled())
g.drawImage(disabledOverlay, 0, 0, this);
if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this);
if (secondaryToggled)
g.drawImage(secondaryToggledIcon, 0, 0, this);
else
g.drawImage(secondaryUntoggledIcon, 0, 0, this);
if (primaryToggled)
g.drawImage(primaryToggledIcon, 0, 0, this);
else
g.drawImage(primaryUntoggledIcon, 0, 0, this);
}
public void
mousePressed(MouseEvent eM)
{
boolean shift = eM.isShiftDown();
boolean prim = eM.getButton() == MouseEvent.BUTTON1;
boolean secon = eM.getButton() == MouseEvent.BUTTON3;
secon |= shift && prim;
if (secon) toggleSecondary();
else if (prim) togglePrimary();
requestFocusInWindow();
}
public void
keyPressed(KeyEvent eK)
{
boolean shift = eK.isShiftDown();
boolean prim = eK.getKeyCode() == KeyEvent.VK_SPACE;
boolean secon = eK.getKeyCode() == KeyEvent.VK_ENTER;
secon |= shift && prim;
if (secon) toggleSecondary();
else if (prim) togglePrimary();
requestFocusInWindow();
}
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
keyReleased(KeyEvent eK) { }
public void
keyTyped(KeyEvent eK) { }
// (悪) If we don't have the 'armed' behaviour of JButton,
// we really need to rectify that, it's important for this
// use case.
// ---%-@-%---
TwoToggleButton(String primaryName, String secondaryName)
{
if (button == null) loadCommonImages();
this.primaryName = primaryName;
this.secondaryName = secondaryName;
setModel(new DefaultButtonModel());
setFocusable(true);
setOpaque(false);
int w = button.getWidth(null);
int h = button.getHeight(null);
setPreferredSize(new Dimension(w, h));
loadSpecificImages();
this.addKeyListener(this);
this.addMouseListener(this);
this.addFocusListener(this);
}
private void
loadSpecificImages()
{
String p1 = "graphics/" + primaryName + "Toggled.png";
String p2 = "graphics/" + secondaryName + "Toggled.png";
String p3 = "graphics/" + primaryName + "Untoggled.png";
String p4 = "graphics/" + secondaryName + "Untoggled.png";
URL u1 = getClass().getResource(p1);
URL u2 = getClass().getResource(p2);
URL u3 = getClass().getResource(p3);
URL u4 = getClass().getResource(p4);
if (u1 == null) primaryToggledIcon = null;
else primaryToggledIcon = new ImageIcon(u1).getImage();
if (u2 == null) secondaryToggledIcon = null;
else secondaryToggledIcon = new ImageIcon(u2).getImage();
if (u3 == null) primaryUntoggledIcon = null;
else primaryUntoggledIcon = new ImageIcon(u3).getImage();
if (u4 == null) secondaryUntoggledIcon = null;
else secondaryUntoggledIcon = new ImageIcon(u4).getImage();
}
// - -%- -
private static void
loadCommonImages()
{
Class c = TwoToggleButton.class;
URL u1 = c.getResource("graphics/button.png");
URL u2 = c.getResource("graphics/disabledOverlay.png");
URL u3 = c.getResource("graphics/selectedOverlay.png");
assert u1 != null && u2 != null && u3 != null;
button = new ImageIcon(u1).getImage();
disabledOverlay = new ImageIcon(u2).getImage();
selectedOverlay = new ImageIcon(u3).getImage();
}
}
class
RoundButton extends AbstractButton
implements KeyListener, MouseListener, FocusListener {
private Image
image;
// - -%- -
private Image
copy,
scaled;
private int
nextEventID = ActionEvent.ACTION_FIRST;
// - -%- -
private static Image
button,
selectedOverlay,
disabledOverlay;
private static Shape
roundClip;
// ---%-@-%---
public void
setImage(Image n)
{
image = n;
copy = null;
scaled = null;
if (image != null)
{
image.flush();
prepareImage(image, this);
}
}
// - -%- -
public boolean
imageUpdate(Image img, int f, int x, int y, int w, int h)
{
// AbstractButton overrode this to refuse updates for
// images that aren't the button's icon. We don't use
// the icon, so we're overriding it back. Also, we have
// some async work to do regarding the images.
if ((f & (ABORT|ERROR)) != 0) return false;
boolean all = (f & ALLBITS) != 0;
boolean frame = (f & FRAMEBITS) != 0;
boolean some = (f & SOMEBITS) != 0;
if (frame && img != this.image) return false;
if (img == this.image && (all || frame))
{
int ow = img.getWidth(null);
int oh = img.getHeight(null);
if (copy == null)
{
int type = BufferedImage.TYPE_INT_ARGB;
copy = new BufferedImage(ow, oh, type);
}
Graphics g = copy.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
int algo = Image.SCALE_SMOOTH;
Rectangle b = roundClip.getBounds();
int sw = ow > oh ? -1 : b.width;
int sh = oh > ow ? -1 : b.height;
scaled = copy.getScaledInstance(sw, sh, algo);
/*
* We create a scaled instance from a BufferedImage copy
* rather than this.image directly, to avoid a ClassCast
* Exception bug in the JDK, where ColorModel was
* incorrectly casting an int array of input data into
* a byte array. I'm not sure why that bug exists nor
* why they haven't noticed it, but.
*/
repaint();
}
if (img == scaled && (some || all))
{
repaint();
}
return all ? false : true;
}
protected void
paintComponent(Graphics g)
{
g.drawImage(button, 0, 0, this);
if (!isEnabled())
g.drawImage(disabledOverlay, 0, 0, this);
if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this);
if (scaled == null) return;
Rectangle b = roundClip.getBounds();
Shape defaultClip = g.getClip();
g.setClip(roundClip);
g.drawImage(scaled, b.x, b.y, this);
getParent().repaint();
// I don't know why, but when we repaint ourselves, our
// parent doesn't repaint, so nothing seems to happen.
g.setClip(defaultClip);
}
private void
announce()
{
ActionEvent eA = new ActionEvent(this, nextEventID++, null);
for (ActionListener listener: getActionListeners())
listener.actionPerformed(eA);
}
public void
keyPressed(KeyEvent eK)
{
if (eK.getKeyCode() != KeyEvent.VK_SPACE) return;
doClick();
}
public void
mouseClicked(MouseEvent eM) { announce(); }
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
mousePressed(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
keyReleased(KeyEvent eK) { }
public void
keyTyped(KeyEvent eK) { }
// (悪) Again, armed?
// ---%-@-%---
RoundButton()
{
if (button == null) loadCommonImages();
setModel(new DefaultButtonModel());
setFocusable(true);
setOpaque(false);
int w = button.getWidth(null);
int h = button.getHeight(null);
setPreferredSize(new Dimension(w, h));
this.addKeyListener(this);
this.addMouseListener(this);
this.addFocusListener(this);
}
// - -%- -
private static void
loadCommonImages()
{
Class c = TwoToggleButton.class;
URL u1 = c.getResource("graphics/button.png");
URL u2 = c.getResource("graphics/disabledOverlay.png");
URL u3 = c.getResource("graphics/selectedOverlay.png");
assert u1 != null && u2 != null && u3 != null;
button = new ImageIcon(u1).getImage();
disabledOverlay = new ImageIcon(u2).getImage();
selectedOverlay = new ImageIcon(u3).getImage();
int radius = 6;
roundClip = new Ellipse2D.Float(
radius,
radius,
button.getWidth(null) - (2 * radius),
button.getHeight(null) - (2 * radius)
);
}
}
class
TwoToggleButtonTest {
public static void
main(String... args)
{
if (args.length != 2)
{
String err = "Please give two toggle names.";
System.err.println(err);
System.exit(1);
}
String p = args[0], s = args[1];
TwoToggleButton t1 = new TwoToggleButton(p, s);
TwoToggleButton t2 = new TwoToggleButton(p, s);
TwoToggleButton t3 = new TwoToggleButton(p, s);
JFrame mainframe = new JFrame("Two-toggle button test");
mainframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainframe.setLocationByPlatform(true);
mainframe.add(t1, java.awt.BorderLayout.WEST);
mainframe.add(t2);
mainframe.add(t3, java.awt.BorderLayout.EAST);
mainframe.pack();
t2.setEnabled(false);
t3.requestFocusInWindow();
mainframe.setVisible(true);
}
}