Decorating a JTextField with an image and hint - java

I'm trying to create some nicer looking JTextFields with an image and a hint. To do this I made a decorator that overrides the paintComponent method. The reason I used a decorator is that I wanted to apply it to other types of JTextField such as JPasswordField.
Here is what I've made so far;
The problem as seen in the form on the left is that, even though I have used a JPasswordField the paintComponent seems to ignore what I assume is the passwords paintComponent which presumably does the password masking symbols.
So the question is, how can I avoid duplicating the code for JTextFields and JPasswordFields but still have the different functionality such as password masking.
This is the decorator code;
public class JTextFieldHint extends JTextField implements FocusListener{
private JTextField jtf;
private Icon icon;
private String hint;
private Insets dummyInsets;
public JTextFieldHint(JTextField jtf, String icon, String hint){
this.jtf = jtf;
setIcon(createImageIcon("icons/"+icon+".png",icon));
this.hint = hint;
Border border = UIManager.getBorder("TextField.border");
JTextField dummy = new JTextField();
this.dummyInsets = border.getBorderInsets(dummy);
addFocusListener(this);
}
public void setIcon(Icon newIcon){
this.icon = newIcon;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int textX = 2;
if(this.icon!=null){
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
int x = dummyInsets.left + 5;
textX = x+iconWidth+2;
int y = (this.getHeight() - iconHeight)/2;
icon.paintIcon(this, g, x, y);
}
setMargin(new Insets(2, textX, 2, 2));
if ( this.getText().equals("")) {
int width = this.getWidth();
int height = this.getHeight();
Font prev = g.getFont();
Font italic = prev.deriveFont(Font.ITALIC);
Color prevColor = g.getColor();
g.setFont(italic);
g.setColor(UIManager.getColor("textInactiveText"));
int h = g.getFontMetrics().getHeight();
int textBottom = (height - h) / 2 + h - 4;
int x = this.getInsets().left;
Graphics2D g2d = (Graphics2D) g;
RenderingHints hints = g2d.getRenderingHints();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.drawString(hint, x, textBottom);
g2d.setRenderingHints(hints);
g.setFont(prev);
g.setColor(prevColor);
}
}
protected ImageIcon createImageIcon(String path, String description) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL, description);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
#Override
public void focusGained(FocusEvent arg0) {
this.repaint();
}
#Override
public void focusLost(FocusEvent arg0) {
this.repaint();
}
}
And this is where I create the fields;
JTextField usernameField = new JTextFieldHint(new JTextField(),"user_green","Username");
JTextField passwordField = new JTextFieldHint(new JPasswordField(),"bullet_key","Password");
Hopefully i've not went completely off in the wrong direction here!
Thanks!
EDIT : Again the more I look at it, it is obvious that calling super.paintComponent(g) is going to call the JTextFields paintcomponent, but I can't see how to solve this without duplicating the code.

Text Prompt works with a JPasswordField.
One difference is that the displayed icon disappears when text is entered. If you want the icon to be permanent then I suggest you create a custom "IconBorder* class to paint an Icon rather then do custom painting in the paintComponent() method.
You approach will not work unless you duplicate the code for both JTextField and JPasswordField.
Edit:
Actually you don't need to create a custom IconBorder. The MatteBorder supports the painting of an Icon in a Border.

In order to paint an icon inside a text field you need to add some insets.
You don't want to hard-code insets in your component but just add a little bit of space for the icon, letting clients and subclasses to set their own.
In the figure above I painted original insets in green and additional insets in red. First thing you want to extend JTextField. We keep track of two things: the original insets (the green ones) mBorder, and the icon.
public class IconTextField extends JTextField {
private Border mBorder;
private Icon mIcon;
// ...
}
Then you need to override setBorder() method.
#Override
public void setBorder(Border border) {
mBorder = border;
if (mIcon == null) {
super.setBorder(border);
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
Border compound = BorderFactory.createCompoundBorder(border, margin);
super.setBorder(compound);
}
}
Here, if we have an icon (the field mIcon is not null), we add our additional insets using a compound border. Then, you should also override the paintComponent() method.
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (mIcon != null) {
Insets iconInsets = mBorder.getBorderInsets(this);
mIcon.paintIcon(this, graphics, iconInsets.left, iconInsets.top);
}
}
Finally, you need a setIcon() method.
public void setIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
setBorder(mBorder);
}
What we are doing here is saving the icon and recalculating the borders.
If you want to do the same same thing with JPasswordField, the most elegant thing is probably to create a helper class with all the methods discussed above.
class IconTextComponentHelper {
private static final int ICON_SPACING = 4;
private Border mBorder;
private Icon mIcon;
private Border mOrigBorder;
private JTextComponent mTextComponent;
IconTextComponentHelper(JTextComponent component) {
mTextComponent = component;
mOrigBorder = component.getBorder();
mBorder = mOrigBorder;
}
Border getBorder() {
return mBorder;
}
void onPaintComponent(Graphics g) {
if (mIcon != null) {
Insets iconInsets = mOrigBorder.getBorderInsets(mTextComponent);
mIcon.paintIcon(mTextComponent, g, iconInsets.left, iconInsets.top);
}
}
void onSetBorder(Border border) {
mOrigBorder = border;
if (mIcon == null) {
mBorder = border;
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
mBorder = BorderFactory.createCompoundBorder(border, margin);
}
}
void onSetIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
mTextComponent.setBorder(mOrigBorder);
}
}
And use it like this:
public class IconTextField extends JTextField {
private IconTextComponentHelper mHelper = new IconTextComponentHelper(this);
public IconTextField() {
super();
}
public IconTextField(int cols) {
super(cols);
}
private IconTextComponentHelper getHelper() {
if (mHelper == null)
mHelper = new IconTextComponentHelper(this);
return mHelper;
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
getHelper().onPaintComponent(graphics);
}
public void setIcon(Icon icon) {
getHelper().onSetIcon(icon);
}
public void setIconSpacing(int spacing) {
getHelper().onSetIconSpacing(spacing);
}
#Override
public void setBorder(Border border) {
getHelper().onSetBorder(border);
super.setBorder(getHelper().getBorder());
}
}

Related

Java/Swing: How to pass properties from Boundary to Model and vice versa

I'm building an app where user can upload an image and draw rectangles on top of it for annotation and also attach comments to that certain region/rectangle, like in Word or Docs. I'm a bit confused on how to make the connection between the classes to pass information around. Once user draws a rectangle and clicks on it, they will be able to write comments in the JTextField provided, and the program will associate that rectangle with that comment. I have three classes Box, DrawingArea, and ImageAnnotator. One of the fields/properties of Box (which is the rectangle) is bComment, and inside the ImageAnnotator class I have a JButton which upon click, will need to set the String retrieved from the JTextField in the ImageAnnotator class to Box.bComment. I know the onClick method should be inside the ImageAnnotator class since that's where the button is but I'm a bit confused to how to pass this String over to Box. Should I import Box inside the ImageAnnotator and set it within an onClick method? So now the reverse way, upon clicking the rectangle, how do I set the JTextField in ImageAnnotator with the String retrieved from Box.bComment, that is, when the user clicks on the same rectangle again, the program will display the previously added comment in the text field. All the listeners for clicking on rectangle is inside the DrawingArea class so I would need to somehow get the Text field from ImageAnnotator and fill the text field with String retrieved from Box.bComment. For more clarity, I have provided the classes below.
ImageAnnotator.java:
public class ImageAnnotator extends JFrame {
private JPanel contentPane;
Model model;
DrawingArea drawingArea;
GroupLayout gl_contentPane;
private JTextField textField;
private JButton btnNewButton;
/**
* Create the frame.
*/
public ImageAnnotator(Model m) {
super();
this.model = m;
setTitle("Image Annotator");
...
setContentPane(contentPane);
drawingArea = new DrawingArea();
//ImageName = new JLabel(drawingArea.imageName);
buttonPanel = new ButtonPanel( drawingArea );
textField = new JTextField(); // this is the text field which will be later set to Box.bComment
btnNewButton = new JButton("Add this comment");//this is the button which needs onClick listener
gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(...);
gl_contentPane.setVerticalGroup(...);
contentPane.add(drawingArea);
contentPane.setLayout(gl_contentPane);
}
// onClick listener should be here for JButton
}
Box.java:
public class Box {
int bWidth, bHeight, bX, bY;
String bImageName, bComment;
Color foreground;
Rectangle rectangle;
public Box(int width, int height) {
bWidth = width;
bHeight = height;
}
public Box(Color foreground, Rectangle rectangle) {
this.foreground = foreground;
this.rectangle = rectangle;
}
public void setComment(String comment) { bComment = comment; }
public String getComment() { return bComment; }
public void setX(int x) { bX = x; }
public int getX() { return bX; }
public void setY(int y) { bY = y; }
public int getY() { return bY; }
public Rectangle getRectangle()
{
return rectangle;
}
}
DrawingArea.java:
public class DrawingArea extends JPanel implements BoxSelectionListener
{
private final static int AREA_SIZE = 490;
private BufferedImage image =
new BufferedImage(AREA_SIZE, AREA_SIZE, BufferedImage.TYPE_INT_ARGB);
private Rectangle shape;
private ArrayList<Box> rectangles = new ArrayList<Box>();
//public String imageName = ""; // this will store the image/file name
public DrawingArea()
{
setBackground(Color.WHITE);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
addMouseMotionListener(ml);
}
public void addBoxSelectionListener(BoxSelectionListener listener) {
listenerList.add(BoxSelectionListener.class, listener);
}
public void removeBoxSelectionListener(BoxSelectionListener listener) {
listenerList.remove(BoxSelectionListener.class, listener);
}
protected void fireBoxSelected(Box box) {
BoxSelectionListener[] listeners = listenerList.getListeners(BoxSelectionListener.class);
// Normally, I'd create a event object, which would wrap the source (this) and
// the Box together, but if there are no listeners, it's a bit of
// a waste to do so, so I return early in those cases
if (listeners.length == 0) {
return;
}
for (BoxSelectionListener listener : listeners) {
listener.didSelect(box);
}
}
public void didSelect(Box box) {
// Probably assign this to a "assigned" or "selected" property
// so it can painted differently
// And now we want to notify some kind of listener so that
// it can update the UI as required
box.setForeground(Color.red);
box.getComment();
repaint();
fireBoxSelected(box);
}
class MyMouseListener extends MouseInputAdapter
{
private Point startPoint;
public void mousePressed(MouseEvent e) {
// Mark the clip point
startPoint = e.getPoint();
}
public void mouseDragged(MouseEvent e) {
// Only create the shape when dragging starts
if (shape == null) {
shape = new Rectangle();
}
int x = Math.min(startPoint.x, e.getX());
int y = Math.min(startPoint.y, e.getY());
int width = Math.abs(startPoint.x - e.getX());
int height = Math.abs(startPoint.y - e.getY());
shape.setBounds(x, y, width, height);
repaint();
}
public void mouseReleased(MouseEvent e) {
if (shape != null) {
if (shape.width != 0 || shape.height != 0) {
addRectangle(shape, e.getComponent().getForeground());
}
} else {
for (Box b : rectangles) {
if (b.getRectangle().contains(e.getPoint())) {
didSelect(b);
break;
}
else
b.setForeground(Color.black);
}
}
startPoint = null;
shape = null;
}
}
}
I was able to get those working by making some fields static (the TextField) so that I can reference it from another class, and my mistake was that I was creating instances of the ImageAnnotator class in my DrawingArea class so when I try to reference the TextField from ImageAnnotator inside DrawingArea, it was null. So my solution was instead I called ImageAnnotator directly without creating an instance and I set the TextField to static.

Adding JPanel after removing it from JFrame

I'm developing a Java Game. I'm stuck at a point,where I need to restart the whole game again after GameOver. Here is the skeleton of my program:
package projectflappy;
import java.awt.*;
public final class TheGame extends JFrame implements MouseListener{
JPanel jp;
//declaration of the varibles
int x_width = 500;
int y_height = 500;
int count = 5 ;
Ellipse2D Ball;
int x_ball;
int y_ball;
int cord_xup1,cord_xdown1;
int cord_xup2,cord_xdown2;
int cord_xup3,cord_xdown3;
int cord_xup4,cord_xdown4;
int cord_xup5,cord_xdown5;
Boolean flag = true;
RoundRectangle2D up1,down1,up2,down2,up3,down3,up4,down4;
Font font = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,40);
Font font1 = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,20);
Font font3 = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,20);
float das[] = {10.0f};
BasicStroke color = new BasicStroke(10,BasicStroke.CAP_ROUND,BasicStroke.JOIN_BEVEL,20.0f,das,0.0f);
GradientPaint gp2 = new GradientPaint(20, 0,
Color.DARK_GRAY, 0, 10, Color.GRAY, true);
GradientPaint gp3 = new GradientPaint(30, 0,
Color.BLACK, 0, 20, Color.GREEN, true);
Toolkit kit = Toolkit.getDefaultToolkit();
//Getting the "background.jpg" image we have in the folder
Image background = kit.getImage("D:\\College\\Programs\\ProjectFLAPPY\\src\\projectflappy\\1.png");
JLabel a = new JLabel("Get Ready ! Click to Start.");
JLabel retry = new JLabel(new ImageIcon("D:\\College\\Programs\\ProjectFLAPPY\\src\\projectflappy\\unnamed.png"));
int score = 0;
//constructor
public TheGame() throws IOException
{
super("Simple Drawing");
setSize(x_width, y_height);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
jp = new DrawingPanel();
add(jp);
addMouseListener(this);
}
ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
update();
repaint();
}
};
Timer t = new Timer(50,action);
public void init()
{
x_ball = 30;
y_ball = 200;
cord_xup1 = 175; cord_xdown1 = 175;
cord_xup2 = 320; cord_xdown2 = 320;
cord_xup3 = 460; cord_xdown3 = 460;
cord_xup4 = 585; cord_xdown4 = 585;
cord_xup5 = 700; cord_xdown5 = 700;
retry.setVisible(false);
retry.setBounds(175,260,46,46);
a.setForeground(Color.YELLOW);
a.setFont(font1);
a.setVisible(true);
a.setBounds(105,200,300,100);
}
#Override
public void mouseClicked(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
a.setVisible(false);
if( flag == false)
{
t.stop();
}
else
{
t.start();
}
y_ball = y_ball - 40;
count--;
}
#Override
public void mousePressed(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseReleased(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseEntered(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseExited(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
// for drawing on the panel
class DrawingPanel extends JPanel{
private static final long serialVersionUID = 1L;
public DrawingPanel() {
setPreferredSize(new Dimension(300, 300));
setLayout(null);
init();
add(a);
add(retry);
// addMouseListener(this);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D d = (Graphics2D)g;
d.drawImage(background, -270,-30, this);
Ball = new Ellipse2D.Double(x_ball,y_ball,30,30);
d.setColor(Color.green);
d.setFont(font3);
up1 = new RoundRectangle2D.Double(cord_xup1,-5,30,175,20,20);
down1 = new RoundRectangle2D.Double(cord_xdown1,310,30,155,20,20);
up2 = new RoundRectangle2D.Double(cord_xup2,-5,30,200,20,20);
down2 = new RoundRectangle2D.Double(cord_xdown2,310,30,175,20,20);
up3 = new RoundRectangle2D.Double(cord_xup3,-5,30,230,20,20);
down3 = new RoundRectangle2D.Double(cord_xdown3,350,30,135,20,20);
up4 = new RoundRectangle2D.Double(cord_xup4,-5,30,115,20,20);
down4 = new RoundRectangle2D.Double(cord_xdown4,240,30,115,20,20);
d.setPaint(gp2);
d.setStroke(color);
d.fill(up1);
d.fill(down1);
d.fill(up2);
d.fill(down2);
d.fill(up3);
d.fill(down3);
d.fill(up4);
d.fill(down4);
d.setPaint(gp3);
d.setStroke(color);
d.fill(Ball);
d.setColor(Color.BLACK);
d.setFont(font1);
d.drawString(""+score ,200,50);
if( Ball.intersects(up1.getBounds()) || Ball.intersects(down1.getBounds()) || Ball.intersects(up2.getBounds()) || Ball.intersects(down2.getBounds()) || Ball.intersects(up3.getBounds()) || Ball.intersects(down3.getBounds()) || Ball.intersects(up4.getBounds()) || Ball.intersects(down4.getBounds()))
{
t.stop();
flag = false;
d.setColor(Color.red);
d.setFont(font);
d.drawString("Game Over : "+score ,100,250);
retry.setVisible(true);
}
retry.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent event) {
init(); //reset properties
}
//...
#Override
public void mousePressed(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseReleased(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseEntered(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseExited(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
});
}
}
public void update()
{
cord_xdown1 -= 5;
cord_xup1 -= 5;
cord_xdown2 -= 5;
cord_xup2 -= 5;
cord_xdown3 -= 5;
cord_xup3 -= 5;
cord_xdown4 -= 5;
cord_xup4 -= 5;
cord_xdown5 -= 5;
cord_xup5 -= 5;
if( cord_xup1 <=-20)
{
cord_xup1 = 500;
cord_xdown1 = 500;
}
if( cord_xup2 <=-20)
{
cord_xup2 = 500;
cord_xdown2 = 500;
}
if( cord_xup3 <=-20)
{
cord_xup3 = 500;
cord_xdown3 = 500;
}
if( cord_xup4 <=-20)
{
cord_xup4 = 500;
cord_xdown4 = 500;
}
if( cord_xup5 <=-20)
{
cord_xup5 = 500;
cord_xdown5 = 500;
}
if(count >= 0)
{
y_ball = y_ball - 7;
count--;
if( y_ball == y_height)
{
t.stop();
}
}
else
{
y_ball = y_ball + 7;
if( y_ball == y_height-70)
{
t.stop();
}
}
if(cord_xdown1 == x_ball || cord_xdown2 == x_ball || cord_xdown3 == x_ball || cord_xdown4 == x_ball)
{
score = score+1;
}
}
public static void main(String[] args) throws IOException {
new TheGame();
}
}
here retry is a JLabel in which I'm using a MouseListener to do things.
When I run,the JPanel gets completely removed from the JFrame but the new JPanel really doesn't seem to work. But only one component i.e, a.setVisble(true) works.
This is the frame when the Players gets out.
This Frames when the Player clicks on the retry button.
The reason your new panel is not showing is due to the component hierarchy being invalid. You attempt to revalidate, but you did it before adding the panel. You need to do it AFTER you add a component to an already visible container. Check out invalidate():
This method is called automatically when any layout-related information changes (e.g. setting the bounds of the component, or adding the component to a container).
So you must validate after adding the component, not before. revalidate() invalidates then revalidates the component hierarchy.
The proper way to handle this would be to revert your game back to it's original form; just change everything back to how it was. No need to create a new panel.
You could create a method, init(), which sets your game to how it should be:
//Contains the properties that will change during gameplay
void init() {
retry.setVisible(false);
a.setForeground(Color.YELLOW);
//...
}
Which you can then call when you create the board (in the constructor) and when you press retry (in the listener):
public DrawingPanel() {
setPreferredSize(new Dimension(300, 300));
setPreferredSize(new Dimension(300, 300));
setLayout(null);
init(); //sets properties
a.setFont(font1);
a.setVisible(true);
a.setBounds(105,200,300,100);
add(a);
retry.setBounds(175,260,46,46);
retry.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent event) {
init(); //reset properties
}
//...
});
add(retry);
}
You shouldn't add a listener to a component in your update() method, since update() will be called multiple times. Add it in your constructor.
If retry is a JButton, you should use an ActionListener. I wasn't sure, so I kept it as a mouse listener.
You should avoid using null layout (absolute positioning). Layout Managers position and size components using specific calculations to ensure your resulting GUI looks the same on all platforms. There are a few uses where absolute positioning is a viable option, as mentioned in the tutorials, but it's always best to prefer a Layout Manager. IMO, null layout is bad practice, and the only reason one would use it is if they didn't understand layout managers, which is a problem in itself.
To learn more about layout managers, check out the Visual Guide to Layout Managers trail. Not only does the JDK come bundled with layouts, but you can also create your own or use a third party layout, like MigLayout
EDIT:
Post Swing code to the Event Dispatch Thread. Swing event handlers (painting, listeners) are executed on the Event Dispatch Thread. To ensure the Swing code you write is in sync with the EDT, post any Swing code that isn't already being executed on the EDT to the EDT by using invokeLater or invokeAndWait.
Do not size your frame directly. Allow your frame to size based off the contents inside of it. Your DrawingPanel (the game canvas) should determine the size of the frame.
TheGame should not extend JFrame, since it's not a frame itself, rather than something contained within a frame. Having it extend JPanel would be a little easier on you (you won't be forced to create a new class to override the paint method). Although, TheGame shouldn't extend anything, it should HAVE these things (has-a relationship, not is-a). But since you're still a beginner, I don't wanna overwhelm you with a completely new design, so I considered TheGame to be the actual game canvas (where things will be draw; TheGame will extend JPanel), so you'll no longer need DrawingBoard.
As mentioned before, you should NOT add listeners (or do any task that is only needed once) in the paint method. Keep in mind that the paint method is for painting, not initializing or setting values. You should attempt to keep logic out of that method if possible.
Stay consistent. You use a JLabel for "Click to start!", yet you use drawString for "Game Over". Pick one or the other. This choice is really up to you. For this example, I chose to use drawString, since it's consistent with the rest of your rendering methods (how you paint the background, ball and obstacles)
DO NOT CREATE NEW OBJECTS IN YOUR PAINT METHOD. You're creating a ton of new objects every 50 milliseconds. This is NOT needed, and will harm performance critically. When you use the new keyword, you create a new object. Instead of creating a new object to change it (or revert it back), just change it's state.
Take advantage of Object Orientation. It'll help keep you organized, and allow you to easily manage and scale up your application. Don't shove a bunch of variables into one class to represent a ton of different things (cordx_up1, cordx_up2... it's definitely not scalable).
Look into some of the Adapter classes like MouseAdapter and KeyAdapter; they allow you to handle events without needing to declare methods you might not use.
Use access modifiers. If you aren't familiar with them, get to know them. It makes managing code a lot easier if you know where it can be used ahead of time.
Your paths point to a specific drive with a specific name. This should not be the case, since not everyone uses that drive and/or folder name. Package your images with your project, then refer to them locally.
With that said, you have a lot of studying to do.
What I did was create a Ball class and an Obstacle class, to get a little more organized. ball_x and ball_y are now inside the Ball class, as well as the gradient for it. The objects we create from this class will now have these properties (states). I create 1 ball object for your game.
Instead of creating new variables for each pole (cordx_up1), the Obstacle class has 2 RoundRectangle2D, top and bottom, which are the poles your ball is supposed to avoid. Each obstacle also has a gradient, which is used for both top and bottom. Now I can create 1 obstacle object for 2 aligned poles. You can change the starting x position of the obstacle (although I don't recommend allowing this; x should be set dynamically based on other obstacles' positions), as well as the size for top and bottom. I create 5 obstacle objects.
To keep your game labels organized (by color, message, location, font) while using drawString instead of JLabel, I created a GameLabel class.
I separated the main method into it's own class, named Launcher, which creates a JFrame and adds your game to it; all on the Event Dispatch Thread:
public class Launcher {
public static void main(String[] args) throws IOException {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new TheGame());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}
Your Game class now extends JPanel, so you can override the paint method to render the game. I created 1 Ball and a LinkedList for your obstacles. I chose a LinkedList since inserting/removing from front/end is guaranteed constant time, meaning it'll take the same amount of time to remove/insert no matter how many obstacles are in the list. When the ball passes an obstacle, I remove it from the front of the list and add it to the back. The first obstacle in the list is always the next obstacle.
I saw how some Strings were being re-used, so I created final variables for them, which you can easily change. There's also the currentlyPlaying and isAlive booleans. currentlyPlaying is set to true when the user first clicks (to start the game), and set to false once the user has clicked to restart the game (after he lost).
readyToJump is the flag I use to forward mouse events to your update() method (technically your updatePlayerPostion() method, but it's still "centeralized" within your update() method). It's best to keep all your logic in one place. readyToJump = true would have been the only statement in your listener's method if you weren't relying on calling timer.start() in it. Since update() can't be called unless the timer has started, and mouseEvent starts the timer, we must still handle starting the game in your listener's method.
#SuppressWarnings("serial")
public final class TheGame extends JPanel implements MouseListener, ActionListener {
public static final int WIDTH = 500, HEIGHT = 500;
private final String START_SCORE = "0",
START_MESSAGE = "Get Ready ! Click to Start.",
BACKGROUND_URL = "/res/flappy.png";
private boolean currentlyPlaying, readyToJump, isAlive = true;
private int score;
private Timer timer;
private Image background;
private GameLabel messageLabel, scoreLabel;
private Collection<Obstacle> obstaclesInOrder;
private LinkedList<Obstacle> obstacles;
private Ball ball;
public TheGame() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
addMouseListener(this);
timer = new Timer(50, this);
background = loadBackgroundImage();
messageLabel = new GameLabel(START_MESSAGE, 150, 240);
scoreLabel = new GameLabel(START_SCORE, 250, 60);
obstacles = new LinkedList<>();
obstacles.removeAll(obstacles);
obstaclesInOrder = Arrays.asList(new Obstacle(175, 20, 45), new Obstacle(320), new Obstacle(460), new Obstacle(585), new Obstacle(700));
obstacles.addAll(obstaclesInOrder);
ball = new Ball(30, 100);
}
#Override
public void mouseClicked(MouseEvent e) {
if (!currentlyPlaying) {
startGame();
} else if (!isAlive) {
reset();
}
readyToJump = true;
}
private void startGame() {
currentlyPlaying = true;
messageLabel.update("");
timer.start();
}
private void endGame() {
isAlive = false;
scoreLabel.update("");
messageLabel.update("Game Over. Your score was " + Integer.toString(score));
timer.stop();
}
private void reset() {
ball.reset();
for (Obstacle obstacle : obstacles)
obstacle.reset();
messageLabel.update(START_MESSAGE, 150, 240);
scoreLabel.update(START_SCORE, 250, 60);
obstacles.removeAll(obstacles);
obstacles.addAll(obstaclesInOrder);
score = 0;
isAlive = true;
currentlyPlaying = false;
repaint();
}
#Override
public void actionPerformed(ActionEvent ae) {
update();
repaint();
}
private void update() {
if (isAlive) {
updateBallPosition();
updateObstaclePositions();
if(ballOutOfBounds() || playerCollidedWithObstacle()) {
endGame();
} else if(ballPassedObstacle()) {
addToScore();
setupNextObstacle();
}
}
}
private void updateBallPosition() {
if (readyToJump) {
readyToJump = false;
ball.jump();
} else {
ball.fall();
}
}
private void updateObstaclePositions() {
for (Obstacle obstacle : obstacles) {
if (obstacle.getX() <= -obstacle.getWidth()) {
obstacle.moveToBack();
continue;
}
obstacle.moveForward();
}
}
private void addToScore() {
scoreLabel.update(Integer.toString(++score));
}
private void setupNextObstacle() {
obstacles.addLast(obstacles.removeFirst());
}
private boolean ballOutOfBounds() {
return ball.getY() >= HEIGHT || ball.getY() <= 0;
}
private boolean ballAtObstacle() {
Obstacle currentObstacle = obstacles.getFirst();
return ball.getX() + ball.getWidth() >= currentObstacle.getX() && ball.getX() <= currentObstacle.getX() + currentObstacle.getWidth();
}
private boolean ballPassedObstacle() {
Obstacle currentObstacle = obstacles.getFirst();
return ball.getX() >= (currentObstacle.getX() + currentObstacle.getWidth());
}
private boolean playerCollidedWithObstacle() {
boolean collided = false;
if(ballAtObstacle()) {
for (Obstacle obstacle : obstacles) {
RoundRectangle2D top = obstacle.getTop();
RoundRectangle2D bottom = obstacle.getBottom();
if (ball.intersects(top.getX(), top.getY(), top.getWidth(), top.getHeight()) || ball.intersects(bottom.getX(), bottom.getY(), bottom.getWidth(), bottom.getHeight())) {
collided = true;
}
}
}
return collided;
}
private Image loadBackgroundImage() {
Image background = null;
URL backgroundPath = getClass().getResource(BACKGROUND_URL);
if(backgroundPath == null) {
background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
} else {
try {
background = ImageIO.read(backgroundPath);
} catch (IOException e) {
e.printStackTrace();
}
}
return background;
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g = (Graphics2D) graphics;
g.drawImage(background, 0, 0, null);
ball.paint(g);
for (Obstacle obstacle : obstacles)
obstacle.paint(g);
scoreLabel.paint(g);
messageLabel.paint(g);
}
//...
}
loadBackgroundImage() loads and returns an image. If a problem occurs (image probably isn't there), it returns a black image.
(Most of) The game logic is in the update() method. Although it should all be in there, we can't due to a design flaw (from you using Timer which manages update(), and you start the timer in a listener, therefore we needed some logic in the listener). Your logic should be easy to read, and every execution step should be monitored and set by highest priority to lowest.
First, I check to make sure the ball hasn't collided with anything or gone out of bounds. If one of those things occur, I end the game.
If not, I check to see if the player has passed an obstacle. If the player passes an obstacle, I add to the score:
private void update() {
if (ballOutOfBounds() || playerCollidedWithObstacle()) {
endGame();
} else if (ballPassedObstacle()) {
addToScore();
setupNextObstacle();
}
updateBallPosition();
updateObstaclePositions();
}
I then finally update the positions.
To avoid constantly comparing the player's position and the obstacle's position (to see if the player has passed it), I created a boolean ballAtObstacle() method, which checks if the player is at an obstacle. Only then do I compare positions:
private boolean playerCollidedWithObstacle() {
boolean collided = false;
if (ballAtObstacle()) {
for (Obstacle obstacle : obstacles) {
RoundRectangle2D top = obstacle.getTop();
RoundRectangle2D bottom = obstacle.getBottom();
if (ball.intersects(top.getX(), top.getY(), top.getWidth(), top.getHeight()) || ball.intersects(bottom.getX(), bottom.getY(), bottom.getWidth(), bottom.getHeight())) {
collided = true;
}
}
}
return collided;
}
The ball.intersects method call is a bit messy. I did this so the shape didn't have to be specific, although you could declare the intersects method as boolean intersects(Shape shape).
Finally, I gave it a more flappy bird feel by increasing the fall speed as you fall. When you jump again, it goes back to normal. The longer it takes you to jump, the faster you'll fall. If you don't like this feature, and don't know how to remove it, let me know and I'll show you how.
The other classes involved:
GameLabel.java
public class GameLabel {
private String message;
private Font font;
private Color color;
private int x, y;
public GameLabel(String message, int x, int y, Color color, Font font) {
update(message, x, y, color, font);
}
public GameLabel(String message, int x, int y) {
this(message, x, y, Color.BLACK, new Font("Matura MT Script Capitals", Font.ROMAN_BASELINE, 20));
}
public GameLabel() {
this("", 0, 0);
}
public void setMessage(String message) {
this.message = message;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setColor(Color color) {
this.color = color;
}
public void setFont(Font font) {
this.font = font;
}
public final void update(String message, int x, int y, Color color, Font font) {
this.message = message;
this.x = x;
this.y = y;
this.color = color;
this.font = font;
}
public void update(String message, int x, int y) {
update(message, x, y, color, font);
}
public void update(String message) {
update(message, x, y);
}
public void paint(Graphics2D g) {
g.setFont(font);
g.setColor(color);
g.drawString(message, x, y);
}
public Font getFont() {
return font;
}
public Color getColor() {
return color;
}
public String getMessage() {
return message;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Obstacle.java
public class Obstacle {
public static final int DEFAULT_TOP_HEIGHT = 175;
public static final int DEFAULT_BOTTOM_HEIGHT = 175;
public static final int DEFAULT_WIDTH = 30;
public static final int DEFAULT_ARCH_WIDTH = 20;
public static final int DEFAULT_ARCH_HEIGHT = 20;
public static final int DEFAULT_TOP_INSET = -5;
public static final int DEFAULT_BOTTOM_INSET = TheGame.HEIGHT + 5;
private RoundRectangle2D top, bottom;
private BasicStroke stroke;
private GradientPaint gradient;
private int initialX, x, width;
public Obstacle(int x, int width, int topHeight, int bottomHeight) {
this.x = initialX = x;
this.width = width;
top = new RoundRectangle2D.Double(x, DEFAULT_TOP_INSET, width, topHeight, DEFAULT_ARCH_WIDTH, DEFAULT_ARCH_HEIGHT);
bottom = new RoundRectangle2D.Double(x, DEFAULT_BOTTOM_INSET-bottomHeight, width, bottomHeight, DEFAULT_ARCH_WIDTH, DEFAULT_ARCH_HEIGHT);
stroke = new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 20.0f, new float[] { 10.0f }, 0.0f);
gradient = new GradientPaint(20, 0, Color.DARK_GRAY, 0, 10, Color.GRAY, true);
}
public Obstacle(int x, int topHeight, int bottomHeight) {
this(x, DEFAULT_WIDTH, topHeight, bottomHeight);
}
public void reset() {
x = initialX;
top.setRoundRect(initialX, top.getY(), top.getWidth(), top.getHeight(), top.getArcWidth(), top.getArcHeight());
bottom.setRoundRect(initialX, bottom.getY(), bottom.getWidth(), bottom.getHeight(), bottom.getArcWidth(), bottom.getArcHeight());
}
public Obstacle(int x, int width) {
this(x, width, DEFAULT_TOP_HEIGHT, DEFAULT_BOTTOM_HEIGHT);
}
public Obstacle(int x) {
this(x, DEFAULT_WIDTH);
}
public void moveToBack() {
x = 600;
}
public void moveForward() {
x -= 5;
top.setRoundRect(x, top.getY(), top.getWidth(), top.getHeight(), top.getArcWidth(), top.getArcHeight());
bottom.setRoundRect(x, bottom.getY(), bottom.getWidth(), bottom.getHeight(), bottom.getArcWidth(), bottom.getArcHeight());
}
public RoundRectangle2D getTop() {
return top;
}
public RoundRectangle2D getBottom() {
return bottom;
}
public int getX() {
return x;
}
public int getWidth() {
return width;
}
public void paint(Graphics2D g) {
g.setPaint(gradient);
g.setStroke(stroke);
g.fill(top);
g.fill(bottom);
}
}
Ball.java
public class Ball {
public static final int DEFAULT_DROP_SPEED = 7;
private Ellipse2D ball;
private GradientPaint gradient;
private int initialY, x, y, width = 30, height = 30, dropSpeed = DEFAULT_DROP_SPEED;
public Ball(int x, int y) {
this.x = x;
this.y = initialY = y;
width = height = 30;
ball = new Ellipse2D.Double(x, y, width, height);
gradient = new GradientPaint(30, 0, Color.BLACK, 0, 20, Color.GREEN, true);
}
public void reset() {
y = initialY;
updateBall();
}
public void jump() {
dropSpeed = DEFAULT_DROP_SPEED;
y -= 40;
updateBall();
}
public void fall() {
y += dropSpeed++;
updateBall();
}
private void updateBall() {
ball.setFrame(x, y, width, height);
}
public boolean intersects(double x, double y, double w, double h) {
return ball.intersects(x, y, w, h);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return width;
}
public void paint(Graphics2D g) {
g.setPaint(gradient);
g.fill(ball);
}
public void moveForward() {
x += 7;
}
}
Reinitialize all the components by adding the initialization code to one method(resetAll) and call that method when u want to reinitailize
Below is an example:
import java.awt.*;
import java.awt.event.*;
public class ResetTest extends Frame{
Button b;
TextField tf;
Frame f;
Panel p;
public ResetTest(){
f=this; //intialize frame f to this to have access to our frame in event handler methods
resetAll(); //first time call to resetAll will initialize all the parameters of the frame
f.pack();
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent we){
System.exit(0);
}
});
this.setLayout(new FlowLayout(FlowLayout.CENTER,20,20));
this.setVisible(true);
}
/**
This method will be called on click of button to
reset all the parameters of the frame so that we
get fresh new frame, everything as it was before.
**/
public void resetAll(){
b=new Button("Reset");
p=new Panel(new FlowLayout(FlowLayout.CENTER,20,20));
p.add(b);
tf = new TextField("Edit And Reset");
p.add(tf);
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
remove(p); //remove the panel that contains all our components
resetAll(); // reset all the components
f.pack(); //refreshes the view of frame when everything is reset.
}
});
add(p);
}
}
class NewClass{
public static void main(String[] args) {
ResetTest fReset = new ResetTest();
}
}

centrally two button not looking good blackberry

I am developing one app in which I want to create two buttons. These buttons should be center aligned custom buttons with bitmap fill backgrounds. Each should also contain text that is centered in that button.
The problem is that those two buttons are not set properly. The second button is gone behind the first and its bitmap height is also decreased compared to the first button.
For both custom buttons I have the same CustomButton class.
Here is code:
CustomButtonField aboutM1 = new CustomButtonField(0,"About G1",registerbg,registerbg,Field.FOCUSABLE,0x324F85);
add(new RichTextField(Field.NON_FOCUSABLE));
// CustomButtonField2 ForgotPass = new CustomButtonField2("Forgot Password?",0x324F85);
CustomButtonField ForgotPass = new CustomButtonField(0,"Forgot Password?",registerbg,registerbg,Field.FOCUSABLE,0x324F85);
add(new RichTextField(Field.NON_FOCUSABLE));
VerticalFieldManager bottomVFM = new VerticalFieldManager(USE_ALL_WIDTH);
HorizontalFieldManager bottomHFM = new HorizontalFieldManager(FIELD_HCENTER);
bottomHFM.add(aboutM1);
bottomHFM.add(ForgotPass);
bottomVFM.add(bottomHFM);
add(bottomVFM);
Custom Button:
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.*;
public class CustomButtonField extends Field
{
Bitmap Unfocus_img, Focus_img, current_pic;
int width;
String text;
Font font;
int custColor;
CustomButtonField(int width, String text, Bitmap onFocus, Bitmap onUnfocus, long style,int custColor)
{
super(style);
Unfocus_img = onUnfocus;
Focus_img = onFocus;
current_pic = onFocus;
this.text = text;
this.width = width;
this.custColor = custColor;
}
protected void layout(int width, int height)
{
setExtent(current_pic.getWidth(), current_pic.getHeight());
}
protected void paint(Graphics graphics)
{
try
{
FontFamily fntFamily = FontFamily.forName("BBAlpha Sans");
font = fntFamily.getFont(Font.BOLD,20);
}
catch(Exception e)
{
font = Font.getDefault();
}
graphics.setFont(font);
graphics.setColor(custColor);
int xText = (getWidth() - font.getAdvance(text)) / 2;
int yText = (getHeight() - font.getHeight()) / 2;
graphics.drawBitmap(0, 0, current_pic.getWidth(), current_pic.getHeight(), current_pic , 0 , 0);
graphics.drawText(text, xText, yText);
/* graphics.drawBitmap(0, 0, current_pic.getWidth(), current_pic.getHeight(), current_pic , 0 , 0);
graphics.drawText(text, width , 7);*/
graphics.setDrawingStyle(Graphics.HCENTER | Graphics.VCENTER, true);
}
protected void onFocus(int direction)
{
super.onFocus(direction);
current_pic = Unfocus_img;
this.invalidate();
}
protected void drawFocus(Graphics graphics, boolean on)
{
}
protected void onUnfocus()
{
super.onUnfocus();
current_pic = Focus_img;
invalidate();
}
public boolean isFocusable() {
return true;
}
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
}
The code you used is correct , the only drawback is that you are centering your text according to your bitmap width & text length is more than bitmap width .
You may need to change your approach .
Have a look on Blackberry UI samples in this below URL & check for EmbossedButtonField Demo
https://github.com/blackberry/Samples-for-Java
Its a good approach to create a custom button , once we are not sure by the button label length.

Remembering where a mouse clicked? ArrayLists? HashCodes?

Sorry guys, I deleted my APPLES and CATS example :) Here's the updated version of my question!
I'm losing my sanity here. I need someone who can enlighten me. I've tried a couple of times explaining my problem here. Hopefully, this time, my question will be easier to understand.
Basically I have this frame, and there's an image displayed. There is a JList on the right, and there is another panel for JLabels at the bottom. Here's a screencap of my frame.
When I click on the image, a JOptionPane pops out, like so. And I enter my input. My JList is an ArrayList, so everything I input is added to the JList and the JPanel at the bottom.
Now, when I hover on the the part where I clicked, you noticed that the square disappeared). It only appears when I click the image, and when I hover the label at the bottom. My labels, as of now are LOLZ NOSE and INPUT HERE.
What I want to do is when I hover on the label, for example INPUT HERE, it shows the square again, featuring the part where I clicked. My problem now is when I click on NOSE, which is supposed to be showing a square on the nose part and a the name NOSE with black bg, IT IS NOT SHOWING. Also, only the last label's square is shown, disregarding the other labels' position clicked.
How do I get a label to remember the position of the click I make? People said I should use ArrayLists or HashCodes however I have no idea how to implement them. Thank you to anyone who can help.
Edit: I've already done the rectangle, btw. It's showing only for the last label inputted. Here are some of the code snippets requested!
How I'm setting the text on JLabel and updating the JList:
public void updateLabel(){
StringBuilder text = new StringBuilder(); //creates empty builder, capacity 16
for(Object s: tagModel.toArray()) //returns an array containing the elements of the tagModel
text.append(" " + s);
repaint();
hoverLabel.setText(text.toString()); //returns a String
hoverLabel.addMouseMotionListener(this);
hoverPanel.add(hoverLabel);
}
My mouseListener upon click:
#Override
public void mouseClicked(MouseEvent event) {
// TODO Auto-generated method stub
x = event.getX();
y = event.getY();
isRectPresent = true;
repaint();
input = JOptionPane.showInputDialog("Enter tag name:");
if((input != null) && !input.isEmpty()){
tagModel.addElement(input);
}
}
My mouseMotionListener upon hovering:
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
xpos = e.getX(); //gets where the mouse moved
ypos = e.getY();
//checks if the mouse is inside the bounds of the rectangle
if (xpos > x && xpos < x + 100 && ypos > y && ypos < y + 100)
isRectPresent = false;
if(e.getSource() == hoverLabel){
isRectPresent = true;
repaint();
}
repaint();
}
How I'm painting:
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(image, 0, 0, null);
if(image != null && isRectPresent){
Stroke stroke = g2.getStroke();
g2.setStroke(new BasicStroke(4));
g2.setColor(Color.WHITE);
g2.drawRect(x-50, y-50, 100, 100);
g2.setStroke(stroke);
}else{
if(xpos > x && xpos < x + 100 && ypos > y && ypos < y + 100){
g.setColor(Color.BLACK);
g.fillRect(x-50, y-50, 100, 25);
g.setColor(Color.WHITE);
g.setFont(new Font("Tahoma", Font.BOLD, 12));
g.drawString(input, x-30, y-30);
}
}
}
If you want me to add some more snippets, just tell me! :)
You should create a HashMap, say something like:
Map linkSet = new HashMap();
And whenever you click on the drawing and create a label, add the JLabel and the point on the image to the set using the put method with the JLabel as the key and the Point as the value. Then in the JLabel's MouseMotionListener, use your label as a key and obtain the corresponding point from the set using the map's get(...) method.
edit:
Corrected as per alicedimarco's comment. Again, thanks!
edit 2
I think you want again to use a Map. If you have a Map, you can have it retrieve the Point of interest from the JLabel's or the JList's String, and then pass this Point to the class that's drawing the image and let it use the Point to draw a rectangle. For instance you could give the image drawing class a Point field called displayPoint, and a method called setDisplayPoint(Point p). It can be as simple as this:
public void setDisplayPoint(Point p) {
this.displayPoint = p;
repaint();
}
and assuming that the object of interest is centered at that point, use the displayPoint in the paintComponent method:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// draw image
if (img != null) {
g.drawImage(img, X_SHIFT, Y_SHIFT, null);
}
// if displayPoint not null, draw the surrounding rectangle
if (displayPoint != null) {
g.setColor(RECT_COLOR);
int x = displayPoint.x - RECT_WIDTH / 2;
int y = displayPoint.y - RECT_WIDTH / 2;
int width = RECT_WIDTH;
int height = RECT_WIDTH;
g.drawRect(x, y, width, height);
}
}
edit 3:
To get mouse clicks, it's quite easy, simply add a MouseListener to the component that holds the image:
// !! added
imgRect.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
imgMousePressed(e);
}
});
And in your code that is called from this mouse listener, use a JOptionPane to get the user's choice of tag name, and add the resulting String to both the listDataModel so that it is seen in the JList and also in the stringPointMap together with the Point obtained from the MouseEvent so that you can map the String to the Point and be able to retrieve it:
// !! added
private void imgMousePressed(MouseEvent e) {
String result = JOptionPane.showInputDialog(this,
"Please enter name for this point on image:");
if (result != null) {
stringPointMap.put(result, e.getPoint());
listDataModel.addElement(result);
}
}
That's it.
Then putting it all together:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ImageRectMain extends JPanel {
private ImageRect imgRect;
private DefaultListModel listDataModel = new DefaultListModel();
private JList list = new JList(listDataModel);
private Map<String, Point> stringPointMap = new HashMap<String, Point>();
public ImageRectMain() {
String nose = "Nose";
String ear = "Ear";
String rightEye = "Right Eye";
String leftEye = "Left Eye";
listDataModel.addElement(ear);
listDataModel.addElement(nose);
listDataModel.addElement(rightEye);
listDataModel.addElement(leftEye);
stringPointMap.put(nose, new Point(480, 500));
stringPointMap.put(ear, new Point(270, 230));
stringPointMap.put(rightEye, new Point(380, 390));
stringPointMap.put(leftEye, new Point(662, 440));
MouseAdapter listMouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
listMouseMoved(e);
}
#Override
public void mouseExited(MouseEvent e) {
listMouseExited(e);
}
};
list.addMouseMotionListener(listMouseAdapter);
list.addMouseListener(listMouseAdapter);
try {
imgRect = new ImageRect();
// !! added
imgRect.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
imgMousePressed(e);
}
});
JPanel eastPanel = new JPanel();
eastPanel.setLayout(new BoxLayout(eastPanel, BoxLayout.PAGE_AXIS));
eastPanel.add(new JLabel("You have tagged the following:"));
eastPanel.add(new JScrollPane(list));
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
setLayout(new BorderLayout());
add(imgRect, BorderLayout.CENTER);
add(eastPanel, BorderLayout.EAST);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// !! added
private void imgMousePressed(MouseEvent e) {
String result = JOptionPane.showInputDialog(this,
"Please enter name for this point on image:");
if (result != null) {
stringPointMap.put(result, e.getPoint());
listDataModel.addElement(result);
}
}
private void listMouseExited(MouseEvent e) {
imgRect.setDisplayPoint(null);
}
private void listMouseMoved(MouseEvent e) {
int index = list.locationToIndex(e.getPoint());
Object value = listDataModel.get(index);
if (value != null) {
Point point = stringPointMap.get(value.toString());
if (point != null) {
imgRect.setDisplayPoint(point);
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("ImageRectMain");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageRectMain());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ImageRect extends JPanel {
public static final String IMAGE_PATH = "http://i.stack.imgur.com/7oNzg.jpg";
private static final int DEFAULT_W = 687;
private static final int DEFAULT_H = 636;
private static final int X_SHIFT = -6;
private static final int Y_SHIFT = -26;
private static final Color RECT_COLOR = Color.pink;
private static final int RECT_WIDTH = 40;
private BufferedImage img;
private Point displayPoint = null;
public ImageRect() throws MalformedURLException, IOException {
img = ImageIO.read(new URL(IMAGE_PATH));
}
public void setDisplayPoint(Point p) {
this.displayPoint = p;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, X_SHIFT, Y_SHIFT, null);
}
if (displayPoint != null) {
g.setColor(RECT_COLOR);
int x = displayPoint.x - RECT_WIDTH / 2;
int y = displayPoint.y - RECT_WIDTH / 2;
int width = RECT_WIDTH;
int height = RECT_WIDTH;
g.drawRect(x, y, width, height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(DEFAULT_W, DEFAULT_H);
}
}
One nice feature of a JList is that you can story any object in it. You're not limited to strings. When objects are stored in JLists, swing will call the object's toString() method, and display it in the list.
Knowing this, you can now write your own class that stores the name of your selection label and the coordinates of the box. This object's toString() method will return the name of the label, which will make the right thing appear in the JList.
Then, in the selection event handler for the JList, you can get your custom object out, and retrieve the box coordinates stored in it, and draw them on the screen. No need to fuss with other containers (although knowing how to use them is a good thing to).
Ok, Create a class like this...
public class MyLabel {
private int x;
private int y;
private String text;
public MyLabel (String text, int x, int y) {
this.text = text;
// assign x and y too...
}
#Override
public String toString() {
return label;
}
public int getX() {
return x;
}
// similar function to getY()
}
The class above overrides toString(), so when you put it in a JList, it will use the call to toString() to determine what to display, and since this toString implementation returns the label name you'll see that in the list.
And add those into your JList instead of Strings. Then at some point in your code you'll do something like this...
// this is pseudocode, method names may not be correct...
MyLabel ml = (MyLabel)jList.getSelectedItem();
int x = ml.getX();
int y = ml.getY();
// draw the box...
Hope that helps.

How do I add a separator to a JComboBox in Java?

I have a JComboBox and would like to have a separator in the list of elements. How do I do this in Java?
A sample scenario where this would come in handy is when making a combobox for font-family-selection; similar to the font-family-selection-control in Word and Excel. In this case I would like to show the most-used-fonts at the top, then a separator and finally all font-families below the separator in alphabetical order.
Can anyone help me with how to do this or is this not possible in Java?
There is a pretty short tutorial with an example that shows how to use a custom ListCellRenderer on java2s
http://www.java2s.com/Code/Java/Swing-Components/BlockComboBoxExample.htm
Basically it involves inserting a known placeholder in your list model and when you detect the placeholder in the ListCellRenderer you return an instance of 'new JSeparator(JSeparator.HORIZONTAL)'
By the time I wrote and tested the code below, you probably got lot of better answers...
I don't mind as I enjoyed the experiment/learning (still a bit green on the Swing front).
[EDIT] Three years later, I am a bit less green, and I took in account the valid remarks of bobndrew. I have no problem with the key navigation that just works (perhaps it was a JVM version issue?). I improved the renderer to show highlight, though. And I use a better demo code. The accepted answer is probably better (more standard), mine is probably more flexible if you want a custom separator...
The base idea is to use a renderer for the items of the combo box. For most items, it is a simple JLabel with the text of the item. For the last recent/most used item, I decorate the JLabel with a custom border drawing a line on its bottom.
import java.awt.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBox extends JComboBox
{
private int m_lastFirstPartIndex;
public TwoPartsComboBox(String[] itemsFirstPart, String[] itemsSecondPart)
{
super(itemsFirstPart);
m_lastFirstPartIndex = itemsFirstPart.length - 1;
for (int i = 0; i < itemsSecondPart.length; i++)
{
insertItemAt(itemsSecondPart[i], i);
}
setRenderer(new JLRenderer());
}
protected class JLRenderer extends JLabel implements ListCellRenderer
{
private JLabel m_lastFirstPart;
public JLRenderer()
{
m_lastFirstPart = new JLabel();
m_lastFirstPart.setBorder(new BottomLineBorder());
// m_lastFirstPart.setBorder(new BottomLineBorder(10, Color.BLUE));
}
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value == null)
{
value = "Select an option";
}
JLabel label = this;
if (index == m_lastFirstPartIndex)
{
label = m_lastFirstPart;
}
label.setText(value.toString());
label.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
label.setOpaque(true);
return label;
}
}
}
Separator class, can be thick, with custom color, etc.
import java.awt.*;
import javax.swing.border.AbstractBorder;
/**
* Draws a line at the bottom only.
* Useful for making a separator in combo box, for example.
*/
#SuppressWarnings("serial")
class BottomLineBorder extends AbstractBorder
{
private int m_thickness;
private Color m_color;
BottomLineBorder()
{
this(1, Color.BLACK);
}
BottomLineBorder(Color color)
{
this(1, color);
}
BottomLineBorder(int thickness, Color color)
{
m_thickness = thickness;
m_color = color;
}
#Override
public void paintBorder(Component c, Graphics g,
int x, int y, int width, int height)
{
Graphics copy = g.create();
if (copy != null)
{
try
{
copy.translate(x, y);
copy.setColor(m_color);
copy.fillRect(0, height - m_thickness, width - 1, height - 1);
}
finally
{
copy.dispose();
}
}
}
#Override
public boolean isBorderOpaque()
{
return true;
}
#Override
public Insets getBorderInsets(Component c)
{
return new Insets(0, 0, m_thickness, 0);
}
#Override
public Insets getBorderInsets(Component c, Insets i)
{
i.left = i.top = i.right = 0;
i.bottom = m_thickness;
return i;
}
}
Test class:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBoxDemo extends JFrame
{
private TwoPartsComboBox m_combo;
public TwoPartsComboBoxDemo()
{
Container cont = getContentPane();
cont.setLayout(new FlowLayout());
cont.add(new JLabel("Data: ")) ;
String[] itemsRecent = new String[] { "ichi", "ni", "san" };
String[] itemsOther = new String[] { "one", "two", "three" };
m_combo = new TwoPartsComboBox(itemsRecent, itemsOther);
m_combo.setSelectedIndex(-1);
cont.add(m_combo);
m_combo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
String si = (String) m_combo.getSelectedItem();
System.out.println(si == null ? "No item selected" : si.toString());
}
});
// Reference, to check we have similar behavior to standard combo
JComboBox combo = new JComboBox(itemsRecent);
cont.add(combo);
}
/**
* Start the demo.
*
* #param args the command line arguments
*/
public static void main(String[] args)
{
// turn bold fonts off in metal
UIManager.put("swing.boldMetal", Boolean.FALSE);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JFrame demoFrame = new TwoPartsComboBoxDemo();
demoFrame.setTitle("Test GUI");
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demoFrame.setSize(400, 100);
demoFrame.setVisible(true);
}
});
}
}
You can use a custom ListCellRenderer which would draw the separator items differently. See docs and a small tutorial.
Try adding this renderer. Just supply a list of index values that you want the separator to be above.
private class SeperatorComboRenderer extends DefaultListCellRenderer
{
private final float SEPARATOR_THICKNESS = 1.0f;
private final float SPACE_TOP = 2.0f;
private final float SPACE_BOTTOM = 2.0f;
private final Color SEPARATOR_COLOR = Color.DARK_GRAY;
private final List<Integer> marks;
private boolean mark;
private boolean top;
public SeperatorComboRenderer(List<Integer> marks)
{
this.marks = marks;
}
#Override
public Component getListCellRendererComponent(JList list, Object object, int index, boolean isSelected, boolean hasFocus)
{
super.getListCellRendererComponent(list, object, index, isSelected, hasFocus);
top = false;
mark = false;
marks.forEach((idx) ->
{
if(index - 1 == idx)
top = true;
if(index == idx)
mark = true;
});
return this;
}
#Override
protected void paintComponent(Graphics g)
{
if(mark)
g.translate(0, (int)(SEPARATOR_THICKNESS + SPACE_BOTTOM));
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g);
if(mark)
{
g2.setColor(SEPARATOR_COLOR);
g2.setStroke(new BasicStroke(SEPARATOR_THICKNESS));
g2.drawLine(0, 0, getWidth(), 0);
}
}
#Override
public Dimension getPreferredSize()
{
Dimension pf = super.getPreferredSize();
double height = pf.getHeight();
if(top) height += SPACE_TOP;
else if(mark) height += SEPARATOR_THICKNESS + SPACE_BOTTOM;
return new Dimension((int)pf.getWidth(), (int)height);
}
}

Categories

Resources