Java SWT update canvas from dialog - java

I am working on a GUI application with Java and SWT.I have createad a main window with a canvas in which I load an image (well it's a PDF page converted to image).I have attached a PaintListener to the canvas and when i drag the mouse onte the canvas I am able to draw a rectangle.
When i release the left button on mouse, I want a dialog window to came out to "fine setting" the rectangle area, so I made a dialog with 4 spinner (x, y, width and height).
Now I want to click on the spinners and the canvas redraw the changed rectangle area.I tried to pass the canvas object to the dialog window and attach a second paint listener to it (So I have one paintlistener from MainWindow.java and another from Dialog.java), but it's no working.
The problem is that I have multiple rectangle drawn on the canvas, and if I call canvas.redraw() from dialog window, the rectangles already drawn on canvas "disappeared".What is the best practise in such situation?
I thinks to put the dialog window "in the toolbar", that is put the 4 spinners in the toolbar (or another area in the main window) so I have only one paint listener and get rid of the dialog, but I prefer the dialog 'cause it is impratical to drag a rectangle onto canvas and then move the mouse to click on toolbar.
Thanks in advance, Mauro

Your main window should implement custom listener for example:
public interface PreferencesListener {
public void updatePreference(PreferencesEvent e)
}
Then create a manager which control updates:
public class PreferencesController {
private static PreferencesController instance = new PreferencesController();
private List<PreferencesListener> preferencesListeners;
private PreferencesController() {
preferencesListeners = new ArrayList<PreferencesListener>();
}
public static PreferencesController getInstance() {
return instance;
}
public void addPreferenceListener(PreferencesListener listener) {
preferencesListeners.add(listener);
}
public void notify(int x, int y, int w, int h) {
PreferencesEvent e = new PreferencesEvent(x, y, w, h);
for(final PreferencesListener listener: preferencesListeners) {
listener.updatePreference(e);
}
}
}
At the moment of creation main window register it in controller:
PreferencesController.getInstance().addPreferenceListener(this);
When you change value in dialog trigger notify:
PreferencesController.getInstance().notify(x,y,w,h);
And then finally implement updatePreference(PreferencesEvent e) from PreferencesListener. From this moment whenever values x y w h are changed your window will be notified. From my point of view it's a good solution because your window and dialog don't even know each other exists, and you could easily add another component that react to preference change.

Related

Designing a simple event-driven GUI

I am creating a simple event-driven GUI for a video game I am making with LibGDX. It only needs to support buttons (rectangular) with a single act() function called when they are clicked. I would appreciate some advice on structuring because the solution I've thought of so far seems to be far from ideal.
My current implementation involves all buttons extending a Button class. Each button has a Rectangle with its bounds and an abstract act() method.
Each game screen (e.g. main menu, character select, pause menu, the in-game screen) has a HashMap of Buttons. When clicked, the game screen iterates through everything in the HashMap, and calls act() on any button that was clicked.
The problem I'm having is that Buttons have to have their act() overridden from their superclass in order to perform their action, and that the Buttons aren't a member of the Screen class which contains all the game code. I am subclassing Button for each button in the game. My main menu alone has a ButtonPlay, ButtonMapDesigner, ButtonMute, ButtonQuit, etc. This is going to get messy fast, but I can't think of any better way to do it while keeping a separate act() method for each button.
Since my mute button isn't a part of the main menu screen and can't access game logic, it's act() is nothing more than mainMenuScreen.mute();. So effectively, for every button in my game, I have to create a class class that does nothing more than <currentGameScreen>.doThisAction();, since the code to actually do stuff must be in the game screen class.
I considered having a big if/then to check the coordinates of each click and call the appropriate action if necessary. For example,
if (clickWithinTheseCoords)
beginGame();
else if(clickWithinTheseOtherCoords)
muteGame();
...
However, I need to be able to add/remove buttons on the fly. When a unit is clicked from the game screen, a button to move it needs to appear, and then disappear when the unit is actually moved. With a HashMap, I can just map.add("buttonMove", new ButtonMove()) and map.remove("buttonMove") in the code called when a unit is clicked or moved. With the if/else method, I won't need a separate class for every button, but I would need to keep track of whether each clickable area tested is visible and clickable by the user at this point in the game, which seems like an even bigger headache that what I have right now.
I would provide a runnable to all the buttons which u will run in the act method. To give u a simple example.
private final Map<String, Button> buttons = new HashMap<>();
public void initialiseSomeExampleButtons() {
buttons.put("changeScreenBytton", new Button(new Runnable() {
#Override
public void run() {
//Put a change screen action here.
}
}));
buttons.put("muteButton", new Button(new Runnable() {
#Override
public void run() {
//Do a mute Action here
}
}));
}
public class Button {
//Your other stuff like rectangle
private final Runnable runnable;
protected Button(Runnable runnable) {
this.runnable = runnable;
}
public void act() {
runnable.run();
}
}
You keep track of your buttons via the map and just need to pass a runnable action to every button in the constructor. I intentionally skipped some code so that you can try yourself. If you have any questions, let me know.
Sneh's response reminded me of a fairly major oversight - instead of having to create a separate class for every button, I could use anonymous inner classes whenever I created a button, specifying its coordinates and act() method every time. I explored lambda syntax as a possible shorter method to do this, but ran into limitations with it. I ended up with a flexible solution, but ended up reducing it a bit further to fit my needs. Both ways are presented below.
Each game screen in my game is subclassed from a MyScreen class, which extends LibGDX's Screen but adds universal features like updating the viewport on resize, having a HashMap of Buttons, etc. I added to the MyScreen class a buttonPressed() method, which takes in as its one parameter an enum. I have ButtonValues enum which contains all the possible buttons (such as MAINMENU_PLAY, MAINMENU_MAPDESIGNER, etc.). In each game screen, buttonPressed() is overriden and a switch is used to perform the correct action:
public void buttonPressed(ButtonValues b) {
switch(b) {
case MAINMENU_PLAY:
beginGame();
case MAINMENU_MAPDESIGNER:
switchToMapDesigner();
}
}
The other solution has the button store a lambda expression so that it can perform actions on its own, instead of requiring buttonPressed() to act as an intermediary that performs the correct action based on what button was pressed.
To add a button, it is created with its coordinates and type (enum), and added to the HashMap of buttons:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex, ButtonValues.MAINMENU_PLAY);
buttons.put("buttonPlay", b);
To remove it, just buttons.remove("buttonPlay"). and it'll disappear from the screen and be forgotten by the game.
The arguments are the game screen which owns it (so the button can call buttonPressed() on the game screen), a Rectangle with its coordinates, its texture (used to draw it), and its enum value.
And here's the Button class:
public class Button {
public Rectangle r;
public TextureRegion image;
private MyScreen screen;
private ButtonValues b;
public Button(MyScreen screen, Rectangle r, TextureRegion image, ButtonValues b) {
this.screen = screen;
this.r = r;
this.image = image;
this.b = b;
}
public void act() {
screen.buttonPressed(b);
}
public boolean isClicked(float x, float y) {
return x > r.x && y > r.y && x < r.x + r.width && y < r.y + r.height;
}
}
isClicked() just takes in an (x, y) and checks whether that point is contained within the button. On mouse click, I iterate through all the buttons and call act() if a button isClicked.
The second way I did it was similar, but with a lambda expression instead of the ButtonValues enum. The Button class is similar, but with these changes (it's a lot simpler than it sounds):
The field ButtonValues b is replaced with Runnable r, and this is removed from the constructor. Added is a setAction() method which takes in a Runnable and sets r to the Runnable passed to it. The act() method is just r.run(). Example:
public class Button {
[Rectangle, Texture, Screen]
Runnable r;
public Button(screen, rectangle, texture) {...}
public void setAction(Runnable r) { this.r = r; }
public void act() { r.run(); }
}
To create a button, I do the following:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex);
b.setAction(() -> b.screen.doSomething());
buttons.put("buttonPlay", b);
First, a button is created with its containing game screen class, its bounding box, and its texture. Then, in the second command, I set its action - in this case, b.screen.doSomething();. This can't be passed to the constructor, because b and b.screen don't exist at that point. setAction() takes a Runnable and sets it as that Button's Runnable that is called when act() is called. However, Runnables can be created with lambda syntax, so you don't need to create an anonymous Runnable class and can just pass in the function it performs.
This method allows much more flexibility, but with one caveat. The screen field in Button holds a MyScreen, the base screen class from which all of my game screens are extended. The Button's function can only use methods that are part of the MyScreen class (which is why I made buttonPressed() in MyScreen and then realized I could just scrap the lambda expressions completely). The obvious solution is to cast the screen field, but for me it wasn't worth the extra code when I could just use the buttonPressed() method.
If I had a beginGame() method in my MainMenuScreen class (which extends MyScreen), the lambda expression passed to the button would need to involve a cast to MainMenuScreen:
b.setAction(() -> ((MainMenuScreen) b.screen).beginGame());
Unfortunately, even wildcard syntax doesn't help here.
And finally, for completeness, the code in the game loop to operate the buttons:
public abstract class MyScreen implements Screen {
protected HashMap<String, Button> buttons; // initialize this in the constructor
// this is called in every game screen's game loop
protected void handleInput() {
if (Gdx.input.justTouched()) {
Vector2 touchCoords = new Vector2(Gdx.input.getX(), Gdx.input.getY());
g.viewport.unproject(touchCoords);
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
if (b.getValue().isClicked(touchCoords.x, touchCoords.y))
b.getValue().act();
}
}
}
}
And to draw them, located in a helper class:
public void drawButtons(HashMap<String, Button> buttons) {
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
sb.draw(b.getValue().image, b.getValue().r.x, b.getValue().r.y);
}
}

Drag and Drop changes in canvas only visible after minimization

public void dropAccept(final DropTargetEvent event)
{
if (TextTransfer.getInstance().isSupportedType(event.currentDataType))
{
final String d=(String)TextTransfer.getInstance().nativeToJava(event.CurrentDataType);
GC gc = new(text);
//text is the name assigned to the Canvas
text.addPaintListener(new PaintListener()
{
public void paintControl(PaintEvent e)
{
int x= event.x- shell.getBounds().x - text.getBounds().x;
int y=event.y - shell.getBounds().y - text.getBounds().y;
e.gc.drawString(d, x, y);
}
}); } }
This code snippet is part of a larger class that implements drag drop of text onto a canvas. The problem is that, the actual dropping of text is not seen on the canvas after I drop it but only after I minimize the shell and then maximize it again. Can anyone please tell me how I can make drop actions immediately visible by modifying this code?
You have not done anything to cause the control to be redrawn. Call
text.redraw();
to request that the control is redrawn (by calling the paint listener).
Note: If you add paint listeners on every drop you are going to end up with lots of listeners registered.

Need help in using scene2d button in libgdx

I am new to libGDX. I am trying to create a custom button by extending com.badlogic.gdx.scenes.scene2d.ui.Button.
I want all the button related logic in this class. But I am not getting how to make the click work. I read many tutorials regarding adding Event Listeners but nothing is working.
public class RestartButton extends Button {
public RestartButton(ButtonStyle style) {
super(style);
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(TextureProvider.getInstance().getRestart(), 175, 100);
}
}
And i am trying to add my button in the screen(i.e in show method) like this
RestartButton restartButton;
restartButton=new RestartButton(new ButtonStyle());
Stage stage;
stage.addActor(restartButton);
I am able to see my button on the screen. Now what i want to do is add some code which gets invoked when button is clicked or touched. Can someone please help ?
restartButton = new RestartButton(new ButtonStyle());
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
System.out.println("Restart clicked!");
}
});
stage.addActor(restartButton);
It does not work because you need to setBounds for your Button. If you wanted to draw the button in the position (175, 100) you could just create a Button directly from Button Class and call
button.setBounds(x, y, width, height);
Then adding the listener will work because now your button will actually have a position and an area in the stage. If you still need to extend the button class for your own reasons, you can set the bounds in the extended class ditectly or you can pass another argument in your RestartButton class. Similar to:
public RestartButton(ButtonStyle style, Vector2 position, Vector2 size) {
super(style);
this.setBounds(position.x, position.y, size.x, size.y);
}
Then the button will automatically be drawn to the position you want without the need of overriding draw method. add the listener by using this.addListener(yourListener);
Hope it helps.

How to force repaint of a glass pane?

I have a Swing app with a glass pane over a map.
It paints dots at certain positions. When I click somewhere on the map, and the glass pane receives the message CONTROLLER_NEW_POLYGON_MARK I
want do display an additional dot at the position specified in the event data (see MyGlassPane.propertyChange).
The glass pane class is called MyGlassPane. Using the debugger I validated that addPointToMark is actually called in propertyChange.
But no additional dots appear on the screen.
How can I change the code so that PointSetMarkingGlassPane.paintComponent is called whenever an event (IEventBus.CONTROLLER_NEW_POLYGON_MARK) is fired?
public class PointSetMarkingGlassPane extends JComponent implements IGlassPane {
private final ILatLongToScreenCoordinatesConverter latLongToScreenCoordinatesConverter;
private final List<Point.Double> pointsToMark = new LinkedList<Point.Double>();
public PointSetMarkingGlassPane(final ILatLongToScreenCoordinatesConverter aConverter) {
this.latLongToScreenCoordinatesConverter = aConverter;
}
protected void addPointToMark(final Point.Double aPoint)
{
if (aPoint != null)
{
pointsToMark.add(aPoint);
}
}
#Override
protected void paintComponent(final Graphics aGraphics) {
for (final Point.Double pointToMark : pointsToMark)
{
final Point positionInScreenCoords = latLongToScreenCoordinatesConverter.getScreenCoordinates(pointToMark);
drawCircle(aGraphics, positionInScreenCoords, Color.red);
}
}
private void drawCircle(Graphics g, Point point, Color color) {
g.setColor(color);
g.fillOval(point.x, point.y, 10, 10);
}
}
public class MyGlassPane extends PointSetMarkingGlassPane implements PropertyChangeListener {
public MyGlassPane(ILatLongToScreenCoordinatesConverter aConverter) {
super(aConverter);
addPointToMark(DemoGlassPane.ARTYOM);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (IEventBus.CONTROLLER_NEW_POLYGON_MARK.equals(evt.getPropertyName()))
{
addPointToMark((Point.Double)evt.getNewValue());
invalidate();
}
}
}
As I think invalidate() only flags your component to check sizes and layout. You should call repaint() to repaint your pane.
Also I am wondering why you use propertyChangeListener for mouse clicks. I would prefer just simple mouse listener + MouseAdapter and MouseEvent x, y, buttons state.
invalidate() probably won't help you, as it flags a component for layout changes, not painting changes. Why not call repaint() instead?
For better performance, you could call the repaint method which takes a Rectangle (or four ints representing a rectangle), so that only the newly added point is repainted; I would suggest changing the return type of addPointToMark from void to java.awt.Point, and have it return the result of latLongToScreenCoordinatesConverter.getScreenCoordinates, so MyGlassPane can derive a rectangle from that Point which can then be passed to a repaint method.

Setting multiple icons on JButton

I have a JButton which I have set a custom icon on. Now I want it to display another icon ontop of the one that is already displayed when I drag my mouse cursor over it but I can't figure out how to do it because if I use button.setIcon(icon); it will replace the icon that already is displayed. How would I do this in an as easy way as possible?
I have a JButton which I have set a custom icon on. Now I want it to
display another icon ontop of the one that is already displayed when I
drag my mouse cursor over it but I can't figure out how to do it
because if I use button.setIcon(icon); it will replace the icon that
already is displayed. How would I do this in an as easy way as
possible
I think thats about JButton.setRolloverIcon(myIcon);
JButton has implemented those methods in API
JButton.setIcon(myIcon);
JButton.setRolloverIcon(myIcon);
JButton.setPressedIcon(myIcon);
JButton.setDisabledIcon(myIcon);
for example
If your icons are already transparent you can easily implement your own Icon to combine the two -
public class CombineIcon implements Icon {
private Icon top;
private Icon bottom;
public CombineIcon(Icon top, Icon bottom) {
this.top = top;
this.bottom = bottom;
}
public int getIconHeight() {
return Math.max(top.getIconHeight(), bottom.getIconHeight());
}
public int getIconWidth() {
return Math.max(top.getIconWidth(), bottom.getIconWidth());
}
public void paintIcon(Component c, Graphics g, int x, int y) {
bottom.paintIcon(c, g, x, y);
top.paintIcon(c, g, x, y);
}
}
You use setRolloverIcon(icon) to specify the icon you want to show when the mouse is over the button.
Create a second version of that button icon which contains the overlay. On mouse over switch to the image with the overlay.
Another approach could be to combine the icon with its overlay to a new icon in memory and place it as an icon on the button. This might be a good approach if your icons are frequently changing. If that's not the case I would definitely use the first approach.
I find this pretty easy.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class CombinedIconButton {
public static BufferedImage getCombinedImage(BufferedImage i1, BufferedImage i2) {
if (i1.getHeight() != i2.getHeight()
|| i1.getWidth() != i2.getWidth()) {
throw new IllegalArgumentException("Images are not the same size!");
}
BufferedImage bi = new BufferedImage(
i1.getHeight(),
i1.getWidth(),
BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.drawImage(i1,0,0,null);
g.drawImage(i2,0,0,null);
g.dispose();
return bi;
}
public static void main(String[] args) throws Exception {
URL url1 = new URL("http://i.stack.imgur.com/gJmeJ.png"); // blue circle
URL url2 = new URL("http://i.stack.imgur.com/5v2TX.png"); // red triangle
final BufferedImage bi1 = ImageIO.read(url1);
final BufferedImage bi2 = ImageIO.read(url2);
final BufferedImage biC = getCombinedImage(bi1,bi2);
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new BorderLayout());
JToggleButton b = new JToggleButton();
b.setIcon(new ImageIcon(bi1));
b.setRolloverIcon(new ImageIcon(biC));
b.setSelectedIcon(new ImageIcon(bi2));
gui.add(b);
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Images borrowed from this answer.
One way to do that is:
Create an icon which you want to see when the pointer is hovered on top of the button by some image editing tool. And set that image once mousehover event occurs.
p.s. use any pic editing tool and you can easily create a overlay image.
I also saw now that there is a concept of roll over icon in AbsractButton class. You can use that as well.

Categories