JTable with autoscroll, problems when updating the cells - java

I have a table (JTable) of values which is updated in real time about every 10 seconds (last 2 columns out of 4 updated, for each row). I want this table to continuous scroll up so I used the example from:
http://wiki.byte-welt.net/wiki/MarqueePanelV
The scrolling wraps around, first line is shown after last line and works fine except one thing:
When I do the update of the DefaultTableModel (I'm using setValueAt() function), the values are printed at their original position (like the table is not scrolled). Only the portion with the 2 columns is refreshed, created some fracture in the view. The view is corrected at the next scroll (in 100 milliseconds) but is still visible on the screen. More, since I'm stopping the scroll when mouse is over the table, the update will create a visible fracture in the table view.
Any idea on how can I fix this problem?
Here is a code example showing the problem:
package scrollingtable;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.DefaultTableModel;
class MarqueePanelV extends JPanel
implements ActionListener, AncestorListener, WindowListener {
private boolean paintChildren;
private boolean scrollingPaused;
private int scrollOffset;
private int wrapOffset;
private int preferredHeight = -1;
private int scrollAmount;
private int scrollFrequency;
private boolean wrap = false;
private int wrapAmount = 0;
private boolean scrollWhenFocused = true;
private Timer timer = new Timer(100, this);
/**
* Convenience constructor that sets both the scroll frequency and
* scroll amount to a value of 5.
*/
public MarqueePanelV() {
this(20, 1);
}
/**
*
* #param scrollFrequency
* #param scrollAmount
*/
#SuppressWarnings("LeakingThisInConstructor")
public MarqueePanelV(int scrollFrequency, int scrollAmount) {
setScrollFrequency(scrollFrequency);
setScrollAmount(scrollAmount);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
addAncestorListener(this);
}
/*
* Translate the location of the children before they are painted so it
* appears they are scrolling bottom to top
*/
#Override
public void paintChildren(Graphics g) {
// Need this so we don't see a flicker of the text before scrolling
if (!paintChildren) {
return;
}
int x = super.getPreferredSize().height;
// Normal painting as the components scroll bottom to top
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -scrollOffset);
super.paintChildren(g);
g2d.translate(0, scrollOffset);
// Repaint the start of the components on the bottom edge of the panel once
// all the components are completely visible on the panel.
// (Its like the components are in two places at the same time)
if (isWrap()) {
//wrapOffset = scrollOffset - super.getPreferredSize().height - wrapAmount;
wrapOffset = scrollOffset - x - wrapAmount;
g2d.translate(0, -wrapOffset);
super.paintChildren(g);
g2d.translate(0, wrapOffset);
}
}
/*
* The default preferred size will be half the size of the components added to
* the panel. This will allow room for components to be scrolled on and off
* the panel.
*
* The default height can be overriden by using the setPreferredHeight() method.
*/
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (preferredHeight == -1) ? d.height / 2 : preferredHeight;
return d;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public int getPreferredHeight() {
return preferredHeight;
}
/**
* Specify the preferred height on the panel. A value of -1 will cause the
* default preferred with size calculation to be used.
*
* #param preferredHeight preferred height of the panel in pixels
*/
public void setPreferredHeight(int preferredHeight) {
this.preferredHeight = preferredHeight;
revalidate();
}
/**
* Get the scroll amount.
*
* #return the scroll amount in pixels
*/
public int getScrollAmount() {
return scrollAmount;
}
/**
* Specify the scroll amount. The number of pixels to scroll every time
* scrolling is done.
*
* #param scrollAmount scroll amount in pixels
*/
public void setScrollAmount(int scrollAmount) {
this.scrollAmount = scrollAmount;
}
/**
* Get the scroll frequency.
*
* #return the scroll frequency
*/
public int getScrollFrequency() {
return scrollFrequency;
}
/**
* Specify the scroll frequency. That is the number of times scrolling
* should be performed every second.
*
* #param scrollFrequency scroll frequency
*/
public void setScrollFrequency(int scrollFrequency) {
this.scrollFrequency = scrollFrequency;
int delay = 1000 / scrollFrequency;
timer.setInitialDelay(delay);
timer.setDelay(delay);
}
/**
* Get the scroll only when visible property.
*
* #return the scroll only when visible value
*/
public boolean isScrollWhenFocused() {
return scrollWhenFocused;
}
/**
* Specify the scrolling property for unfocused windows.
*
* #param scrollWhenVisible when true scrolling pauses when the window
* loses focus. Scrolling will continue when
* the window regains focus. When false
* scrolling is continuous unless the window
* is iconified.
*/
public void setScrollWhenFocused(boolean scrollWhenFocused) {
this.scrollWhenFocused = scrollWhenFocused;
}
/**
* Get the wrap property.
*
* #return the wrap value
*/
public boolean isWrap() {
return wrap;
}
/**
* Specify the wrapping property. Normal scrolling is such that all the text
* will scroll from bottom to top. When the last part of the text scrolls off
* the bottom edge scrolling will start again from the bottom edge. Therefore
* there is a time when the component is blank as nothing is displayed.
* Wrapping implies that as the end of the text scrolls off the top edge
* the beginning of the text will scroll in from the bottom edge. So the end
* and the start of the text is displayed at the same time.
*
* #param wrap when true the start of the text will scroll in from the bottom
* edge while the end of the text is still scrolling off the top
* edge. Otherwise the panel must be clear of text before it
* will begin again from the bottom edge.
*/
public void setWrap(boolean wrap) {
this.wrap = wrap;
}
/**
* Get the wrap amount.
*
* #return the wrap amount value
*/
public int getWrapAmount() {
return wrapAmount;
}
/**
* Specify the wrapping amount. This specifies the space between the end of the
* text on the top edge and the start of the text from the bottom edge when
* wrapping is turned on.
*
* #param wrapAmount the amount in pixels
*/
public void setWrapAmount(int wrapAmount) {
this.wrapAmount = wrapAmount;
}
/**
* Start scrolling the components on the panel. Components will start
* scrolling from the bottom edge towards the top edge.
*/
public void startScrolling() {
paintChildren = true;
scrollOffset = -getSize().height;
timer.start();
}
/**
* Stop scrolling the components on the panel. The conponents will be
* cleared from the view of the panel
*/
public void stopScrolling() {
timer.stop();
paintChildren = false;
repaint();
}
/**
* The components will stop scrolling but will remain visible
*/
public void pauseScrolling() {
if (timer.isRunning()) {
timer.stop();
scrollingPaused = true;
}
}
/**
* The components will resume scrolling from where scrolling was stopped.
*/
public void resumeScrolling() {
if (scrollingPaused) {
timer.restart();
scrollingPaused = false;
}
}
/**
* Adjust the offset of the components on the panel so it appears that
* they are scrolling from bottom to top.
*/
#Override
public void actionPerformed(ActionEvent e) {
scrollOffset += scrollAmount;
int height = super.getPreferredSize().height;
if (scrollOffset > height) {
scrollOffset = isWrap() ? wrapOffset + scrollAmount : -getSize().height;
}
//System.out.println("scroll offset: " + scrollOffset);
repaint();
}
/**
* Get notified when the panel is added to a Window so we can use a
* WindowListener to automatically start the scrolling of the components.
*/
#Override
public void ancestorAdded(AncestorEvent event) {
SwingUtilities.windowForComponent(this).addWindowListener(this);
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
// Implement WindowListener
#Override
public void windowOpened(WindowEvent e) {
startScrolling();
}
#Override
public void windowClosing(WindowEvent e) {
stopScrolling();
}
#Override
public void windowClosed(WindowEvent e) {
stopScrolling();
}
#Override
public void windowIconified(WindowEvent e) {
pauseScrolling(); }
#Override
public void windowDeiconified(WindowEvent e) {
resumeScrolling(); }
#Override
public void windowActivated(WindowEvent e) {
if (isScrollWhenFocused()) {
resumeScrolling();
}
}
#Override
public void windowDeactivated(WindowEvent e) {
if (isScrollWhenFocused()) {
pauseScrolling();
}
}
}
public class Main extends JDialog implements ActionListener {
private JTable table;
private DefaultTableModel model;
MarqueePanelV mpv = new MarqueePanelV();
private Timer timer2;
public static void main(String[] args) {
// TODO code application logic here
new Main();
}
Main() {
setSize(600, 400);
this.setLocation(300, 300);
table = new JTable();
model = new DefaultTableModel(20, 2);
table.setModel(model);
table.getColumnModel().getColumn(0).setPreferredWidth(280);
table.getColumnModel().getColumn(1).setPreferredWidth(280);
for(int i = 0; i < 20; i++) {
model.setValueAt(i, i, 0);
model.setValueAt(100 + i, i, 1);
}
mpv.add(table);
add(mpv);
mpv.setWrap(true);
mpv.setWrapAmount(0);
mpv.startScrolling();
table.addMouseListener(new MouseListener() {
// Implement MouseListener
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
mpv.pauseScrolling();
}
#Override
public void mouseExited(MouseEvent e) {
mpv.resumeScrolling();
}
});
setVisible(true);
timer2 = new Timer(2000, this);
timer2.setInitialDelay(1000);
timer2.setDelay(2000);
timer2.start();
}
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < 20; i++) {
model.setValueAt(100 + i, i, 1);
}
}
}

Problem was solved by calling mpv.repaint(); in the timer2 action:
package scrollingtable;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.DefaultTableModel;
class MarqueePanelV extends JPanel
implements ActionListener, AncestorListener, WindowListener {
private boolean paintChildren;
private boolean scrollingPaused;
private int scrollOffset;
private int wrapOffset;
private int preferredHeight = -1;
private int scrollAmount;
private int scrollFrequency;
private boolean wrap = false;
private int wrapAmount = 0;
private boolean scrollWhenFocused = true;
private Timer timer = new Timer(100, this);
/**
* Convenience constructor that sets both the scroll frequency and
* scroll amount to a value of 5.
*/
public MarqueePanelV() {
this(20, 1);
}
/**
*
* #param scrollFrequency
* #param scrollAmount
*/
#SuppressWarnings("LeakingThisInConstructor")
public MarqueePanelV(int scrollFrequency, int scrollAmount) {
setScrollFrequency(scrollFrequency);
setScrollAmount(scrollAmount);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
addAncestorListener(this);
}
/*
* Translate the location of the children before they are painted so it
* appears they are scrolling bottom to top
*/
#Override
public void paintChildren(Graphics g) {
// Need this so we don't see a flicker of the text before scrolling
if (!paintChildren) {
return;
}
int x = super.getPreferredSize().height;
// Normal painting as the components scroll bottom to top
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -scrollOffset);
super.paintChildren(g);
g2d.translate(0, scrollOffset);
// Repaint the start of the components on the bottom edge of the panel once
// all the components are completely visible on the panel.
// (Its like the components are in two places at the same time)
if (isWrap()) {
//wrapOffset = scrollOffset - super.getPreferredSize().height - wrapAmount;
wrapOffset = scrollOffset - x - wrapAmount;
g2d.translate(0, -wrapOffset);
super.paintChildren(g);
g2d.translate(0, wrapOffset);
}
}
/*
* The default preferred size will be half the size of the components added to
* the panel. This will allow room for components to be scrolled on and off
* the panel.
*
* The default height can be overriden by using the setPreferredHeight() method.
*/
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (preferredHeight == -1) ? d.height / 2 : preferredHeight;
return d;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public int getPreferredHeight() {
return preferredHeight;
}
/**
* Specify the preferred height on the panel. A value of -1 will cause the
* default preferred with size calculation to be used.
*
* #param preferredHeight preferred height of the panel in pixels
*/
public void setPreferredHeight(int preferredHeight) {
this.preferredHeight = preferredHeight;
revalidate();
}
/**
* Get the scroll amount.
*
* #return the scroll amount in pixels
*/
public int getScrollAmount() {
return scrollAmount;
}
/**
* Specify the scroll amount. The number of pixels to scroll every time
* scrolling is done.
*
* #param scrollAmount scroll amount in pixels
*/
public void setScrollAmount(int scrollAmount) {
this.scrollAmount = scrollAmount;
}
/**
* Get the scroll frequency.
*
* #return the scroll frequency
*/
public int getScrollFrequency() {
return scrollFrequency;
}
/**
* Specify the scroll frequency. That is the number of times scrolling
* should be performed every second.
*
* #param scrollFrequency scroll frequency
*/
public void setScrollFrequency(int scrollFrequency) {
this.scrollFrequency = scrollFrequency;
int delay = 1000 / scrollFrequency;
timer.setInitialDelay(delay);
timer.setDelay(delay);
}
/**
* Get the scroll only when visible property.
*
* #return the scroll only when visible value
*/
public boolean isScrollWhenFocused() {
return scrollWhenFocused;
}
/**
* Specify the scrolling property for unfocused windows.
*
* #param scrollWhenVisible when true scrolling pauses when the window
* loses focus. Scrolling will continue when
* the window regains focus. When false
* scrolling is continuous unless the window
* is iconified.
*/
public void setScrollWhenFocused(boolean scrollWhenFocused) {
this.scrollWhenFocused = scrollWhenFocused;
}
/**
* Get the wrap property.
*
* #return the wrap value
*/
public boolean isWrap() {
return wrap;
}
/**
* Specify the wrapping property. Normal scrolling is such that all the text
* will scroll from bottom to top. When the last part of the text scrolls off
* the bottom edge scrolling will start again from the bottom edge. Therefore
* there is a time when the component is blank as nothing is displayed.
* Wrapping implies that as the end of the text scrolls off the top edge
* the beginning of the text will scroll in from the bottom edge. So the end
* and the start of the text is displayed at the same time.
*
* #param wrap when true the start of the text will scroll in from the bottom
* edge while the end of the text is still scrolling off the top
* edge. Otherwise the panel must be clear of text before it
* will begin again from the bottom edge.
*/
public void setWrap(boolean wrap) {
this.wrap = wrap;
}
/**
* Get the wrap amount.
*
* #return the wrap amount value
*/
public int getWrapAmount() {
return wrapAmount;
}
/**
* Specify the wrapping amount. This specifies the space between the end of the
* text on the top edge and the start of the text from the bottom edge when
* wrapping is turned on.
*
* #param wrapAmount the amount in pixels
*/
public void setWrapAmount(int wrapAmount) {
this.wrapAmount = wrapAmount;
}
/**
* Start scrolling the components on the panel. Components will start
* scrolling from the bottom edge towards the top edge.
*/
public void startScrolling() {
paintChildren = true;
scrollOffset = -getSize().height;
timer.start();
}
/**
* Stop scrolling the components on the panel. The conponents will be
* cleared from the view of the panel
*/
public void stopScrolling() {
timer.stop();
paintChildren = false;
repaint();
}
/**
* The components will stop scrolling but will remain visible
*/
public void pauseScrolling() {
if (timer.isRunning()) {
timer.stop();
scrollingPaused = true;
}
}
/**
* The components will resume scrolling from where scrolling was stopped.
*/
public void resumeScrolling() {
if (scrollingPaused) {
timer.restart();
scrollingPaused = false;
}
}
// Implement ActionListener
/**
* Adjust the offset of the components on the panel so it appears that
* they are scrolling from bottom to top.
*/
#Override
public void actionPerformed(ActionEvent e) {
scrollOffset += scrollAmount;
int height = super.getPreferredSize().height;
if (scrollOffset > height) {
scrollOffset = isWrap() ? wrapOffset + scrollAmount : -getSize().height;
}
repaint();
}
// Implement AncestorListener
/**
* Get notified when the panel is added to a Window so we can use a
* WindowListener to automatically start the scrolling of the components.
*/
#Override
public void ancestorAdded(AncestorEvent event) {
SwingUtilities.windowForComponent(this).addWindowListener(this);
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
// Implement WindowListener
#Override
public void windowOpened(WindowEvent e) {
startScrolling();
}
#Override
public void windowClosing(WindowEvent e) {
stopScrolling();
}
#Override
public void windowClosed(WindowEvent e) {
stopScrolling();
}
#Override
public void windowIconified(WindowEvent e) {
pauseScrolling(); }
#Override
public void windowDeiconified(WindowEvent e) {
resumeScrolling(); }
#Override
public void windowActivated(WindowEvent e) {
if (isScrollWhenFocused()) {
resumeScrolling();
}
}
#Override
public void windowDeactivated(WindowEvent e) {
if (isScrollWhenFocused()) {
pauseScrolling();
}
}
}
public class Main extends JDialog implements ActionListener {
private JTable table;
private DefaultTableModel model;
MarqueePanelV mpv = new MarqueePanelV();
private Timer timer2;
public static void main(String[] args) {
// TODO code application logic here
new Main();
}
Main() {
setSize(600, 400);
this.setLocation(300, 300);
table = new JTable();
model = new DefaultTableModel(20, 2);
table.setModel(model);
table.getColumnModel().getColumn(0).setPreferredWidth(280);
table.getColumnModel().getColumn(1).setPreferredWidth(280);
for(int i = 0; i < 20; i++) {
model.setValueAt(i, i, 0);
model.setValueAt(100 + i, i, 1);
}
mpv.add(table);
add(mpv);
mpv.setWrap(true);
mpv.setWrapAmount(0);
mpv.startScrolling();
table.addMouseListener(new MouseListener() {
// Implement MouseListener
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
mpv.pauseScrolling();
}
#Override
public void mouseExited(MouseEvent e) {
mpv.resumeScrolling();
}
});
setVisible(true);
timer2 = new Timer(2000, this);
timer2.setInitialDelay(1000);
timer2.setDelay(2000);
timer2.start();
}
int k = 0;
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < 20; i++) {
model.setValueAt(100 + i + k, i, 1);
}
k++;
mpv.repaint();
}
}

Related

JFrame with a Transparent Background Flickers when using ComponentResizer

Problem: An undecorated JFrame with a transparent background flickers when using a ComponentResizer to resize it. As seen in the below video and MCVE, the problem does not occur with an opaque background.
ComponentResizer (A MouseAdapter) works by calculating the drag distance and direction when the mouse is dragged and changes the size of its component accordingly.
The answer to What causes the Jframe to flicker while resizing? links to How to stop the auto-repaint() when I resize the Jframe, which says to turn of dynamic layout with Toolkit.getDefaultToolkit().setDynamicLayout(false), however, this does not solve the problem as it has no effect, possibly because macOS is not a platform that allows it to be disabled.
Question: How can I allow the user to resize an undecorated JFrame with a transparent background without it flickering? Is ComponentResizer the problem?
MCVE: (Length due to ComponentResizer class)
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
public class JFrameFlickerMCVE {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
Toolkit.getDefaultToolkit().setDynamicLayout(false);
final JFrame frame = new JFrame();
frame.setUndecorated(true);
final JToggleButton backgroundButton = new JToggleButton("Break me!");
backgroundButton.setSelected(true);
backgroundButton.addActionListener(e -> {
if(!backgroundButton.isSelected()) {
frame.setBackground(new Color(0, 0, 0, 0));
backgroundButton.setText("Fix me!");
} else {
frame.setBackground(UIManager.getColor("control"));
backgroundButton.setText("Break me!");
}
});
final JLabel label = new JLabel("Resize Here");
label.setBorder(BorderFactory.createLineBorder(Color.RED));
frame.getContentPane().add(backgroundButton);
frame.getContentPane().add(label, BorderLayout.SOUTH);
new ComponentResizer(frame);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
/**
* The ComponentResizer allows you to resize a component by dragging a border
* of the component.
*/
public static class ComponentResizer extends MouseAdapter
{
private final static Dimension MINIMUM_SIZE = new Dimension(10, 10);
private final static Dimension MAXIMUM_SIZE =
new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>();
{
cursors.put(1, Cursor.N_RESIZE_CURSOR);
cursors.put(2, Cursor.W_RESIZE_CURSOR);
cursors.put(4, Cursor.S_RESIZE_CURSOR);
cursors.put(8, Cursor.E_RESIZE_CURSOR);
cursors.put(3, Cursor.NW_RESIZE_CURSOR);
cursors.put(9, Cursor.NE_RESIZE_CURSOR);
cursors.put(6, Cursor.SW_RESIZE_CURSOR);
cursors.put(12, Cursor.SE_RESIZE_CURSOR);
}
private Insets dragInsets;
private Dimension snapSize;
private int direction;
protected static final int NORTH = 1;
protected static final int WEST = 2;
protected static final int SOUTH = 4;
protected static final int EAST = 8;
private Cursor sourceCursor;
private boolean resizing;
private Rectangle bounds;
private Point pressed;
private boolean autoscrolls;
private Dimension minimumSize = MINIMUM_SIZE;
private Dimension maximumSize = MAXIMUM_SIZE;
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components must be registered separately.
*/
public ComponentResizer()
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1));
}
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param components components to be automatically registered
*/
public ComponentResizer(Component... components)
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components);
}
/**
* Convenience contructor. Eligible borders are resisable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Component... components)
{
this(dragInsets, new Dimension(1, 1), components);
}
/**
* Create a ComponentResizer.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param snapSize Specify the dimension to which the border will snap to
* when being dragged. Snapping occurs at the halfway mark.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components)
{
setDragInsets( dragInsets );
setSnapSize( snapSize );
registerComponent( components );
}
/**
* Get the drag insets
*
* #return the drag insets
*/
public Insets getDragInsets()
{
return dragInsets;
}
/**
* Set the drag dragInsets. The insets specify an area where mouseDragged
* events are recognized from the edge of the border inwards. A value of
* 0 for any size will imply that the border is not resizable. Otherwise
* the appropriate drag cursor will appear when the mouse is inside the
* resizable border area.
*
* #param dragInsets Insets to control which borders are resizeable.
*/
public void setDragInsets(Insets dragInsets)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.dragInsets = dragInsets;
}
/**
* Get the components maximum size.
*
* #return the maximum size
*/
public Dimension getMaximumSize()
{
return maximumSize;
}
/**
* Specify the maximum size for the component. The component will still
* be constrained by the size of its parent.
*
* #param maximumSize the maximum size for a component.
*/
public void setMaximumSize(Dimension maximumSize)
{
this.maximumSize = maximumSize;
}
/**
* Get the components minimum size.
*
* #return the minimum size
*/
public Dimension getMinimumSize()
{
return minimumSize;
}
/**
* Specify the minimum size for the component. The minimum size is
* constrained by the drag insets.
*
* #param minimumSize the minimum size for a component.
*/
public void setMinimumSize(Dimension minimumSize)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.minimumSize = minimumSize;
}
/**
* Remove listeners from the specified component
*
* #param component the component the listeners are removed from
*/
public void deregisterComponent(Component... components)
{
for (Component component : components)
{
component.removeMouseListener( this );
component.removeMouseMotionListener( this );
}
}
/**
* Add the required listeners to the specified component
*
* #param component the component the listeners are added to
*/
public void registerComponent(Component... components)
{
for (Component component : components)
{
component.addMouseListener( this );
component.addMouseMotionListener( this );
}
}
/**
* Get the snap size.
*
* #return the snap size.
*/
public Dimension getSnapSize()
{
return snapSize;
}
/**
* Control how many pixels a border must be dragged before the size of
* the component is changed. The border will snap to the size once
* dragging has passed the halfway mark.
*
* #param snapSize Dimension object allows you to separately spcify a
* horizontal and vertical snap size.
*/
public void setSnapSize(Dimension snapSize)
{
this.snapSize = snapSize;
}
/**
* When the components minimum size is less than the drag insets then
* we can't determine which border should be resized so we need to
* prevent this from happening.
*/
private void validateMinimumAndInsets(Dimension minimum, Insets drag)
{
int minimumWidth = drag.left + drag.right;
int minimumHeight = drag.top + drag.bottom;
if (minimum.width < minimumWidth
|| minimum.height < minimumHeight)
{
String message = "Minimum size cannot be less than drag insets";
throw new IllegalArgumentException( message );
}
}
/**
*/
#Override
public void mouseMoved(MouseEvent e)
{
Component source = e.getComponent();
Point location = e.getPoint();
direction = 0;
if (location.x < dragInsets.left)
direction += WEST;
if (location.x > source.getWidth() - dragInsets.right - 1)
direction += EAST;
if (location.y < dragInsets.top)
direction += NORTH;
if (location.y > source.getHeight() - dragInsets.bottom - 1)
direction += SOUTH;
// Mouse is no longer over a resizable border
if (direction == 0)
{
source.setCursor( sourceCursor );
}
else // use the appropriate resizable cursor
{
int cursorType = cursors.get( direction );
Cursor cursor = Cursor.getPredefinedCursor( cursorType );
source.setCursor( cursor );
}
}
#Override
public void mouseEntered(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
sourceCursor = source.getCursor();
}
}
#Override
public void mouseExited(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
source.setCursor( sourceCursor );
}
}
#Override
public void mousePressed(MouseEvent e)
{
// The mouseMoved event continually updates this variable
if (direction == 0) return;
// Setup for resizing. All future dragging calculations are done based
// on the original bounds of the component and mouse pressed location.
resizing = true;
Component source = e.getComponent();
pressed = e.getPoint();
SwingUtilities.convertPointToScreen(pressed, source);
bounds = source.getBounds();
// Making sure autoscrolls is false will allow for smoother resizing
// of components
if (source instanceof JComponent)
{
JComponent jc = (JComponent)source;
autoscrolls = jc.getAutoscrolls();
jc.setAutoscrolls( false );
}
}
/**
* Restore the original state of the Component
*/
#Override
public void mouseReleased(MouseEvent e)
{
resizing = false;
Component source = e.getComponent();
source.setCursor( sourceCursor );
if (source instanceof JComponent)
{
((JComponent)source).setAutoscrolls( autoscrolls );
}
}
/**
* Resize the component ensuring location and size is within the bounds
* of the parent container and that the size is within the minimum and
* maximum constraints.
*
* All calculations are done using the bounds of the component when the
* resizing started.
*/
#Override
public void mouseDragged(MouseEvent e)
{
if (resizing == false) return;
Component source = e.getComponent();
Point dragged = e.getPoint();
SwingUtilities.convertPointToScreen(dragged, source);
changeBounds(source, direction, bounds, pressed, dragged);
}
protected void changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current)
{
// Start with original locaton and size
int x = bounds.x;
int y = bounds.y;
int width = bounds.width;
int height = bounds.height;
// Resizing the West or North border affects the size and location
if (WEST == (direction & WEST))
{
int drag = getDragDistance(pressed.x, current.x, snapSize.width);
int maximum = Math.min(width + x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
x -= drag;
width += drag;
}
if (NORTH == (direction & NORTH))
{
int drag = getDragDistance(pressed.y, current.y, snapSize.height);
int maximum = Math.min(height + y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
y -= drag;
height += drag;
}
// Resizing the East or South border only affects the size
if (EAST == (direction & EAST))
{
int drag = getDragDistance(current.x, pressed.x, snapSize.width);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.width - x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
width += drag;
}
if (SOUTH == (direction & SOUTH))
{
int drag = getDragDistance(current.y, pressed.y, snapSize.height);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.height - y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
height += drag;
}
source.setBounds(x, y, width, height);
source.validate();
}
/*
* Determine how far the mouse has moved from where dragging started
*/
private int getDragDistance(int larger, int smaller, int snapSize)
{
int halfway = snapSize / 2;
int drag = larger - smaller;
drag += (drag < 0) ? -halfway : halfway;
drag = (drag / snapSize) * snapSize;
return drag;
}
/*
* Adjust the drag value to be within the minimum and maximum range.
*/
private int getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum)
{
while (dimension + drag < minimum)
drag += snapSize;
while (dimension + drag > maximum)
drag -= snapSize;
return drag;
}
/*
* Keep the size of the component within the bounds of its parent.
*/
private Dimension getBoundingSize(Component source)
{
if (source instanceof Window)
{
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
return new Dimension(bounds.width, bounds.height);
}
else
{
return source.getParent().getSize();
}
}
}
}
I searched for a solution but no one was working...
The reason seems to be the redrawing of all component tree during the resize user action.
As the redrawing of the resized component and of its children components is done not only after mouse released (end of the resize) but during the resize too (during mouse dragging) I tried to comment the
source.validate();
line into
changeBounds method (it is continuously called during dragging).
I put the line at the beginning of mouseReleased method into a if block:
if(resizing == true) {
e.getComponent().validate();
}
It works (the flickering is almost absent).
Let me know if this solution works for you too...

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();
}
}
}

Button with round edges

I am trying to get a button with round edges. My button has a background color of yellow. I am not able to get the round edge for my button. Here is the code i am trying
class RoundedBorder implements Border {
int radius;
RoundedBorder(int radius) {
this.radius = radius;
}
public Insets getBorderInsets(Component c) {
return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.drawRoundRect(x,y,width-1,height-1,radius,radius);
}
}
jButton1.setText(aContinue);
jButton1.setBackground(new java.awt.Color(255, 255, 0));
jButton1.setBorder(new RoundedBorder(20));
I am not able to get round edges using this piece of code.. Here is how my button looks .
I want to have round edges with no overflowing background color.
Option1- Use Images
Option2- Use the following code (Extracted from Make a button round)
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class RoundButton extends JButton {
public RoundButton(String label) {
super(label);
// These statements enlarge the button so that it
// becomes a circle rather than an oval.
Dimension size = getPreferredSize();
size.width = size.height = Math.max(size.width,
size.height);
setPreferredSize(size);
// This call causes the JButton not to paint
// the background.
// This allows us to paint a round background.
setContentAreaFilled(false);
}
// Paint the round background and label.
protected void paintComponent(Graphics g) {
if (getModel().isArmed()) {
// You might want to make the highlight color
// a property of the RoundButton class.
g.setColor(Color.lightGray);
} else {
g.setColor(getBackground());
}
g.fillOval(0, 0, getSize().width-1,
getSize().height-1);
// This call will paint the label and the
// focus rectangle.
super.paintComponent(g);
}
// Paint the border of the button using a simple stroke.
protected void paintBorder(Graphics g) {
g.setColor(getForeground());
g.drawOval(0, 0, getSize().width-1,
getSize().height-1);
}
// Hit detection.
Shape shape;
public boolean contains(int x, int y) {
// If the button has changed size,
// make a new shape object.
if (shape == null ||
!shape.getBounds().equals(getBounds())) {
shape = new Ellipse2D.Float(0, 0,
getWidth(), getHeight());
}
return shape.contains(x, y);
}
// Test routine.
public static void main(String[] args) {
// Create a button with the label "Jackpot".
JButton button = new RoundButton("Jackpot");
button.setBackground(Color.green);
// Create a frame in which to show the button.
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.yellow);
frame.getContentPane().add(button);
frame.getContentPane().setLayout(new FlowLayout());
frame.setSize(150, 150);
frame.setVisible(true);
}
}
Option3- Use Look and Feel that supports Round Buttons http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/NimbusLookandFeel_OBE2012/CustomizingLandF.html
Option4- Go with JavaFX and use CSS. There are free CSS scripts supporting this
Found this great example by oracle which supplies a class which creates RoundButton.
Here is an example using an edited RoundButton class to create RoundedButton class:
import java.awt.AWTEvent;
import java.awt.AWTEventMulticaster;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public Test() {
initComponents();
}
private void initComponents() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField tf = new JTextField("");
RoundedButton rb = new RoundedButton("Go");
rb.setBackground(Color.yellow);
rb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(frame, "You said: " + tf.getText());
}
});
frame.add(tf, BorderLayout.NORTH);
frame.add(rb);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
class RoundedButton extends Component {
ActionListener actionListener; // Post action events to listeners
String label; // The Button's text
protected boolean pressed = false; // true if the button is detented.
/**
* Constructs a RoundedButton with no label.
*/
public RoundedButton() {
this("");
}
/**
* Constructs a RoundedButton with the specified label.
*
* #param label the label of the button
*/
public RoundedButton(String label) {
this.label = label;
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* gets the label
*
* #see setLabel
*/
public String getLabel() {
return label;
}
/**
* sets the label
*
* #see getLabel
*/
public void setLabel(String label) {
this.label = label;
invalidate();
repaint();
}
/**
* paints the RoundedButton
*/
#Override
public void paint(Graphics g) {
// paint the interior of the button
if (pressed) {
g.setColor(getBackground().darker().darker());
} else {
g.setColor(getBackground());
}
g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 20, 20);
// draw the perimeter of the button
g.setColor(getBackground().darker().darker().darker());
g.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 20, 20);
// draw the label centered in the button
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
g.setColor(getForeground());
g.drawString(label, getWidth() / 2 - fm.stringWidth(label) / 2, getHeight() / 2 + fm.getMaxDescent());
}
}
/**
* The preferred size of the button.
*/
#Override
public Dimension getPreferredSize() {
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
return new Dimension(max, max);
} else {
return new Dimension(100, 100);
}
}
/**
* The minimum size of the button.
*/
#Override
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
/**
* Adds the specified action listener to receive action events from this
* button.
*
* #param listener the action listener
*/
public void addActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.add(actionListener, listener);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Removes the specified action listener so it no longer receives action
* events from this button.
*
* #param listener the action listener
*/
public void removeActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.remove(actionListener, listener);
}
/**
* Determine if click was inside round button.
*/
#Override
public boolean contains(int x, int y) {
int mx = getSize().width / 2;
int my = getSize().height / 2;
return (((mx - x) * (mx - x) + (my - y) * (my - y)) <= mx * mx);
}
/**
* Paints the button and distribute an action event to all listeners.
*/
#Override
public void processMouseEvent(MouseEvent e) {
Graphics g;
switch (e.getID()) {
case MouseEvent.MOUSE_PRESSED:
// render myself inverted....
pressed = true;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
break;
case MouseEvent.MOUSE_RELEASED:
if (actionListener != null) {
actionListener.actionPerformed(new ActionEvent(
this, ActionEvent.ACTION_PERFORMED, label));
}
// render myself normal again
if (pressed == true) {
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
}
break;
case MouseEvent.MOUSE_ENTERED:
break;
case MouseEvent.MOUSE_EXITED:
if (pressed == true) {
// Cancel! Don't send action event.
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
// Note: for a more complete button implementation,
// you wouldn't want to cancel at this point, but
// rather detect when the mouse re-entered, and
// re-highlight the button. There are a few state
// issues that that you need to handle, which we leave
// this an an excercise for the reader (I always
// wanted to say that!)
}
break;
}
super.processMouseEvent(e);
}
}
Another clean way to make this happen is to define a custom ButtonUI that would draw a rounded corner button.

Java paint issue

OK, i've been searching and googling for ages and it's driving me MAD. (please bear in mind this started as code from the University).
Any help would be massively appreciated! Since I appear to have a mental block when it comes to java graphics :(.
The problem is to label an image however i'm having issues with half of the image being lost for various reasons unless I move around the frame and then it comes back. Likewise if I try to remove 'labels' which are painted on the BufferedImage, I have to move around the Jframe in order to see the result.
Lastly, when the JOptionPane pops up to ask for a label name, it does this when closed:
getting an image to post
https://docs.google.com/document/d/1I5rFH23a75IHB6twTu1dPRwmRt-By1sIUvDvI6FE_OY/edit
package hci;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Deals with the label tools
*/
public class LabelToolGUI extends JPanel implements ActionListener {
JButton newButton = new JButton("New");
JButton deleteButton = new JButton("Delete");
JButton editButton = new JButton("Edit");
JButton undoButton = new JButton("Undo");
JComboBox labelsBox = new JComboBox();
String saveIcon = "./images/icons/save.jpg";
String deleteIcon = "./images/icons/delete.jpg";
String openIcon = "./images/icons/open.jpg";
String newIcon = "./images/icons/new.jpg";
String helpIcon = "./images/icons/help.jpg";
String labelsIcon = "./images/icons/help.jpg";
String fname = "";
String fnameURL = "";
public LabelToolGUI() {
super();
newButton.addActionListener(this);
deleteButton.addActionListener(this);
editButton.addActionListener(this);
undoButton.addActionListener(this);
setLayout(new BorderLayout());
setBorder(BorderFactory.createTitledBorder("Label tools"));
add(newButton, BorderLayout.WEST);
add(deleteButton, BorderLayout.EAST);
add(editButton, BorderLayout.CENTER);
add(labelsBox, BorderLayout.NORTH);
add(undoButton, BorderLayout.SOUTH);
}
#Override
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if(src == newButton) {
String labelName = JOptionPane.showInputDialog("Please input a value");
ImageLabeller.addNewPolygon(labelName);
labelsBox.addItem(labelName);
} else if(src == deleteButton) {
String toDelete = labelsBox.getSelectedItem().toString();
System.out.println("Deleting " + toDelete);
labelsBox.removeItem(labelsBox.getSelectedItem());
ImageLabeller.removeLabel(toDelete);
} else if(src == undoButton) {
ImageLabeller.undo();
ImageLabeller.imagePanel.repaint();
}
}
}
package hci;
import javax.swing.*;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* Main class of the program - handles display of the main window
* #author Michal
*
*/
public class ImageLabeller extends JFrame implements ActionListener {
/**
* some java stuff to get rid of warnings
*/
private static final long serialVersionUID = 1L;
/**
* main window panel
*/
JPanel appPanel = null;
/**
* toolbox - put all buttons and stuff here!
*/
JPanel toolboxPanel = null;
/**
* image panel - displays image and editing area
*/
static ImagePanel imagePanel = null;
/**
* handles New Object button action
*/
public static void addNewPolygon(String labelName) {
imagePanel.addNewPolygon(labelName);
}
public static void removeLabel(String labelName) {
LabelHandler.deleteLabel(labelName);
}
/**
* Removes last point added to the label.
*/
public static void undo() {
imagePanel.currentLabel.removeLast();
}
/*#Override
public void paint(Graphics g) {
super.paint(g);
imagePanel.paint(g); //update image panel
} */
/**
* sets up application window
* #param imageFilename image to be loaded for editing
* #throws Exception
*/
public void setupGUI(String imageFilename) throws Exception {
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
//here we exit the program (maybe we should ask if the user really wants to do it?)
//maybe we also want to store the polygons somewhere? and read them next time
System.out.println("Bye bye!");
System.exit(0);
}
});
//setup main window panel
appPanel = new JPanel();
this.setLayout(new BoxLayout(appPanel, BoxLayout.X_AXIS));
this.setContentPane(appPanel);
//Create and set up the image panel.
imagePanel = new ImagePanel(imageFilename);
imagePanel.setOpaque(true); //content panes must be opaque
appPanel.add(imagePanel);
//add toolbox to window
appPanel.add(new LabelToolGUI());
//display all the stuff
this.pack();
this.setVisible(true);
}
/**
* Runs the program
* #param argv path to an image
*/
public static void main(String argv[]) {
try {
//create a window and display the image
ImageLabeller window = new ImageLabeller();
window.setupGUI(argv[0]);
} catch (Exception e) {
System.err.println("Image: " + argv[0]);
e.printStackTrace();
}
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action clicked");
imagePanel.paint(imagePanel.getGraphics());
}
}
package hci;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import hci.utils.*;
/**
* Handles image editing panel
* #author Michal
*
*/
public class ImagePanel extends JPanel implements MouseListener {
/**
* some java stuff to get rid of warnings
*/
private static final long serialVersionUID = 1L;
/**
* image to be tagged
*/
BufferedImage image = null;
/**
* list of current polygon's vertices
*/
Label currentLabel = null;
/**
* default constructor, sets up the window properties
*/
public ImagePanel() {
currentLabel = new Label();
this.setVisible(true);
Dimension panelSize = new Dimension(800, 600);
this.setSize(panelSize);
this.setMinimumSize(panelSize);
this.setPreferredSize(panelSize);
this.setMaximumSize(panelSize);
addMouseListener(this);
}
/**
* extended constructor - loads image to be labelled
* #param imageName - path to image
* #throws Exception if error loading the image
*/
public ImagePanel(String imageName) throws Exception{
this();
image = ImageIO.read(new File(imageName));
if (image.getWidth() > 800 || image.getHeight() > 600) {
int newWidth = image.getWidth() > 800 ? 800 : (image.getWidth() * 600)/image.getHeight();
int newHeight = image.getHeight() > 600 ? 600 : (image.getHeight() * 800)/image.getWidth();
System.out.println("SCALING TO " + newWidth + "x" + newHeight );
Image scaledImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST);
image = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
image.getGraphics().drawImage(scaledImage, 0, 0, this);
}
}
/**
* Displays the image
*/
public void ShowImage() {
Graphics g = this.getGraphics();
if (image != null) {
g.drawImage(image, 0, 0, null);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
//display iamge
ShowImage();
//display all the completed polygons
for(Label l : LabelHandler.labels) {
drawLabel(l);
finishPolygon(l);
}
//display current polygon
drawLabel(currentLabel);
}
/**
* displays a polygon without last stroke
* #param l label to be displayed
*/
public void drawLabel(Label l) {
Graphics2D g = (Graphics2D)this.getGraphics();
g.setColor(Color.GREEN);
for(int i = 0; i < l.size(); i++) {
Point currentVertex = l.getPoint(i);
if (i != 0) {
Point prevVertex = l.getPoint(i - 1);
g.drawLine(prevVertex.getX(), prevVertex.getY(), currentVertex.getX(), currentVertex.getY());
}
g.fillOval(currentVertex.getX() - 5, currentVertex.getY() - 5, 10, 10);
}
}
/**
* displays last stroke of the polygon (arch between the last and first vertices)
* #param l label to be finished
*/
public void finishPolygon(Label l) {
//if there are less than 3 vertices than nothing to be completed
if (l.size() >= 3) {
Point firstVertex = l.getPoint(0);
Point lastVertex = l.getPoint(l.size() - 1);
Graphics2D g = (Graphics2D)this.getGraphics();
g.setColor(Color.GREEN);
g.drawLine(firstVertex.getX(), firstVertex.getY(), lastVertex.getX(), lastVertex.getY());
}
}
/**
* moves current polygon to the list of polygons and makes pace for a new one
*/
public void addNewPolygon(String labelName) {
//finish the current polygon if any
if (currentLabel != null ) {
finishPolygon(currentLabel);
LabelHandler.addLabel(currentLabel);
}
currentLabel = new Label(labelName);
}
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
//check if the cursos withing image area
if (x > image.getWidth() || y > image.getHeight()) {
//if not do nothing
return;
}
Graphics2D g = (Graphics2D)this.getGraphics();
//if the left button than we will add a vertex to poly
if (e.getButton() == MouseEvent.BUTTON1) {
g.setColor(Color.GREEN);
if (currentLabel.size() != 0) {
Point lastVertex = currentLabel.getPoint(currentLabel.size() - 1);
g.drawLine(lastVertex.getX(), lastVertex.getY(), x, y);
}
g.fillOval(x-5,y-5,10,10);
currentLabel.addPoint(new Point(x,y));
System.out.println(x + " " + y);
}
}
#Override
public void mouseEntered(MouseEvent arg0) {
}
#Override
public void mouseExited(MouseEvent arg0) {
}
#Override
public void mousePressed(MouseEvent arg0) {
}
#Override
public void mouseReleased(MouseEvent arg0) {
}
}
Don't use getGraphics() for your painting, as it is temporary buffer which is recycled on next repaint. Do you painting in paintComponent() and call repaint() if needed.
Also don't call paint() manually (ie: imagePanel.paint(imagePanel.getGraphics());)
See Performing Custom Painting for more information. Also see Painting in AWT and Swing.
In ImagePanel.paintComponent replace ShowImage(); by if (image != null) g.drawImage( image, 0, 0, this );.
The device context g is initialized by swing, you don"t have to obtain another by getGraphics().

A Java Scroll Indicator with floating scrollbars which only appears during scrolling

I need a scroll pane with scrollbars only visible during scroll events or when needed to pull.
Something that looks like the scroll bars on the nowadays smartphones.
I searched a lot but found only realizations in javascript.
So I tried it by myself.
Has anyone a better solution or hints?
The animation classes can be found here: TimingFrameWork
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyTimes;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
* A scrollpane like component, where the scroll bars are floating over the
* scrollable view to indicate the current scroll positions.
* The scroll indicators appear smoothly during scroll events and disappear
* smoothly afterwards.
* <p>
* The scrollbars can be dragged just as normal.</p>
* <p>
* The usage is similar to a classic scrollpane.</p>
*
* #author Jolly Littlebottom
*/
public class JScrollIndicator extends JLayeredPane {
private static final Color THUMB_COLOR = Color.DARK_GRAY;
private static final Color THUMB_BORDER_COLOR = Color.LIGHT_GRAY;
private static final float MAX_ALPHA = 0.8f;
private static final int THUMB_THICKNESS = 7;
private static final int THUMB_MIN_SIZE = 48;
private static final int THUMB_MARGIN = 3;
private static final int FADE_IN_TIME = 300;
private static final int STAY_TIME = 2000;
private static final int FADE_OUT_TIME = 1000;
private final JScrollPane scrollPane;
private final ControlPanel controlPanel;
/**
* Creates a <code>JScrollIndicator</code> that displays the contents of the
* specified component, where both horizontal and vertical scrollbars appear
* whenever the component's contents are larger than the view and scrolling
* in underway or the mouse is over the scrollbar position.
*
* #see #setViewportView
* #param view the component to display in the scrollpane's viewport
*/
public JScrollIndicator(final JComponent view) {
this(view, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
/**
* Creates a <code>JScrollIndicator</code> that displays the view component
* in a viewport whose view position can be controlled with a pair of
* scrollbars.
* The scrollbar policies specify when the scrollbars are displayed,
* For example, if <code>vsbPolicy</code> is
* <code>JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED</code>
* then the vertical scrollbar only appears if the view doesn't fit
* vertically. The available policy settings are listed at
* {#link #JScrollPane.setVerticalScrollBarPolicy} and
* {#link #JScrollPane.setHorizontalScrollBarPolicy}.
*
* #param view the component to display in the scrollpanes viewport
* #param vsbPolicy an integer that specifies the vertical scrollbar policy
* #param hsbPolicy an integer that specifies the horizontal scrollbar policy
*/
public JScrollIndicator(final JComponent view, int vsbPolicy, int hsbPolicy) {
scrollPane = new JScrollPane(view, vsbPolicy, hsbPolicy);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
add(scrollPane, JLayeredPane.DEFAULT_LAYER);
controlPanel = new ControlPanel(scrollPane);
add(controlPanel, JLayeredPane.PALETTE_LAYER);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
// listen to changes of JLayeredPane size
scrollPane.setSize(getSize());
scrollPane.getViewport().revalidate();
controlPanel.setSize(getSize());
controlPanel.revalidate();
}
});
}
/**
* Returns the scroll pane used by this scroll indicator.
* Use carefully (e.g. to set unit increments) because not all changes have an
* effect. You have to write listeners in this cases (e.g. for changing the
* scrollbar policy)
*
* #return
*/
public JScrollPane getScrollPane() {
return scrollPane;
}
private class ControlPanel extends JPanel {
private final JMyScrollBar vScrollBar;
private final JMyScrollBar hScrollBar;
private ControlPanel(JScrollPane scrollPane) {
setLayout(new BorderLayout());
setOpaque(false);
vScrollBar = new JMyScrollBar(JScrollBar.VERTICAL);
scrollPane.setVerticalScrollBar(vScrollBar);
scrollPane.remove(vScrollBar);
if (scrollPane.getVerticalScrollBarPolicy() != JScrollPane.VERTICAL_SCROLLBAR_NEVER) {
add(vScrollBar, BorderLayout.EAST);
}
hScrollBar = new JMyScrollBar(JScrollBar.HORIZONTAL);
scrollPane.setHorizontalScrollBar(hScrollBar);
scrollPane.remove(hScrollBar);
if (scrollPane.getHorizontalScrollBarPolicy() != JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
add(hScrollBar, BorderLayout.SOUTH);
}
}
}
private class JMyScrollBar extends JScrollBar {
protected final MyScrollBarUI scrollUI;
public JMyScrollBar(int direction) {
super(direction);
scrollUI = new MyScrollBarUI(this);
super.setUI(scrollUI);
int size = THUMB_THICKNESS + THUMB_MARGIN;
setPreferredSize(new Dimension(size, size));
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
scrollUI.setVisible(true, true);
}
#Override
public void mouseExited(MouseEvent e) {
scrollUI.setVisible(false, false);
}
});
addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
scrollUI.setVisible(true, false);
}
});
}
#Override
public void setUI(ScrollBarUI ui) {
}
#Override
public void updateUI() {
}
#Override
public void paint(Graphics g) {
scrollUI.paintThumb(g, this); // just the thumb
}
#Override
public void repaint(Rectangle r) {
JScrollIndicator scrollIndicator = JScrollIndicator.this;
Rectangle rect = SwingUtilities.convertRectangle(this, r, scrollIndicator);
rect.grow(1, 1);
// ensure for a translucent thumb, that the view is first painted
scrollIndicator.repaint(rect);
}
}
public class MyScrollBarUI extends BasicScrollBarUI {
private JMyScrollBar scrollBar;
private float alpha = 0.0f;
private FadeAnimation fadeAnimation;
private MyScrollBarUI(JMyScrollBar scrollBar) {
this.scrollBar = scrollBar;
fadeAnimation = new FadeAnimation(this);
}
#Override
protected void installComponents() {
incrButton = new JButton();
decrButton = new JButton();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
int size = THUMB_THICKNESS + THUMB_MARGIN; // let lower right corner empty
incrButton.setPreferredSize(new Dimension(size, size));
}
else {
incrButton.setPreferredSize(new Dimension(THUMB_MARGIN, THUMB_MARGIN));
}
decrButton.setPreferredSize(new Dimension(THUMB_MARGIN, THUMB_MARGIN));
}
#Override
protected void installDefaults() {
super.installDefaults();
// ensure the minimum size of the thumb
int w = minimumThumbSize.width;
int h = minimumThumbSize.height;
if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
h = Math.max(h, Math.min(maximumThumbSize.height, THUMB_MIN_SIZE));
}
else {
w = Math.max(w, Math.min(maximumThumbSize.width, THUMB_MIN_SIZE));
}
minimumThumbSize = new Dimension(w, h);
}
private void paintThumb(Graphics g, JComponent c) {
if (alpha == 0.0f) {
return; // don't paint anything
}
g.setColor(getAlphaColor(THUMB_COLOR));
int radius = THUMB_THICKNESS >>> 1; // half width
Rectangle thumbBounds = getThumbBounds();
int x = thumbBounds.x;
int y = thumbBounds.y;
int w = thumbBounds.width;
int h = thumbBounds.height;
if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
w -= THUMB_MARGIN;
}
else {
h -= THUMB_MARGIN;
}
g.fillRoundRect(x, y, w, h, radius, radius);
g.setColor(getAlphaColor(THUMB_BORDER_COLOR));
g.drawRoundRect(x, y, w, h, radius, radius);
}
private Color getAlphaColor(Color color) {
if (alpha == 1.0f) {
return color;
}
int rgb = color.getRGB() & 0xFFFFFF; // color without alpha values
rgb |= ((int)(alpha*255)) << 24; // add alpha value
return new Color(rgb, true);
}
public void setAlpha(float alpha) {
this.alpha = alpha;
scrollBar.repaint(getThumbBounds());
}
public void setVisible(boolean visible, boolean mouseOver) {
if (visible) {
fadeAnimation.fadeIn(mouseOver);
}
else {
fadeAnimation.fadeOut();
}
scrollBar.repaint(getThumbBounds());
}
}
private class FadeAnimation {
private final MyScrollBarUI scrollUI;
private Animator fadeAnimator;
private Timer fadeOutTimer;
private boolean isFadeIn;
private FadeAnimation(MyScrollBarUI scrollUI) {
this.scrollUI = scrollUI;
}
public synchronized void fadeIn(boolean mouseOver) {
if (mouseOver) {
cancelTimer();
}
if (!isFadeIn) {
isFadeIn = true;
cancelAnimationAndTimer();
fadeAnimator = PropertySetter.createAnimator(
FADE_IN_TIME, scrollUI, "alpha",
new KeyFrames(KeyValues.create(scrollUI.alpha, MAX_ALPHA),
new KeyTimes(0.0f, 1.0f)));
fadeAnimator.start();
if (!mouseOver) {
fadeOutTimer = new Timer(FADE_IN_TIME + STAY_TIME,
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
FadeAnimation.this.fadeOut();
}
});
fadeOutTimer.start();
}
}
}
public synchronized void fadeOut() {
cancelAnimationAndTimer();
if (isFadeIn) {
isFadeIn = false;
fadeAnimator = PropertySetter.createAnimator(
FADE_OUT_TIME, scrollUI, "alpha",
new KeyFrames(KeyValues.create(scrollUI.alpha, 0.0f),
new KeyTimes(0.0f, 1.0f)));
fadeAnimator.start();
}
}
private void cancelAnimationAndTimer() {
if (fadeAnimator != null && fadeAnimator.isRunning()) {
fadeAnimator.cancel();
fadeAnimator = null;
}
cancelTimer();
}
private void cancelTimer() {
if (fadeOutTimer != null) {
fadeOutTimer.stop();
fadeOutTimer = null;
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String text = "";
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
text += i + " - " + j;
}
text += "\n";
}
JTextArea area = new JTextArea(text);
JScrollIndicator scrollIndicator = new JScrollIndicator(area);
frame.getContentPane().add(scrollIndicator);
frame.setBounds(100, 100, 200, 300);
frame.setVisible(true);
}
});
}
}

Categories

Resources