Magic movement in game tile by tile - java

I have a 2D tile game and my hero can use magic(in this case fire), and the goal is to make the fireball move tile by tile until it finds either a wall or an enemy and to make the game stop while the fire is moving. I already have the fire moving and stopping if there is a wall or an enemy(and damaging the enemy). The problem is i can't seem to make the game show the fireball change from tile to tile, which means when i launch the fireball the game automatically shows me the fireball in its last position before collision, and then it disappears from the tiles. Anyone got any ideas as to what I am doing wrong or what i should do to make the game update the fire tile by tile?
(Btw i thought it might have something to do with my observer but I've tried thread.sleep and wait() and it isn't quite working, maybe I am doing it the wrong way).Thank you for your help and if you guys need any code just ask.
public class ImageMatrixGUI extends Observable {
private static final ImageMatrixGUI INSTANCE = new ImageMatrixGUI();
private final String IMAGE_DIR = "images";
private final int SQUARE_SIZE;
private final int N_SQUARES_WIDTH;
private final int N_SQUARES_HEIGHT;
private JFrame frame;
private JPanel panel;
private JPanel info;
private Map<String, ImageIcon> imageDB = new HashMap<String, ImageIcon>();
private List<ImageTile> images = new ArrayList<ImageTile>();
private List<ImageTile> statusImages = new ArrayList<ImageTile>();
private int lastKeyPressed;
private boolean keyPressed;
private ImageMatrixGUI() {
SQUARE_SIZE = 48;
N_SQUARES_WIDTH = 10;
N_SQUARES_HEIGHT = 10;
init();
}
public static ImageMatrixGUI getInstance() {
return INSTANCE;
}
public void setName(final String name) {
frame.setTitle(name);
}
private void init() {
frame = new JFrame();
panel = new RogueWindow();
info = new InfoWindow();
panel.setPreferredSize(new Dimension(N_SQUARES_WIDTH * SQUARE_SIZE, N_SQUARES_HEIGHT * SQUARE_SIZE));
info.setPreferredSize(new Dimension(N_SQUARES_WIDTH * SQUARE_SIZE, SQUARE_SIZE));
info.setBackground(Color.BLACK);
frame.add(panel);
frame.add(info, BorderLayout.NORTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initImages();
new KeyWatcher().start();
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
lastKeyPressed = e.getKeyCode();
keyPressed = true;
releaseObserver();
}
});
}
synchronized void releaseObserver() {
notify();
}
synchronized void waitForKey() throws InterruptedException {
while (!keyPressed) {
wait();
}
setChanged();
notifyObservers(lastKeyPressed);
keyPressed = false;
}
private void initImages() {
File dir = new File(IMAGE_DIR);
for (File f : dir.listFiles()) {
assert (f.getName().lastIndexOf('.') != -1);
imageDB.put(f.getName().substring(0, f.getName().lastIndexOf('.')),
new ImageIcon(IMAGE_DIR + "/" + f.getName()));
}
}
public void go() {
frame.setVisible(true);
}
public void newImages(final List<ImageTile> newImages) {
synchronized (images) { // Added 16-Mar-2016
if (newImages == null)
return;
if (newImages.size() == 0)
return;
for (ImageTile i : newImages) {
if (!imageDB.containsKey(i.getName())) {
throw new IllegalArgumentException("No such image in DB " + i.getName());
}
}
images.addAll(newImages);
}
}
public void removeImage(final ImageTile image) {
synchronized (images) {
images.remove(image);
}
}
public void addImage(final ImageTile image) {
synchronized (images) {
images.add(image);
}
}
public void clearImages() {
synchronized (images) {
images.clear();
}
public void newStatusImages(final List<ImageTile> newImages) {
synchronized (statusImages) {
if (newImages == null)
return;
if (newImages.size() == 0)
return;
for (ImageTile i : newImages) {
if (!imageDB.containsKey(i.getName())) {
throw new IllegalArgumentException("No such image in DB " + i.getName());
}
}
statusImages.addAll(newImages);
}
}
public void removeStatusImage(final ImageTile image) {
synchronized (statusImages) {
statusImages.remove(image);
}
public void addStatusImage(final ImageTile image) {
synchronized (statusImages) {
statusImages.add(image);
}
}
public void clearStatus() {
synchronized (statusImages) {
statusImages.clear();
}
}
#SuppressWarnings("serial")
private class RogueWindow extends JPanel {
#Override
public void paintComponent(Graphics g) {
// System.out.println("Thread " + Thread.currentThread() + "
// repainting");
synchronized (images) {
for (ImageTile i : images) {
g.drawImage(imageDB.get(i.getName()).getImage(), i.getPosition().getX() * SQUARE_SIZE,
i.getPosition().getY() * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE, frame);
}
}
}
}
#SuppressWarnings("serial")
private class InfoWindow extends JPanel {
#Override
public void paintComponent(Graphics g) {
synchronized (statusImages) {
for (ImageTile i : statusImages)
g.drawImage(imageDB.get(i.getName()).getImage(), i.getPosition().getX() * SQUARE_SIZE, 0,
SQUARE_SIZE, SQUARE_SIZE, frame);
}
}
}
private class KeyWatcher extends Thread {
public void run() {
try {
while (true)
waitForKey();
} catch (InterruptedException e) {
}
}
}
public void update() {
frame.repaint();
}
public void dispose() {
images.clear();
statusImages.clear();
imageDB.clear();
frame.dispose();
}
public Dimension getGridDimension() {
return new Dimension(N_SQUARES_WIDTH, N_SQUARES_HEIGHT);
}
}

Related

The constructor "public GUI()" is called twice, causing two similar windows to open

I am currently working on a test project using windows and servers. I have it working so far (doing what's supposed to be doing), but the only problem is that the constructor is being called twice, making the code to display two similar windows. Help!
Here is my code:
(you can ignore the mouse listener parts, I don't really think that's the problem).
public class RummyClient implements Runnable{
public GUI gui = new GUI();
public static Servidor servidor;
public static void main(String[] args) throws UnknownHostException, IOException {
servidor = new Servidor(25565, "localhost");
new Thread(new RummyClient()).start();
}
#Override
public void run() {
while(true) {
gui.repaint();
}
}
public class GUI extends JFrame {
public int clickedX = 0;
public int clickedY = 0;
public String stage = "";
public boolean start = false;
public GUI() {
setTitle("Rummy");
setSize(1280, 720);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
setResizable(false);
Board board = new Board();
setContentPane(board);
getContentPane().setPreferredSize(new Dimension(1280, 720));
Move move = new Move();
addMouseMotionListener(move);
Click click = new Click();
addMouseListener(click);
pack();
stage = "start";
System.out.println("created");
}
public class Board extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
try {
Image board = ImageIO.read(new File("client/src/imgs/board.jpg")).getScaledInstance(1280, 720, java.awt.Image.SCALE_SMOOTH);
g.drawImage(board, 0, 0, this);
switch (stage) {
case "start":
Image playButtonNP = ImageIO.read(new File("client/src/imgs/play.png")).getScaledInstance(406, 220, java.awt.Image.SCALE_SMOOTH);
Image rummyTitle = ImageIO.read(new File("client/src/imgs/rummy.png")).getScaledInstance(526, 340, java.awt.Image.SCALE_SMOOTH);
g.drawImage(rummyTitle, 376, 0, this);
if (start){
g.drawImage(playButtonNP, 448, 360, this);
}
break;
}
} catch (Exception e) {
System.out.println("error!");
}
}
}
public class Move implements MouseMotionListener {
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
}
}
public class Click implements MouseListener{
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
clickedX = e.getX();
clickedY = e.getY();
}
#Override
public void mouseReleased(MouseEvent e) {
clickedX = 0;
clickedY = 0;
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
public void start() {
this.start = true;
}
}
static class Servidor extends RummyClient{
Socket s;
PrintWriter out;
Scanner in;
public Servidor(int port, String ip) throws UnknownHostException, IOException {
this.s = new Socket(ip, port);
this.out = new PrintWriter(s.getOutputStream());
this.in = new Scanner(s.getInputStream());
Thread input = new Thread(new Input());
input.start();
}
public void send(String text) {
out.println(text);
out.flush();
}
class Input implements Runnable {
#Override
public void run() {
while(true) {
String incoming = in.nextLine();
switch (incoming) {
case "ready":
gui.start();
break;
}
}
}
}
}
}

How to make two thread communicate through handler?

I have form that button can auto re-size their size when mouse hover on button and default their size when mouse is exited. It work normally first time but after i try it more than one time their size is enlarged that i can not control normally.
ImageIcon ima=new ImageIcon("C:\\Users\\chen rina\\Pictures\\win.png");
ImageIcon icon;
Thread thr;
Runnable r=new Runnable() {
#Override
public void run() {
int i=40;
while(i!=80){
try{
Thread.sleep(20);
Image scale=ima.getImage().getScaledInstance(i,i,Image.SCALE_FAST);
icon=new ImageIcon(scale);
btn2.setIcon(icon);
i=i+5;
}
catch(Exception ex){}
}
}
};
private void btn2MouseEntered(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
thr=new Thread(r);
thr.start();
}
Runnable res=new Runnable() {
int i;
#Override
public void run() {
int i=80;
while(i!=40){
try{
Thread.sleep(20);
Image scale=ima.getImage().getScaledInstance(i,i,Image.SCALE_AREA_AVERAGING);
icon=new ImageIcon(scale);
btn2.setIcon(icon);
i=i-5;
}
catch(Exception ex){}
}
}
};
private void btn2MouseExited(java.awt.event.MouseEvent evt) {
thr=new Thread(res);
thr.start();
}
Your code violates Swing thread integrity rules by making Swing calls, here setIcon(...) from within a background state. Having said that, why not simplify all of this by:
Reading in and creating and storing your ImageIcons once and only once
Never ignore exceptions as you're doing. That's unsafe coding.
Most important, use a Swing Timer to simply swap icons every 20 msec, and have no fear about violating Swing threading rules.
Your grow Timer's ActionListener could be as simple as this:
// a private inner class
private class GrowListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
// assuming the button is called button and the list iconList
button.setIcon(iconList.get(index));
index++;
if (index >= iconList.size()) {
((Timer) e.getSource()).stop();
}
}
}
The iconList would look something like:
private List<Icon> iconList = new ArrayList<>();
And you could fill it with code looking something like:
for (int i = startLength; i <= endLength; i += step) {
Image img = originalImg.getScaledInstance(i, i, Image.SCALE_FAST);
iconList.add(new ImageIcon(img));
}
And a more complete and runnable example:
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ResizeIconTest extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int START_LENGTH = 40;
private static final int END_LENGTH = 120;
private static final int STEP = 5;
private static final int TIMER_DELAY = 20;
private static final String URL_SPEC = "https://upload.wikimedia.org/wikipedia/commons/"
+ "thumb/2/2b/Oryx_gazella_-_Etosha_2014_square_crop.jpg/"
+ "600px-Oryx_gazella_-_Etosha_2014_square_crop.jpg";
private JButton button = new JButton();
private ResizeIcon resizeIcon;
public ResizeIconTest() throws IOException {
add(button);
URL imageUrl = new URL(URL_SPEC);
BufferedImage originalImg = ImageIO.read(imageUrl);
resizeIcon = new ResizeIcon(button, originalImg, START_LENGTH,
END_LENGTH, STEP, TIMER_DELAY);
button.setIcon(resizeIcon.getSmallestIcon());
button.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
resizeIcon.grow();
}
#Override
public void mouseExited(MouseEvent e) {
resizeIcon.shrink();
}
});
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
ResizeIconTest mainPanel = null;
try {
mainPanel = new ResizeIconTest();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("ResizeIconTest");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class ResizeIcon {
private List<Icon> iconList = new ArrayList<>();
private AbstractButton button;
private int delayTime;
private Timer growTimer;
private Timer shrinkTimer;
public ResizeIcon(AbstractButton button, BufferedImage originalImg,
int startLength, int endLength, int step, int delayTime) {
this.button = button;
this.delayTime = delayTime;
for (int i = startLength; i <= endLength; i += step) {
Image img = originalImg.getScaledInstance(i, i, Image.SCALE_FAST);
iconList.add(new ImageIcon(img));
}
}
public Icon getSmallestIcon() {
return iconList.get(0);
}
public void grow() {
if (growTimer != null && growTimer.isRunning()) {
return; // let's not run this multiple times
}
if (button.getIcon() == iconList.get(iconList.size() - 1)) {
return; // don't run if already at largest size
}
growTimer = new Timer(delayTime, new GrowListener());
growTimer.start();
}
public void shrink() {
if (shrinkTimer != null && shrinkTimer.isRunning()) {
return; // let's not run this multiple times
}
if (button.getIcon() == iconList.get(0)) {
return; // don't run if already at smallest size
}
shrinkTimer = new Timer(delayTime, new ShrinkListener());
shrinkTimer.start();
}
private class GrowListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
button.setIcon(iconList.get(index));
index++;
if (index >= iconList.size()) {
((Timer) e.getSource()).stop();
}
}
}
private class ShrinkListener implements ActionListener {
private int index = iconList.size() - 1;
#Override
public void actionPerformed(ActionEvent e) {
button.setIcon(iconList.get(index));
index--;
if (index < 0) {
((Timer) e.getSource()).stop();
}
}
}
}

Can you make a JToolBar undetachable?

I would like to make my JToolBar impossible to detach from its container but still let the user drag it to one of the container's sides.
I know about
public void setFloatable( boolean b )
but this won't allow the user to move the JToolBar at all.
Is there any way of doing this without overwriting ToolBarUI?
Also, is there an option to highlight its new position before dropping it?
It's not the most elegant solution, but it works.
public class Example extends JFrame {
BasicToolBarUI ui;
Example() {
JToolBar tb = new JToolBar();
tb.add(new JButton("AAAAA"));
tb.setBackground(Color.GREEN);
ui = (BasicToolBarUI) tb.getUI();
getContentPane().addContainerListener(new Listener());
getContentPane().add(tb, BorderLayout.PAGE_START);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 300);
setLocationRelativeTo(null);
setVisible(true);
}
class Listener implements ContainerListener {
#Override
public void componentAdded(ContainerEvent e) {}
#Override
public void componentRemoved(ContainerEvent e) {
if (ui.isFloating()) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ui.setFloating(false, null);
}
});
}
}
}
public static void main(String[] args) {
new Example();
}
}
Explanation:
Whenever the toolbar is moving to a floating state, it is instructed not do so. The only problem is that you have to wait for the EDT to finish the process for creating the floating window, and only then can you tell it not to float. The result is that you actually see the window created and then hidden.
Note:
I think that overriding the UI for the toolbar is a better solution, though it's possible that with a more intricate approach doing something similar to what I did will also work well.
works for me quite correctly on WinOS, old code from SunForum
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CaptiveToolBar {
private Robot robot;
private JDialog dialog;
private JFrame frame;
public static void main(String[] args) {
//JFrame.setDefaultLookAndFeelDecorated(true);
//JDialog.setDefaultLookAndFeelDecorated(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CaptiveToolBar().makeUI();
}
});
}
public void makeUI() {
try {
robot = new Robot();
} catch (AWTException ex) {
ex.printStackTrace();
}
final JToolBar toolBar = new JToolBar();
for (int i = 0; i < 3; i++) {
toolBar.add(new JButton("" + i));
}
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.add(toolBar, BorderLayout.NORTH);
final ComponentListener dialogListener = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dialog = (JDialog) e.getSource();
setLocations(false);
}
};
toolBar.addHierarchyListener(new HierarchyListener() {
#Override
public void hierarchyChanged(HierarchyEvent e) {
Window window = SwingUtilities.getWindowAncestor(toolBar);
if (window instanceof JDialog) {
boolean listenerAdded = false;
for (ComponentListener listener : window.getComponentListeners()) {
if (listener == dialogListener) {
listenerAdded = true;
break;
}
}
if (!listenerAdded) {
window.addComponentListener(dialogListener);
}
}
}
});
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
if (dialog != null && dialog.isShowing()) {
setLocations(true);
}
}
});
frame.setVisible(true);
}
private void setLocations(boolean moveDialog) {
int dialogX = dialog.getX();
int dialogY = dialog.getY();
int dialogW = dialog.getWidth();
int dialogH = dialog.getHeight();
int frameX = frame.getX();
int frameY = frame.getY();
int frameW = frame.getWidth();
int frameH = frame.getHeight();
boolean needToMove = false;
if (dialogX < frameX) {
dialogX = frameX;
needToMove = true;
}
if (dialogY < frameY) {
dialogY = frameY;
needToMove = true;
}
if (dialogX + dialogW > frameX + frameW) {
dialogX = frameX + frameW - dialogW;
needToMove = true;
}
if (dialogY + dialogH > frameY + frameH) {
dialogY = frameY + frameH - dialogH;
needToMove = true;
}
if (needToMove) {
if (!moveDialog && robot != null) {
robot.mouseRelease(InputEvent.BUTTON1_MASK);
}
dialog.setLocation(dialogX, dialogY);
}
}
}

Windows alignment in SWING-based GUI

My goal is to display several views of one object. For each view I create a thread. Also, I have a class which controls those views, e.g. send a command to align them. However, it is not always I get correct alignment. So there is a data races, and I cannot understand what I am doing wrong.
Here there is a piece of code showing the problem I have. It has a simple idea: create a main view window, and then align the second window of the same size near its right border.
First, I have an abstract class to create a thread:
public abstract class ViewWindow implements Runnable{
private Thread thread;
private boolean terminate = false ;
private Controller controller;
private UpdateTask currentUpdateTask = null;
private class UpdateTask {
boolean alignWindows = true;
}
public ViewWindow(Controller controller, String title) {
this.title = title;
this.controller = controller;
}
public void startThread() {
thread = new Thread(this);
thread.start();
}
#Override
public void run() {
UpdateTask updateTask = null;
synchronized (thread) {
while (terminate == false) {
try {
thread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
updateTask = currentUpdateTask;
currentUpdateTask = null;
if(updateTask.alignWindows) {
controller.getLock().lock();
setLocationRelativeTo(controller.getMainWindow());
controller.getLock().unlock();
}
}
}
}
public void alignWindowsUsingThread() {
synchronized (thread) {
currentUpdateTask = new UpdateTask();
thread.notify();
}
}
public abstract void setLocationRelativeTo(ImageViewWindow imageWindow);
}
Then I extend it to create an abstraction for the window views:
public abstract class ImageViewWindow extends ViewWindow {
private JFrame frame;
public ImageViewWindow(Controller controller, String title) {
super(controller, title);
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel(title);
frame.getContentPane().add(label, BorderLayout.CENTER);
frame.setPreferredSize(new Dimension(300,500));
frame.pack();
frame.setVisible(true);
}
public JFrame getFrame() {
return frame;
}
synchronized public void setLocation(int x, int y) {
frame.setLocation(x, y);
}
synchronized public Point getLocation() {
return frame.getLocation();
}
}
Finally, I override a function to set relative location for each window:
public class FirstWindow extends ImageViewWindow {
public FirstWindow(Controller controller, String title) {
super(controller, title);
this.setLocation(50, 50);
this.startThread();
}
#Override
public void setLocationRelativeTo(ImageViewWindow imageWindow) { }
}
public class SecondWindow extends ImageViewWindow {
public SecondWindow(Controller controller, String title) {
super(controller, title);
this.startThread();
}
#Override
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = imageWindow.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
}
Here there is a class which is responsible for the control:
public class Controller {
private Lock controlLock;
private List<ImageViewWindow> windows = new ArrayList<ImageViewWindow>();
private ImageViewWindow mainWindow;
public Controller() {
controlLock = new ReentrantLock();
}
public ImageViewWindow getMainWindow() {
return mainWindow;
}
public Lock getLock() {
return controlLock;
}
public void addMainWindow(ImageViewWindow mainViewWindow) {
this.mainWindow = mainViewWindow;
this.addWindow(mainViewWindow);
}
public void addWindow(ImageViewWindow imageWindow) {
windows.add(imageWindow);
}
public void updateWindowPositions() {
for(ImageViewWindow window : windows) {
window.alignWindowsUsingThread();
}
}
}
And do run everything:
public class Start {
public static void main(String[] args) {
Controller controller = new Controller();
ImageViewWindow window1 = new FirstWindow(controller, "FirstWindow");
controller.addMainWindow(window1);
ImageViewWindow window2 = new SecondWindow(controller, "SecondWindow");
controller.addWindow(window2);
controller.updateWindowPositions();
}
}
UPD: I updated the code based on the answer below, but the problem still remains!
You don’t specify how you want to “align” your windows but from your code
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = this.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
I suppose you want this to be placed to the right of imageWindow. In this case you have to use imageWindow.x + imageWindow.width rather than imageWindow.x + this.width:
| imageWindow | this
x ← width → x ← width →
↳=imageWindow.x+imageWindow.width
So the correct method would be:
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = imageWindow.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
By the way, I don’t get why you are making such a simple task that complicated and even multi-threaded. There’s no benefit from multi-threading here, only a complication that obstructs the view on the simplest things…

Multiple Point JSlider, similar to Fixed Width Slider in Microsoft Excel

I want to make a slider in Java, similar to JSlider, but it can have muliple sliders within itself. To be precise, I want to implement the slider used in Fixed Width Functionality in Microsoft Excel(From Data-> Fixed Width).
A normal slider has just one point, which we can drag to various positions. Now if i click within a position at a slider, a new point should get added, and the dragging operation of this point should be independent of the previous ones. Also if I do a double click, the point is removed from the slider.
import java.awt.*;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSliderUI;
public class MultiSlider extends JComponent
{
public static void main(String[] args)
{ // main method just for showing a usage example
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
catch(Exception ex){}
JFrame f=new JFrame();
final MultiSlider slider = new MultiSlider();
slider.setValue(0, 80);
slider.addValue(20);
f.getContentPane().add(slider);
f.setSize(200, 100);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
public MultiSlider()
{
super.setLayout(null);
addSlider(0);
}
public void setValue(int slider, int value)
{
((JSlider)getComponent(slider)).setValue(value);
}
public void addValue(int value)
{
addSlider(value);
}
#Override
public boolean isOptimizedDrawingEnabled()
{
return false;
}
#Override
public void doLayout()
{
Insets i=getInsets();
int x=i.left, y=i.top, width=getWidth()-x-i.right, height=getHeight()-y-i.bottom;
for(int ix=0, n=getComponentCount(); ix<n; ix++)
getComponent(ix).setBounds(x, y, width, height);
}
class SubSlider extends JSlider
{
private SubSlider active;
#Override
protected void processMouseEvent(MouseEvent e)
{
SubSlider sl=getClosestSlider(e);
if(e.getID()==MouseEvent.MOUSE_PRESSED) active=sl;
else if(e.getID()==MouseEvent.MOUSE_RELEASED) active=null;
if(e.getID()==MouseEvent.MOUSE_CLICKED)
{
if(sl==null && e.getClickCount()==1) addSlider(e.getPoint());
else if(sl!=null && e.getClickCount()==2)
{
removeSlider(sl);
return;
}
}
if(sl!=null) sl.realProcessMouseEvent(e);
}
private void realProcessMouseEvent(MouseEvent e)
{
e.setSource(this);
super.processMouseEvent(e);
}
#Override
protected void processMouseMotionEvent(MouseEvent e)
{
if(e.getID()==MouseEvent.MOUSE_MOVED)
toAllSliders(e);
else
{
if(active==null) active=getClosestSlider(e);
if(active!=null) active.realProcessMouseMotionEvent(e);
}
}
private void realProcessMouseMotionEvent(MouseEvent e)
{
e.setSource(this);
super.processMouseMotionEvent(e);
}
}
final void toAllSliders(MouseEvent e)
{
for(int ix=0, n=getComponentCount(); ix<n; ix++)
((SubSlider)getComponent(ix)).realProcessMouseMotionEvent(e);
}
public void removeSlider(SubSlider sl)
{
if(getComponentCount()<=1) return;// must keep the last slider
remove(sl);
JSlider slider=(JSlider)getComponent(getComponentCount()-1);
slider.setOpaque(true);
slider.setPaintTrack(true);
revalidate();
repaint();
}
final SubSlider getClosestSlider(MouseEvent e)
{
SubSlider s=(SubSlider)getComponent(0);
BasicSliderUI bsUI=(BasicSliderUI)s.getUI();
int value = bsUI.valueForXPosition(e.getX());
if(Math.abs(s.getValue()-value)<=1) return s;
for(int ix=1, n=getComponentCount(); ix<n; ix++)
{
s=(SubSlider)getComponent(ix);
if(Math.abs(s.getValue()-value)<=1) return s;
}
return null;
}
void addSlider(Point point)
{
BasicSliderUI bsUI = (BasicSliderUI)((JSlider)getComponent(0)).getUI();
addSlider(bsUI.valueForXPosition(point.x));
}
void addSlider(int value)
{
final JSlider slider = new SubSlider();
slider.setFocusable(false);
slider.setValue(value);
if(getComponentCount()!=0)
{
slider.setOpaque(false);
slider.setPaintTrack(false);
}
super.add(slider, 0);
revalidate();
repaint();
}
}

Categories