Button with image, text, arrow and menu - java

I want to create a button that has an icon with text on the top and arrow on the corner. Upon clicking on it, a menu is displayed. I was able to achieve all of above except for the arrow part. I know I can have an an image file that has the icon and arrow saved in one png file. However, I don't want to alter the icon file. Here is the code I have so far.
How can I add an arrow ( whether programmtically or from another arrow image file to the button)
public class JButtonMenu extends JToggleButton {
JPopupMenu popup;
public JButtonMenu(ImageIcon img, String title, String []list) {
super(name);
this.popup = new JPopupMenu();
this.buttonId = buttonId;
this.setMenuList(list); //This is another method
setIcon(img);
setVerticalTextPosition(SwingConstants.TOP);
setHorizontalTextPosition(SwingConstants.CENTER);
}
public void setMenuList(String[]list){
if(list == null){
return;
}
for(String item:list){
popup.add(new JMenuItem(new AbstractAction(item) {
public void actionPerformed(ActionEvent e) {
JMenuItem menuItem = (JMenuItem)e.getSource();
int index= popup.getComponentIndex(menuItem);
menuItemListener.itemSelectedListener(buttonId,index, menuItem.getText());
}
}));
}
}
}

This is basically a watered down version of this implementation of a split button but which focuses on the need for painting an additional image as well as some of the other functionality you'll need to implement to ensure that the original text and icon are offset accurtaly.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
SplitButton btn = new SplitButton();
btn.setText("This is a split button");
JFrame frame = new JFrame("Testing");
frame.setLayout(new GridBagLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btn);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 30;
private int arrowSize = 8;
private Rectangle splitRectangle;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
public SplitButton() {
super();
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(isEnabled() ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
}
}
Swing has a well defined and documented painting process, in order to perform custom painting you need to work within the constraints of the API otherwise you will end up with no end ot issues.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details

A quick way to do it would be
Image img1=imageIcon1.getImage();
Image img2=imageIcon2.getImage();
Graphics g=img1.getGraphics();
g.drawImage(img2, x, y, sizex, sizey, null)
where x, y is where you put the second icon (arrow) on the first icon and sizex, sizey is the reduced size of the second icon. You can change these by trying.
You have to have another method
public JButtonMenu(ImageIcon imageIcon1, ImageIcon imageIcon2, String title, String []list) {
// above segment here
// continue with the rest of the code
}

Related

How do I double-buffer in Java Swing on a Retina display without losing the higher resolution?

I'm using double-buffered graphics in my JLayer subclass to implement a simple swipe animation in a Java Swing application. It works fine on the older displays, but when I run it on a Retina display, the screen loses the doubled-resolution when the animation starts, and gets it back when it ends. I'm not sure how to maintain the higher resolution during the animation.
My animate method originally looked like this:
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> { /* omitted for brevity */ };
timer.addActionListener(actionListener);
int imageType = BufferedImage.TYPE_INT_ARGB;
upcomingScreen = new BufferedImage(liveComponent.getWidth(), liveComponent.getHeight(), imageType);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D); // liveComponent is a JComponent
graphics2D.dispose();
timer.start();
}
I tried doubling the image size, but that didn't help.
upcomingScreen = new BufferedImage(liveComponent.getWidth()*2, liveComponent.getHeight()*2, imageType);
To reflect these changes, I changed my drawing code in LayerUI by doubling xLimit, width, height:
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth()*2 * frame) / maxFrames;
int width = c.getWidth()*2;
int height = c.getHeight()*2;
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
super.paint(g, c);
}
}
This doesn't help. It draws the same with or without this last change, which makes no sense.
Here is a class that illustrates the problem:
/**
* <p>Created by IntelliJ IDEA.
* <p>Date: 5/2/20
* <p>Time: 10:25 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings({"HardcodedLineSeparator", "StringConcatenation", "HardCodedStringLiteral", "DuplicatedCode"})
public final class SwipeViewTest extends JPanel {
public static final String text1 = "Demo of Swipe View.\n\nThe swipe button will toggle between two pages of text. It has a built-in " +
"special effect, which is a swipe. When you hit the swipe button, it should flip between two pages of text. This worked fine on " +
"the older displays, but for some reason, on a Retina display, the text briefly switches to low resolution as the swipe proceeds, " +
"then switches back once it has finished. This code is written for retina displays. I don't know if it will work for the older, " +
"low resolution displays.\n\nYou can watch it swipe by hitting the space bar or by clicking the swipe button.";
public static final String text2 = "Demo of Swipe View.\n\nThis is the second page of the swipe-text demo. The change in resolution is " +
"most easily noticed when watching the line at the top, which doesn't change as the swipe is performed.";
private final SwipeView<TestView> swipeView;
private final TestView testView;
public static void main(String[] args) {
JFrame frame = new JFrame("SwipeView demo");
SwipeViewTest comp = new SwipeViewTest();
comp.install();
frame.add(comp);
frame.setLocationByPlatform(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private boolean page1 = true;
private SwipeViewTest() {
super(new BorderLayout());
testView = new TestView();
swipeView = SwipeView.wrap(testView, 1000);
add(BorderLayout.CENTER, swipeView.getLayer());
}
private void install() {
JButton jButton = new JButton("Swipe");
jButton.addActionListener(this::doSwipe);
add(jButton, BorderLayout.PAGE_END);
AncestorListener ancestorListener = new AncestorListener() {
#Override
public void ancestorAdded(final AncestorEvent event) {
JComponent button = event.getComponent();
button.requestFocus();
button.removeAncestorListener(this); // execute only once.
}
#Override public void ancestorRemoved(final AncestorEvent event) { }
#Override public void ancestorMoved(final AncestorEvent event) { }
};
jButton.addAncestorListener(ancestorListener);
}
private void doSwipe(ActionEvent ignored) {
swipeView.swipeLeft(this::flipPage);
}
private void flipPage() {
page1 = !page1;
if (page1) {
testView.setText(text1);
} else {
testView.setText(text2);
}
}
private static class TestView extends JPanel {
private final JTextArea textArea;
TestView() {
super(new BorderLayout());
textArea = new JTextArea(20, 40);
JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setText(text1);
add(scrollPane, BorderLayout.CENTER);
}
private void setText(String text) {
textArea.setText(text);
}
}
/**
* SwipeView adds a swipe special effect to a Component. This draws a swipe-right or swipe-left effect on a chosen
* action. It also optionally supports a repeated action when the mouse is held down.
* <p>
* This class is very specific right now, but I hope to generalize it for other special effects later.
* <p>Created by IntelliJ IDEA.
* <p>Date: 4/4/18
* <p>Time: 12:38 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings("MagicNumber")
public static final class SwipeView<C extends JComponent> extends LayerUI<C> {
public static <J extends JComponent> SwipeView<J> wrap(J view, int durationMillis) {
JLayer<J> jLayer = new JLayer<>(view);
final SwipeView<J> ui = new SwipeView<>(view, jLayer, durationMillis);
jLayer.setUI(ui);
return ui;
}
private final C liveComponent;
private Image priorScreen = null;
private Image upcomingScreen = null;
private final JLayer<C> layer;
private boolean isAnimating = false;
private SwipeDirection swipeDirection = SwipeDirection.SWIPE_RIGHT;
private final int maxFrames;
// Calculated:
#SuppressWarnings("FieldCanBeLocal")
private final int frameMillis;
private int frame = 0;
private final long startTime = System.currentTimeMillis();
private SwipeView(C view, JLayer<C> theLayer, int animationDurationMillis) {
super();
liveComponent = view;
layer = theLayer;
maxFrames = (30 * animationDurationMillis) / 1000;
frameMillis = animationDurationMillis / maxFrames;
}
public JLayer<C> getLayer() { return layer; }
/**
* Perform the specified operation with a swipe-right special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeRight(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-right after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeRight(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_RIGHT);
}
/**
* Perform the specified operation with a swipe-left special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-Left after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeLeft(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_LEFT);
}
private void swipe(Runnable operation, SwipeDirection swipeDirection) {
prepareToAnimate(swipeDirection);
operation.run();
animate();
}
// #SuppressWarnings({"HardCodedStringLiteral", "HardcodedFileSeparator"})
#Override
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth() * 2 * frame) / maxFrames;
if (swipeDirection == SwipeDirection.SWIPE_LEFT) {
xLimit = (c.getWidth() * 2) - xLimit;
}
int width = c.getWidth() * 2;
int height = c.getHeight() * 2;
// //noinspection UseOfSystemOutOrSystemErr
// System.out.printf("Dimensions: Frame: %d/%d (at %d) xLimit: %4d (%4d x %4d) (from %4d x %4d) Animating: %b%n",
// frame, maxFrames, System.currentTimeMillis() - startTime, xLimit, width, height, c.getWidth(), c.getHeight(), isAnimating);
assert upcomingScreen != null;
assert priorScreen != null;
Image pScreen = Objects.requireNonNull(priorScreen);
Image uScreen = Objects.requireNonNull(upcomingScreen);
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
}
} else {
super.paint(g, c);
}
}
private void prepareToAnimate(SwipeDirection swipeDirection) {
this.swipeDirection = swipeDirection;
isAnimating = true;
frame = 0;
// Save current state
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
}
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> {
frame++;
layer.repaint();
if (frame == maxFrames) {
frame = 0;
isAnimating = false;
timer.stop(); // Investigate: Am I leaking timers?
}
};
timer.addActionListener(actionListener);
upcomingScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
timer.start();
}
}
public static enum SwipeDirection {
#SuppressWarnings("JavaDoc") SWIPE_RIGHT,
#SuppressWarnings("JavaDoc") SWIPE_LEFT
}
}
I don't use a retina display, but I did notice a slight painting difference when the animation started.
I changed both of your BufferedImage to get rid of the alpha value and I no longer notice the painting difference:
//priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_RGB);
It turns out I needed to change the way I animated the frame to account for the doubling of the scale.
First, I needed to detect the scale. I added this code, which requires Java 9 or greater to work correctly. (It compiles under java 8, but fails to execute correctly, always returning 1 for any screen.)
private static final int SCALE = calculateScaleForDefaultScreen();
private static int calculateScaleForDefaultScreen() {
// scale will be 2.0 for a Retina screen, and 1.0 for an older screen
double scale = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getDefaultTransform()
.getScaleX(); // Requires Java 9+ to work. Compiles under Java 8 but always returns 1.0.
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(scale);
}
When I prepared my two off-screen graphics, I needed to do so at twice the scale:
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the current state of liveComponent into the image
graphics2D.dispose();
And…
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the upcoming state of liveComponent into the image
graphics2D.dispose();
Then, when I did my animation, I needed to include the SCALE in the drawing.
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
}
There are several other places where I multiplied widths and heights by 2. I changed those to SCALE as well.
I wish there were a more elegant solution, but this works.

How do I change background of component mouse is hovering on?

I have a chess grid with dimensions 7 x 6, each square has a background, I want that background to change hue to red as I hover on it, then return to normal as I hover on something else, how do I achieve this using the mouse listeners?
This is my mouse dragged listener:
public void mouseDragged(MouseEvent me) {
if (chessPiece == null)
return;
// The drag location should be within the bounds of the chess board
int x = me.getX() + xAdjustment;
int xMax = chessBoard.getWidth() - chessPiece.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = chessBoard.getHeight() - chessPiece.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
chessPiece.setLocation(x, y); //drags the piece as i move, i want to
//change the hue of the square/Jpanel
//under my cursor as i move.
Component c= chessBoard.getComponentAt(chessPiece.getX(),chessPiece.getY());
}
Square is an instance of BackgroundPanel
package model.gui;
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
/*
* Support custom painting on a panel in the form of
*
* a) images - that can be scaled, tiled or painted at original size
* b) non solid painting - that can be done by using a Paint object
*
* Also, any component added directly to this panel will be made
* non-opaque so that the custom painting can show through.
*/
public class BackgroundPanel extends JPanel
{
public static final int SCALED = 0;
public static final int TILED = 1;
public static final int ACTUAL = 2;
private Paint painter;
private Image image;
private int style = SCALED;
private float alignmentX = 0.5f;
private float alignmentY = 0.5f;
private boolean isTransparentAdd = true;
Graphics g;
/*
* Set image as the background with the SCALED style
*/
public BackgroundPanel(Image image)
{
this(image, SCALED);
}
/*
* Set image as the background with the specified style
*/
public BackgroundPanel(Image image, int style)
{
setImage( image );
setStyle( style );
setLayout( new BorderLayout() );
}
/*
* Set image as the backround with the specified style and alignment
*/
public BackgroundPanel(Image image, int style, float alignmentX, float alignmentY)
{
setImage( image );
setStyle( style );
setImageAlignmentX( alignmentX );
setImageAlignmentY( alignmentY );
setLayout( new BorderLayout() );
}
/*
* Use the Paint interface to paint a background
*/
public BackgroundPanel(Paint painter)
{
setPaint( painter );
setLayout( new BorderLayout() );
}
/*
* Set the image used as the background
*/
public void setImage(Image image)
{
this.image = image;
repaint();
}
/*
* Set the style used to paint the background image
*/
public void setStyle(int style)
{
this.style = style;
repaint();
}
/*
* Set the Paint object used to paint the background
*/
public void setPaint(Paint painter)
{
this.painter = painter;
repaint();
}
/*
* Specify the horizontal alignment of the image when using ACTUAL style
*/
public void setImageAlignmentX(float alignmentX)
{
this.alignmentX = alignmentX > 1.0f ? 1.0f : alignmentX < 0.0f ? 0.0f : alignmentX;
repaint();
}
/*
* Specify the horizontal alignment of the image when using ACTUAL style
*/
public void setImageAlignmentY(float alignmentY)
{
this.alignmentY = alignmentY > 1.0f ? 1.0f : alignmentY < 0.0f ? 0.0f : alignmentY;
repaint();
}
/*
* Override method so we can make the component transparent
*/
public void add(JComponent component)
{
add(component, null);
}
/*
* Override to provide a preferred size equal to the image size
*/
#Override
public Dimension getPreferredSize()
{
if (image == null)
return super.getPreferredSize();
else
return new Dimension(image.getWidth(null), image.getHeight(null));
}
/*
* Override method so we can make the component transparent
*/
public void add(JComponent component, Object constraints)
{
if (isTransparentAdd)
{
makeComponentTransparent(component);
}
super.add(component, constraints);
}
/*
* Controls whether components added to this panel should automatically
* be made transparent. That is, setOpaque(false) will be invoked.
* The default is set to true.
*/
public void setTransparentAdd(boolean isTransparentAdd)
{
this.isTransparentAdd = isTransparentAdd;
}
/*
* Try to make the component transparent.
* For components that use renderers, like JTable, you will also need to
* change the renderer to be transparent. An easy way to do this it to
* set the background of the table to a Color using an alpha value of 0.
*/
private void makeComponentTransparent(JComponent component)
{
component.setOpaque( false );
if (component instanceof JScrollPane)
{
JScrollPane scrollPane = (JScrollPane)component;
JViewport viewport = scrollPane.getViewport();
viewport.setOpaque( false );
Component c = viewport.getView();
if (c instanceof JComponent)
{
((JComponent)c).setOpaque( false );
}
}
}
/*
* Add custom painting
*/
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
// Invoke the painter for the background
if (painter != null)
{
Dimension d = getSize();
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(painter);
g2.fill( new Rectangle(0, 0, d.width, d.height) );
}
// Draw the image
if (image == null ) return;
switch (style)
{
case SCALED :
drawScaled(g);
break;
case TILED :
drawTiled(g);
break;
case ACTUAL :
drawActual(g);
break;
default:
drawScaled(g);
}
}
/*
* Custom painting code for drawing a SCALED image as the background
*/
private void drawScaled(Graphics g)
{
Dimension d = getSize();
g.drawImage(image, 0, 0, d.width, d.height, null);
}
/*
* Custom painting code for drawing TILED images as the background
*/
private void drawTiled(Graphics g)
{
Dimension d = getSize();
int width = image.getWidth( null );
int height = image.getHeight( null );
for (int x = 0; x < d.width; x += width)
{
for (int y = 0; y < d.height; y += height)
{
g.drawImage( image, x, y, null, null );
}
}
}
/*
* Custom painting code for drawing the ACTUAL image as the background.
* The image is positioned in the panel based on the horizontal and
* vertical alignments specified.
*/
private void drawActual(Graphics g)
{
Dimension d = getSize();
Insets insets = getInsets();
int width = d.width - insets.left - insets.right;
int height = d.height - insets.top - insets.left;
float x = (width - image.getWidth(null)) * alignmentX;
float y = (height - image.getHeight(null)) * alignmentY;
g.drawImage(image, (int)x + insets.left, (int)y + insets.top, this);
}
}

how to fix paint component not invoking paintComponent?

I looked through some of the other questions similar to mine, but none of them seemed of help when i tried their fixes, How would i go about fixing this as its the only thing stopping my programme from printing rectangles which is vital.
Below is the code for the entire programme as im not sure in where the problem lies, only what the problem is. The stuff that is commented I'm currently not using, but I'm keeping it in there just in case I find a use for it later on. Many thanks
Currently, my programme does not draw any sort of rectangle as it should, and its because repaint doesn't invoke the paintcomponent. My question is basically asking as to why nothing is printed when I start it.
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* Write a description of class Game here.
*
* #author Adam Steele
* #version 1.0.0
*/
public class Game extends JPanel implements MouseListener
{
// logger
private static final Logger LOGGER = Logger.getLogger(Game.class.getName());
// variables
private boolean isGolden; // Class wide boolean for whether rectangle is golden
private int score; // Stores the score
private int noOfAttempts; // stores number of rectangles that are created
private int goldTimer; // attempts before fail & new rectangle is created
// graphics
private Rectangle box;
private JFrame frame;
private JPanel panel;
// testing
private ArrayList<Rect> rects = new ArrayList<Rect>();
/**
* Constructor for objects of class Game
*/
public Game()
{
setup();
}
public void setup()
{
LOGGER.info("setup has been called");
// initalise frame
frame = new JFrame();
final int FRAME_WIDTH = 800;
final int FRAME_HEIGHT = 600;
// set frame attributes
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setTitle("Gold Picker");
// initalise panel
panel = new JPanel();
panel.addMouseListener(this);
// add things to panel - ie. score, time limit, etc.
// panel.add();
JLabel amLabel = new JLabel("I am a GUI label.");
panel.add(amLabel);
JButton button = new JButton("Button");
button.setBackground(Color.YELLOW);
button.setForeground(Color.GREEN);
panel.add(button);
// add panel to frame
frame.add(panel);
// Make frame visible
showFrame();
LOGGER.info("Game window has been created");
// (re)set variables
score = 0;
noOfAttempts = 0;
goldTimer = 30;
// this is to check variable initalisation
LOGGER.info("Variables have been initalised at: score = " + score + ", noOfAttempts = " + noOfAttempts + ", goldTimer = " + goldTimer);
// start game
decideGolden();
}
public void showFrame()
{
frame.setVisible(true);
}
/**
* A method that decides if the rectangle will be golden based on a random chance
*/
public void decideGolden()
{
double goldProportion = Math.random() * 1;
double goldChance = Math.random() * 1;
//System.out.println(goldChance + " " + goldProportion);
if(goldChance <= goldProportion) {
isGolden = true;
} else {
isGolden = false;
}
LOGGER.info("isGolden has been set to " + isGolden);
timedRect();
}
/**
* Method for generating rectangled on timed intervals
*/
public void timedRect()
{
/*
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run()
{
}
}, 20000, 20000 );
/*
if(isGolden) {
repaint();
} else {
createNormalRectangle();
}
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
}
},
20000); */
LOGGER.info("timedRect has been called");
for(int i = 0; i < goldTimer; i++) {
try {
LOGGER.info("try has been reached");
//repaint();
drawRectangle();
Thread.sleep(20000); // wait 20 seconds..
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
LOGGER.info("paintComponent has been called with " + g);
/*
int x = 400;
int y = 300;
// Using Math.random() or .nextInt() we could make random gold rects
int width = 100;
int height = 100;
LOGGER.info("x = " + x + ", y = " + y + ", width = " + width + ", height = " + height);
if(isGolden) {
//g.drawRect(x, y, width, height);
g.setColor(Color.BLACK);
g.fillRect(x, y, 100, 100); // g.fillRect(x, y, size, size);
} else {
//g.drawRect(x, y, width, height);
g.setColor(Color.BLACK);
g.fillRect(x, y, 100, 100);
}
*/
Graphics2D g2d = (Graphics2D) g;
for(Rect rectangle : rects) { // for each Rect object in rect ArrayList ..
rectangle.paint(g2d);
}
}
/**
*
*/
public void drawRectangle()
{
int x = (int) (Math.random() * getWidth());
int y = (int) (Math.random() * getHeight());
int width = (int) (Math.random() * (getWidth() / 4));
int height = (int) (Math.random() * (getHeight() / 4));
// LOGGER.info("x = " + x + ", y = " + y + ", width = " + width + ", height = " + height);
if (x + width > getWidth()) {
x = getWidth() - width;
}
if (y + height > getHeight()) {
y = getHeight() - height;
}
Color color = new Color(
(int) (Math.random() * 255),
(int) (Math.random() * 255),
(int) (Math.random() * 255));
rects.add(new Rect(x, y, width, height, color));
repaint();
// LOGGER.info("repaint has been called, check if paintComponent has been called..");
}
/**
* these methods are needed to override the MouseListener
* ..and hence needed to implement the MouseListener
* ..which will probably only be used for testing
* ..actionlistener maybe more appropriate
*/
#Override
public void mouseClicked(MouseEvent e) {
LOGGER.info("Mouse has been clicked");
drawRectangle();
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
}
Rect class in case its needed
Rect class
import java.awt.*;
/**
* Deaals with misc properties of rect
* Inherits the Rectangle class from java.awt.Rectangle
public class Rect extends Rectangle
{
private Color color;
/**
* Constructor for objects of class Rect
*/
public Rect(int x, int y, int width, int height, Color color)
{
super(x, y, width, height);
this.color = color;
}
public void paint(Graphics2D g2d)
{
g2d.setColor(color);
g2d.fill(this);
}
}
Basically, you never actually add Game to anything which can display it, therefore it will never be painted.
Before a component can be painted, it must be added to a container which is realised on the screen.
One of the issues you're having is your Game class is taking on to much responsibility, it should be focused on displaying and managing the game state, not also creating the basic UI.
Another issue you're going to have is your timedRect method will block the EDT, preventing anything from getting painted anyway
This is a "basic" example (I've not tested it because I don't have your Rect class), but conceptually it should get you closer to your goal.
Realistically, I'd have a separate "main" class which started the app, setup the initial state, created the UI and gets the ball rolling.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.w3c.dom.css.Rect;
public class Game extends JPanel implements MouseListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
LOGGER.info("setup has been called");
// initalise frame
JFrame frame = new JFrame();
final int FRAME_WIDTH = 800;
final int FRAME_HEIGHT = 600;
// set frame attributes
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setTitle("Gold Picker");
// initalise panel
JPanel panel = new Game();
// add things to panel - ie. score, time limit, etc.
// panel.add();
JLabel amLabel = new JLabel("I am a GUI label.");
panel.add(amLabel);
JButton button = new JButton("Button");
button.setBackground(Color.YELLOW);
button.setForeground(Color.GREEN);
panel.add(button);
// add panel to frame
frame.add(panel);
frame.setVisible(true);
LOGGER.info("Game window has been created");
}
});
}
// logger
private static final Logger LOGGER = Logger.getLogger(Game.class.getName());
// variables
private boolean isGolden; // Class wide boolean for whether rectangle is golden
private int score; // Stores the score
private int noOfAttempts; // stores number of rectangles that are created
private int goldTimer; // attempts before fail & new rectangle is created
private int gameLoops = 0;
// graphics
private Rectangle box;
// testing
private ArrayList<Rect> rects = new ArrayList<Rect>();
/**
* Constructor for objects of class Game
*/
public Game() {
addMouseListener(this);
// (re)set variables
score = 0;
noOfAttempts = 0;
goldTimer = 30;
// this is to check variable initalisation
LOGGER.info("Variables have been initalised at: score = " + score + ", noOfAttempts = " + noOfAttempts + ", goldTimer = " + goldTimer);
decideGolden();
}
/**
* A method that decides if the rectangle will be golden based on a random
* chance
*/
public void decideGolden() {
double goldProportion = Math.random() * 1;
double goldChance = Math.random() * 1;
//System.out.println(goldChance + " " + goldProportion);
if (goldChance <= goldProportion) {
isGolden = true;
} else {
isGolden = false;
}
LOGGER.info("isGolden has been set to " + isGolden);
timedRect();
}
/**
* Method for generating rectangled on timed intervals
*/
public void timedRect() {
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (gameLoops < goldTimer) {
LOGGER.info("try has been reached");
//repaint();
drawRectangle();
gameLoops++;
} else {
((Timer) (e.getSource())).stop();
}
}
});
timer.start();
LOGGER.info("timedRect has been called");
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
LOGGER.info("paintComponent has been called with " + g);
Graphics2D g2d = (Graphics2D) g;
for (Rect rectangle : rects) { // for each Rect object in rect ArrayList ..
rectangle.paint(g2d);
}
}
/**
*
*/
public void drawRectangle() {
int x = (int) (Math.random() * getWidth());
int y = (int) (Math.random() * getHeight());
int width = (int) (Math.random() * (getWidth() / 4));
int height = (int) (Math.random() * (getHeight() / 4));
// LOGGER.info("x = " + x + ", y = " + y + ", width = " + width + ", height = " + height);
if (x + width > getWidth()) {
x = getWidth() - width;
}
if (y + height > getHeight()) {
y = getHeight() - height;
}
Color color = new Color(
(int) (Math.random() * 255),
(int) (Math.random() * 255),
(int) (Math.random() * 255));
rects.add(new Rect(x, y, width, height, color));
repaint();
LOGGER.info("repaint has been called, check if paintComponent has been called..");
}
/**
* these methods are needed to override the MouseListener ..and hence needed
* to implement the MouseListener ..which will probably only be used for
* testing ..actionlistener maybe more appropriate
*/
#Override
public void mouseClicked(MouseEvent e) {
LOGGER.info("Mouse has been clicked");
drawRectangle();
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public class Rect extends Rectangle {
private Color color;
/**
* Constructor for objects of class Rect
*/
public Rect(int x, int y, int width, int height, Color color) {
super(x, y, width, height);
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fill(this);
}
}
}
Why are you extending JPanel when you have an instance of JPanel? When you extend a class, you inherit all of its functionality.
Instead of using your instance "panel.add" whatever, get rid of your JPanel instance and use this.add.
Which leads to my next statement:
Your class should extend JComponent, you're not overriding paintcomponent because JPanel doesn't have a paintcomponent() method to override.
Read up on how to properly implement JComponent and JFrame and how they go together. Normally you add all your drawings to the JComponent, then you say frame.add(component), where component is of type JComponent.
Try reimplementing your code in this fashion and seperate your classes, now you don't have to follow this exact format but from what you've posted, I think you're lost.
Implement a class that extends JFrame, this will be your frame.
Implement a class that "knows" how to draw your rectangle shapes. You must put it in a method called draw(Graphics2D g2){}.
Implement a class that extends JComponent, this class may have an arraylist of your "rectangle" objects.
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// initialize your rectangle objects here
for(Rectangles rects: whateveryourarraylistvariable) {
rects.draw(g2);
}
}
good luck

Actions inside of another action like Netbeans

I need to find the way to show several actions inside of another action like Netbeans does with Run Main Project icon.
You can see there is a default action Run Main Project and if you click in the little arrow next to the green play icon, you can select a specific action like Run .
I was checking the code of Netbeans but I can't find the code to make this in my application.
Ah, (one of) the holy grails of UI components, a split button. Over a number of years I've tried to find one which would perform well under multiple look and feels and failed dismally.
Many used multiple buttons or just resorted to using a JComboBox
Like many things, I stumbled across one which was pretty well done, but which I had to modify to suit my needs, unfortunately, I don't remember the original version or author, sorry. (If you believe this code is based on yours, please leave a comment with a link to the original and I will evaluate and provide appropriate credit)
Basically, if you click the button, it will run the "default" action (Bananas) otherwise you can select one of the sub elements and it will execute it
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private JFrame popupMenu;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private PopupWindowEventHandler popupWindowEventHandler;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action action : actions) {
addAction(action);
}
}
public SplitButton(String text, Icon icon, Action... actions) {
this((Action) null, actions);
setText(text);
setIcon(icon);
}
public SplitButton(String text, Action... actions) {
this((Action) null, actions);
setText(text);
}
public JSplitButton(Icon icon, Action... actions) {
this((Action) null, actions);
setIcon(icon);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
JSplitButton btn = new JSplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, Action... actions) {
JSplitButton btn = new JSplitButton(icon, actions);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected AbstractButton getButtonFor(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = null;
for (Component comp : parent.getComponents()) {
if (comp instanceof AbstractButton) {
Action childAction = ((AbstractButton) comp).getAction();
if (action.equals(childAction)) {
btn = (AbstractButton) comp;
break;
}
}
}
return btn;
}
/**
* Returns the index of the specified action within the popup window or -1
* of it does not exist
*
* #param action
* #return
*/
public int indexOfAction(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = getButtonFor(action);
return btn == null ? -1 : parent.getComponentZOrder(btn);
}
/**
* Adds the specified action to the popup menu...
*
* This simply calls getPopupWindow().add(action)
*
* #param action Add
*/
public void addAction(Action action) {
addActionAt(action, -1);
}
protected int getOptionsCount() {
return ((JFrame) getPopupWindow()).getContentPane().getComponentCount();
}
protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}
protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}
/**
* Creates a new JMenuItem from the supplied Action. This is used to
* provided the ability for subclasses to either change the type of menu
* item used by the button or add additional functionality (like listeners)
* should they be required
*
* #param action
* #return
*/
protected JMenuItem createMenuItem(Action action) {
return new JMenuItem(action);
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the window that acts as the buttons popup window
*
* #return
*/
public Window getPopupWindow() {
if (popupMenu == null) {
popupMenu = new JFrame();
popupMenu.setFocusableWindowState(false);
popupMenu.setUndecorated(true);
popupMenu.setContentPane(createPopupWindowContentPane());
popupMenu.setAlwaysOnTop(true);
DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("focusOwner".equalsIgnoreCase(name)
|| "permanentFocusOwner".equalsIgnoreCase(name)
|| "focusedWindow".equalsIgnoreCase(name)
|| "activeWindow".equalsIgnoreCase(name)) {
Window focusedWindow = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (!popupMenu.equals(focusedWindow)) {
closePopupWinodw();
}
}
}
});
}
return popupMenu;
}
protected Container createPopupWindowContentPane() {
return new DefaultMenuPane();
}
protected void closePopupWinodw() {
getPopupWindow().setVisible(false);
if (popupWindowEventHandler != null) {
Toolkit.getDefaultToolkit().removeAWTEventListener(popupWindowEventHandler);
}
}
protected void showPopupWindow() {
Window popup = getPopupWindow();
popup.pack();
Point pos = getLocationOnScreen();
popup.setLocation(pos.x + (getWidth() - popup.getWidth()), pos.y + getHeight());
popup.setVisible(true);
if (popupWindowEventHandler == null) {
popupWindowEventHandler = new PopupWindowEventHandler();
}
Toolkit.getDefaultToolkit().addAWTEventListener(popupWindowEventHandler, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(popupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupWindow();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class PopupWindowEventHandler implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
if (popupMenu.isVisible()) {
switch (event.getID()) {
case MouseEvent.MOUSE_RELEASED:
Object source = event.getSource();
if (source instanceof Component) {
Window win = SwingUtilities.getWindowAncestor((Component) source);
if (!popupMenu.equals(win)) {
closePopupWinodw();
}
}
break;
}
}
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupWinodw();
}
}
protected class DefaultMenuPane extends JPanel {
public DefaultMenuPane() {
setBorder(UIManager.getBorder("PopupMenu.border"));
setBackground(UIManager.getColor("PopupMenu.background"));
setLayout(new GridLayout(0, 1));
}
}
}
It would be configured something like ...
SplitButton btn = new SplitButton();
btn.setAction(new FruitAction("Banana", new BananaIcon(32, 32)));
btn.addAction(new FruitAction("Apple", new AppleIcon(32, 32)));
btn.addAction(new FruitAction("Black Berry", new BlackBerriesIcon(32, 32)));
btn.addAction(new FruitAction("Grapes", new GrapesIcon(32, 32)));
btn.addAction(new FruitAction("Peach", new PeachIcon(32, 32)));
btn.addAction(new FruitAction("Strewberry", new StrewberriesIcon(32, 32)));
And, for reference, the fruit action looks like...
public class FruitAction extends AbstractAction {
public FruitAction(String text, Icon icon) {
putValue(NAME, text);
putValue(SMALL_ICON, icon);
putValue(SHORT_DESCRIPTION, text);
}
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "I am " + getValue(NAME), "Fruit", JOptionPane.INFORMATION_MESSAGE);
}
}
This is use a custom vector based icon library, so obviously, I won't be including that, but it gives you an idea of how to configure it
I too have been looking for a decent JSplitButton for a while, but everything I found as a standalone component was rather disappointing.
MadProgrammer's answer looked very promising, but it did not work all that well for me. I'm not 100% sure what it might be caused by, whether it's by design or because of the L&F I'm using but the provided component had focus issues. Specifically, the items of the popup did not show any hover indicators, indicating they weren't receiving focus. Also upon clicking an item in the popup the popup did not close itself.
Anyway, I rewrote parts of the component and made it use a JPopupMenu instead of a custom JFrame to avoid handling focus myself. The component set's the popup menu as its popup menu using the JComponent.setComponentPopupMenu() and then just invoking the popup menu upon clicking the dropdown arrow. This also makes it that it's possible to right-click the button to show the popup directly.
The popup has focus and behaves like a regular JPopupMenu, supporting adding stuff like separators and whatnot.
Note: The L&F used in the image is flatlaf
The button's default action can be set like a normal JButton using addActionListener() or using actions by calling setAction().
SplitButton btn = new SplitButton("Click me");
btn.addActionListener((e) -> {
System.out.println("Button clicked");
});
SplitButton btn2 = new SplitButton(new AbstractAction("Click me") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
The popup menu can be created separately and then assigned to the button using setPopupMenu() or you can add individual items as actions to the menu without the need of creating it yourself using addAction and addActionAt.
JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem("A popup option"));
popup.add(new JMenuItem("JMenuItem with icon", Icons.deleteBin));
popup.addSeparator();
btn.setPopupMenu(popup);
btn.addAction(new AbstractAction("Or don't", Icons.alert) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Or don't clicked");
}
});
btn.addAction(new AbstractAction("Click me in a different way") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Click me in a different way clicked");
}
});
The popup menu can be retrieved simply by using getPopupMenu().
The full code
Again, original by MadProgrammer and whoever he got it from :D
/**
* A swing split button implementation.
* A JButton that has an additional section with an arrow icon on the right that when clicked
* shows a JPopupMenu that is positioned flush with the button.
*
* The implementation sets the buttons pop-up menu using setComponentPopupMenu()
* meaning that in addition to clicking the drop-down arrow, user can also right click
* the button to open the pop-up menu.
*
* Author: DUDSS - 21.02.2020
* I modified the button to use a JPopupMenu instead of a custom JFrame to avoid hacky
* focus workarounds and fix focus issues.
*
* Credit:
* Modified version of a split button by MadProgrammer.
* https://stackoverflow.com/questions/36352707/actions-inside-of-another-action-like-netbeans
* It's original author seems to be unknown.
*
*/
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private JPopupMenu jpopupMenu;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction) {
this();
setAction(defaultAction);
}
public SplitButton(Action defaultAction, JPopupMenu popup) {
this();
setAction(defaultAction);
setPopupMenu(popup);
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action a : actions) {
addAction(a);
}
}
public SplitButton(String text) {
this();
setText(text);
}
public SplitButton(String text, Icon icon) {
this();
setText(text);
setIcon(icon);
}
public SplitButton(String text, JPopupMenu popup) {
this();
setText(text);
setPopupMenu(popup);
}
public SplitButton(String text, Icon icon, JPopupMenu popup) {
this();
setText(text);
setIcon(icon);
setPopupMenu(popup);
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
SplitButton btn = new SplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, JPopupMenu popupMenu) {
SplitButton btn = new SplitButton(text, icon);
btn.setPopupMenu(popupMenu);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
#Override
public void addActionListener(ActionListener l) {
if (l != null) {
setAlwaysDropDown(false);
}
super.addActionListener(l);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
public void addActionAt(Action a, int index) {
getPopupMenu().insert(a, index);
}
public void addAction(Action a) {
getPopupMenu().add(a);
}
public void setPopupMenu(JPopupMenu popup) {
jpopupMenu = popup;
this.setComponentPopupMenu(popup);
}
/**
* Returns the buttons popup menu.
*
* #return
*/
public JPopupMenu getPopupMenu() {
if (jpopupMenu == null) {
jpopupMenu = new JPopupMenu();
}
return jpopupMenu;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected int getOptionsCount() {
return getPopupMenu().getComponentCount();
}
/*protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}*/
/*protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}*/
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
protected void closePopupMenu() {
getPopupMenu().setVisible(false);
}
protected void showPopupMenu() {
if (getOptionsCount() > 0) {
JPopupMenu menu = getPopupMenu();
menu.setVisible(true); //Necessary to calculate pop-up menu width the first time it's displayed.
menu.show(this, (getWidth() - menu.getWidth()), getHeight());
}
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(jpopupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupMenu();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupMenu();
}
}
}

java awt Canvas getInstance undefined (newbie)

Trying the exercise in lesson2 of the Udacity course. Despite importing the classes (I'm at java.awt.* now, but I also tried java.awt.Color and java.awt.Canvas separately (also need Shape))..
package com.jul.udacity.lesson2;
public class TestRectangle {
public static void main(String[] args) {
// TODO Auto-generated method stub
Rectangle rect1 = new Rectangle(100.0, 100.0, 200.0, 100.0);
rect1.draw();
}
}
And the class is copied from there and java.awt import added. Any help will be great. Thanks!
package com.jul.udacity.lesson2;
//HIDE
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
//import java.awt.Color;
//import java.awt.Shape;
//import java.awt.Canvas;
import java.awt.*;
public class Rectangle implements Shape
{
private Color color = Color.BLACK;
private boolean filled = false;
private double x;
private double y;
private double width;
private double height;
/**
Constructs an empty rectangle.
*/
public Rectangle()
{
x = 0;
y = 0;
width = 0;
height = 0;
}
/**
Constructs a rectangle.
#param x the leftmost x-coordinate
#param y the topmost y-coordinate
#param width the width
#param height the height
*/
public Rectangle(double x, double y, double width, double height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
Gets the leftmost x-position of this rectangle.
#return the leftmost x-position
*/
public int getX()
{
return (int) Math.round(x);
}
/**
Gets the topmost y-position of this rectangle.
#return the topmost y-position
*/
public int getY()
{
return (int) Math.round(y);
}
/**
Gets the width of this rectangle.
#return the width
*/
public int getWidth()
{
return (int) Math.round(width);
}
/**
Gets the height of this rectangle.
#return the height
*/
public int getHeight()
{
return (int) Math.round(height);
}
/**
Moves this rectangle by a given amount.
#param dx the amount by which to move in x-direction
#param dy the amount by which to move in y-direction
*/
public void translate(double dx, double dy)
{
x += dx;
y += dy;
Canvas.getInstance().repaint();
}
/**
Resizes this rectangle both horizontally and vertically.
#param dw the amount by which to resize the width on each side
#param dw the amount by which to resize the height on each side
*/
public void grow(double dw, double dh)
{
width += 2 * dw;
height += 2 * dh;
x -= dw;
y -= dh;
Canvas.getInstance().repaint();
}
/**
Sets the color of this rectangle.
#param newColor the new color
*/
public void setColor(Color newColor)
{
color = newColor;
Canvas.getInstance().repaint();
}
/**
Draws this rectangle.
*/
public void draw()
{
filled = false;
Canvas.getInstance().show(this);
}
/**
Fills this rectangle.
*/
public void fill()
{
filled = true;
Canvas.getInstance().show(this);
}
public String toString()
{
return "Rectangle[x=" + getX() + ",y=" + getY() + ",width=" + getWidth() + ",height=" + getHeight() + "]";
}
public void paintShape(Graphics2D g2)
{
Rectangle2D.Double rect = new Rectangle2D.Double(getX(), getY(),
getWidth(), getHeight());
g2.setColor(new java.awt.Color((int) color.getRed(), (int) color.getGreen(), (int) color.getBlue()));
if (filled)
{
g2.fill(rect);
}
else
{
g2.draw(rect);
}
}
}
You might want to check the lesson directions carefully. java.awt.Canvas has no getInstance() method. You just use new to make a Canvas. So you either didn't read carefully and are using the wrong Canvas, or there's something else going on.
Also the show() methods are deprecated, so I'm leaning towards you are supposed to be using a different Canvas class.
Also, Swing is not thread safe. Read up on how to use Swing objects
markspace is correct: Canvas here is not the class from java.awt - the Udacity instructors are using their own class called Canvas. I suggest grabbing the lesson files and using those. Here's the Canvas class from that Intro To Java Course:
import java.awt.image.BufferedImage;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.RescaleOp;
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Canvas
{
private static Canvas canvas = new Canvas();
private ArrayList<Shape> shapes = new ArrayList<Shape>();
private BufferedImage background;
private JFrame frame;
private CanvasComponent component;
private static final int MIN_SIZE = 100;
private static final int MARGIN = 10;
private static final int LOCATION_OFFSET = 120;
class CanvasComponent extends JComponent
{
public void paintComponent(Graphics g)
{
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(java.awt.Color.BLACK);
if (background != null)
{
g.drawImage(background, 0, 0, null);
}
for (Shape s : new ArrayList<Shape>(shapes))
{
Graphics2D g2 = (Graphics2D) g.create();
s.paintShape(g2);
g2.dispose();
}
}
public Dimension getPreferredSize()
{
int maxx = MIN_SIZE;
int maxy = MIN_SIZE;
if (background != null)
{
maxx = Math.max(maxx, background.getWidth());
maxy = Math.max(maxx, background.getHeight());
}
for (Shape s : shapes)
{
maxx = (int) Math.max(maxx, s.getX() + s.getWidth());
maxy = (int) Math.max(maxy, s.getY() + s.getHeight());
}
return new Dimension(maxx + MARGIN, maxy + MARGIN);
}
}
private Canvas()
{
component = new CanvasComponent();
if (System.getProperty("com.horstmann.codecheck") == null)
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(component);
frame.pack();
frame.setLocation(LOCATION_OFFSET, LOCATION_OFFSET);
frame.setVisible(true);
}
else
{
final String SAVEFILE ="canvas.png";
final Thread currentThread = Thread.currentThread();
Thread watcherThread = new Thread()
{
public void run()
{
try
{
final int DELAY = 10;
while (currentThread.getState() != Thread.State.TERMINATED)
{
Thread.sleep(DELAY);
}
saveToDisk(SAVEFILE);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
};
watcherThread.start();
}
}
public static Canvas getInstance()
{
return canvas;
}
public void show(Shape s)
{
if (!shapes.contains(s))
{
shapes.add(s);
}
repaint();
}
public void repaint()
{
if (frame == null) return;
Dimension dim = component.getPreferredSize();
if (dim.getWidth() > component.getWidth()
|| dim.getHeight() > component.getHeight())
{
frame.pack();
}
else
{
frame.repaint();
}
}
/**
* Pauses so that the user can see the picture before it is transformed.
*/
public void pause()
{
if (frame == null) return;
JOptionPane.showMessageDialog(frame, "Click Ok to continue");
}
/**
* Takes a snapshot of the screen, fades it, and sets it as the background.
*/
public static void snapshot()
{
Dimension dim = getInstance().component.getPreferredSize();
java.awt.Rectangle rect = new java.awt.Rectangle(0, 0, dim.width, dim.height);
BufferedImage image = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, rect.width, rect.height);
g.setColor(java.awt.Color.BLACK);
getInstance().component.paintComponent(g);
float factor = 0.8f;
float base = 255f * (1f - factor);
RescaleOp op = new RescaleOp(factor, base, null);
BufferedImage filteredImage
= new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
op.filter(image, filteredImage);
getInstance().background = filteredImage;
getInstance().component.repaint();
}
public void saveToDisk(String fileName)
{
Dimension dim = component.getPreferredSize();
java.awt.Rectangle rect = new java.awt.Rectangle(0, 0, dim.width, dim.height);
BufferedImage image = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setColor(java.awt.Color.WHITE);
g.fill(rect);
g.setColor(java.awt.Color.BLACK);
component.paintComponent(g);
String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
try
{
ImageIO.write(image, extension, new File(fileName));
}
catch(IOException e)
{
System.err.println("Was unable to save the image to " + fileName);
}
g.dispose();
}
}

Categories

Resources