Showing posts with label Java Swing. Show all posts
Showing posts with label Java Swing. Show all posts

Tetris game in Java Swing

The Tetris game

In this chapter, we will create a Tetris game clone in Java Swing. The following example is based on the C++ code example available at doc.trolltech.com. It is modified and simplified.

Tetris

The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a russian programmer Alexey Pajitnov in 1985. Since then, Tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the Tetris game.
Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the Tetris game is to move and rotate the shapes, so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the tetris game until we top out.
Tetrominoes
Figure: Tetrominoes

The development

We do not have images for our tetris game, we draw the tetrominoes using Swing drawing API. Behind every computer game, there is a mathematical model. So it is in Tetris.
Some ideas behind the game.
  • We use a Timer class to create a game cycle
  • The tetrominoes are drawn
  • The shapes move on a square by square basis (not pixel by pixel)
  • Mathematically a board is a simple list of numbers
I have simplified the game a bit, so that it is easier to understand. The game starts immediately, after it is launched. We can pause the game by pressing the p key. The space key will drop the tetris piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The score is the number of lines, that we have removed.
Tetris.java
package tetris;

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;


public class Tetris extends JFrame {

JLabel statusbar;


public Tetris() {

statusbar = new JLabel(" 0");
add(statusbar, BorderLayout.SOUTH);
Board board = new Board(this);
add(board);
board.start();

setSize(200, 400);
setTitle("Tetris");
setDefaultCloseOperation(EXIT_ON_CLOSE);
}

public JLabel getStatusBar() {
return statusbar;
}

public static void main(String[] args) {

Tetris game = new Tetris();
game.setLocationRelativeTo(null);
game.setVisible(true);

}
}
In the Tetris.java file, we set up the game. We create a board on which we play the game. We create a statusbar.
board.start();
The start() method starts the Tetris game. Immediately, after the window appears on the screen.
Shape.java
package tetris;

import java.util.Random;
import java.lang.Math;


public class Shape {

enum Tetrominoes { NoShape, ZShape, SShape, LineShape,
TShape, SquareShape, LShape, MirroredLShape };

private Tetrominoes pieceShape;
private int coords[][];
private int[][][] coordsTable;


public Shape() {

coords = new int[4][2];
setShape(Tetrominoes.NoShape);

}

public void setShape(Tetrominoes shape) {

coordsTable = new int[][][] {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};

for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j) {
coords[i][j] = coordsTable[shape.ordinal()][i][j];
}
}
pieceShape = shape;

}

private void setX(int index, int x) { coords[index][0] = x; }
private void setY(int index, int y) { coords[index][1] = y; }
public int x(int index) { return coords[index][0]; }
public int y(int index) { return coords[index][1]; }
public Tetrominoes getShape() { return pieceShape; }

public void setRandomShape()
{
Random r = new Random();
int x = Math.abs(r.nextInt()) % 7 + 1;
Tetrominoes[] values = Tetrominoes.values();
setShape(values[x]);
}

public int minX()
{
int m = coords[0][0];
for (int i=0; i < 4; i++) {
m = Math.min(m, coords[i][0]);
}
return m;
}


public int minY()
{
int m = coords[0][1];
for (int i=0; i < 4; i++) {
m = Math.min(m, coords[i][1]);
}
return m;
}

public Shape rotateLeft()
{
if (pieceShape == Tetrominoes.SquareShape)
return this;

Shape result = new Shape();
result.pieceShape = pieceShape;

for (int i = 0; i < 4; ++i) {
result.setX(i, y(i));
result.setY(i, -x(i));
}
return result;
}

public Shape rotateRight()
{
if (pieceShape == Tetrominoes.SquareShape)
return this;

Shape result = new Shape();
result.pieceShape = pieceShape;

for (int i = 0; i < 4; ++i) {
result.setX(i, -y(i));
result.setY(i, x(i));
}
return result;
}
}
The Shape class provides information about a tetris piece.
enum Tetrominoes { NoShape, ZShape, SShape, LineShape, 
TShape, SquareShape, LShape, MirroredLShape };
The Tetrominoes enum holds all seven tetris shapes. Plus the empty shape called here NoShape.
public Shape() {

coords = new int[4][2];
setShape(Tetrominoes.NoShape);

}
This is the constructor of the Shape class. The coords array holds the actual coordinates of a tetris piece.
coordsTable = new int[][][] {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};
The coordsTable array holds all possible coordinate values of our tetris pieces. This is a template from which all pieces take their coordiate values.
for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j) {
coords[i][j] = coordsTable[shape.ordinal()][i][j];
}
}
Here we put one row of the coordiate values from the coordsTable to a coordsarray of a tetris piece. Note the use of the ordinal() method. In C++, an enum type is esencially an integer. Unlike in C++, Java enums are full classes. And the ordinal() method returns the current position of the enum type in the enum object.
The following image will help understand the coordinate values a bit more. The coords array saves the coordinates of the tetris piece. For example, numbers { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, -1 } , represent a rotated S-shape. The following diagram illustrates the shape.
Tetris
Figure: Tetris
 public Shape rotateLeft() 
{
if (pieceShape == Tetrominoes.SquareShape)
return this;

Shape result = new Shape();
result.pieceShape = pieceShape;

for (int i = 0; i < 4; ++i) {
result.setX(i, y(i));
result.setY(i, -x(i));
}
return result;
}
This code rotates the piece to the left. The square does not have to be rotated. That's why we simply return the reference to the current object. Looking at the previous image will help to understand the rotation.
Board.java
package tetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

import tetris.Shape.Tetrominoes;


public class Board extends JPanel implements ActionListener {


final int BoardWidth = 10;
final int BoardHeight = 22;

Timer timer;
boolean isFallingFinished = false;
boolean isStarted = false;
boolean isPaused = false;
int numLinesRemoved = 0;
int curX = 0;
int curY = 0;
JLabel statusbar;
Shape curPiece;
Tetrominoes[] board;



public Board(Tetris parent) {

setFocusable(true);
curPiece = new Shape();
timer = new Timer(400, this);
timer.start();

statusbar = parent.getStatusBar();
board = new Tetrominoes[BoardWidth * BoardHeight];
addKeyListener(new TAdapter());
clearBoard();
}

public void actionPerformed(ActionEvent e) {
if (isFallingFinished) {
isFallingFinished = false;
newPiece();
} else {
oneLineDown();
}
}


int squareWidth() { return (int) getSize().getWidth() / BoardWidth; }
int squareHeight() { return (int) getSize().getHeight() / BoardHeight; }
Tetrominoes shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }


public void start()
{
if (isPaused)
return;

isStarted = true;
isFallingFinished = false;
numLinesRemoved = 0;
clearBoard();

newPiece();
timer.start();
}

private void pause()
{
if (!isStarted)
return;

isPaused = !isPaused;
if (isPaused) {
timer.stop();
statusbar.setText("paused");
} else {
timer.start();
statusbar.setText(String.valueOf(numLinesRemoved));
}
repaint();
}

public void paint(Graphics g)
{
super.paint(g);

Dimension size = getSize();
int boardTop = (int) size.getHeight() - BoardHeight * squareHeight();


for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
if (shape != Tetrominoes.NoShape)
drawSquare(g, 0 + j * squareWidth(),
boardTop + i * squareHeight(), shape);
}
}

if (curPiece.getShape() != Tetrominoes.NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
drawSquare(g, 0 + x * squareWidth(),
boardTop + (BoardHeight - y - 1) * squareHeight(),
curPiece.getShape());
}
}
}

private void dropDown()
{
int newY = curY;
while (newY > 0) {
if (!tryMove(curPiece, curX, newY - 1))
break;
--newY;
}
pieceDropped();
}

private void oneLineDown()
{
if (!tryMove(curPiece, curX, curY - 1))
pieceDropped();
}


private void clearBoard()
{
for (int i = 0; i < BoardHeight * BoardWidth; ++i)
board[i] = Tetrominoes.NoShape;
}

private void pieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
board[(y * BoardWidth) + x] = curPiece.getShape();
}

removeFullLines();

if (!isFallingFinished)
newPiece();
}

private void newPiece()
{
curPiece.setRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.minY();

if (!tryMove(curPiece, curX, curY)) {
curPiece.setShape(Tetrominoes.NoShape);
timer.stop();
isStarted = false;
statusbar.setText("game over");
}
}

private boolean tryMove(Shape newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (shapeAt(x, y) != Tetrominoes.NoShape)
return false;
}

curPiece = newPiece;
curX = newX;
curY = newY;
repaint();
return true;
}

private void removeFullLines()
{
int numFullLines = 0;

for (int i = BoardHeight - 1; i >= 0; --i) {
boolean lineIsFull = true;

for (int j = 0; j < BoardWidth; ++j) {
if (shapeAt(j, i) == Tetrominoes.NoShape) {
lineIsFull = false;
break;
}
}

if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
}
}
}

if (numFullLines > 0) {
numLinesRemoved += numFullLines;
statusbar.setText(String.valueOf(numLinesRemoved));
isFallingFinished = true;
curPiece.setShape(Tetrominoes.NoShape);
repaint();
}
}

private void drawSquare(Graphics g, int x, int y, Tetrominoes shape)
{
Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102),
new Color(102, 204, 102), new Color(102, 102, 204),
new Color(204, 204, 102), new Color(204, 102, 204),
new Color(102, 204, 204), new Color(218, 170, 0)
};


Color color = colors[shape.ordinal()];

g.setColor(color);
g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);

g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);

g.setColor(color.darker());
g.drawLine(x + 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + squareHeight() - 1);
g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + 1);

}

class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {

if (!isStarted || curPiece.getShape() == Tetrominoes.NoShape) {
return;
}

int keycode = e.getKeyCode();

if (keycode == 'p' || keycode == 'P') {
pause();
return;
}

if (isPaused)
return;

switch (keycode) {
case KeyEvent.VK_LEFT:
tryMove(curPiece, curX - 1, curY);
break;
case KeyEvent.VK_RIGHT:
tryMove(curPiece, curX + 1, curY);
break;
case KeyEvent.VK_DOWN:
tryMove(curPiece.rotateRight(), curX, curY);
break;
case KeyEvent.VK_UP:
tryMove(curPiece.rotateLeft(), curX, curY);
break;
case KeyEvent.VK_SPACE:
dropDown();
break;
case 'd':
oneLineDown();
break;
case 'D':
oneLineDown();
break;
}

}
}
}
Finally, we have the Board.java file. This is where the game logic is located.
 ...
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
We initialize some important variables. The isFallingFinished variable determines, if the tetris shape has finished falling and we then need to create a new shape. The numLinesRemoved counts the number of lines, we have removed so far. The curX and curY variables determine the actual position of the falling tetris shape.
setFocusable(true);
We must explicitely call the setFocusable() method. From now, the board has the keyboard input.
timer = new Timer(400, this);
timer.start();
Timer object fires one or more action events after a specified delay. In our case, the timer calls the actionPerformed() method each 400 ms.
public void actionPerformed(ActionEvent e) {
if (isFallingFinished) {
isFallingFinished = false;
newPiece();
} else {
oneLineDown();
}
}
The actionPerformed() method checks if the falling has finished. If so, a new piece is created. If not, the falling tetris piece goes one line down.
Inside the paint() method, we draw the all objects on the board. The painting has two steps.
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
if (shape != Tetrominoes.NoShape)
drawSquare(g, 0 + j * squareWidth(),
boardTop + i * squareHeight(), shape);
}
}
In the first step we paint all the shapes, or remains of the shapes, that have been dropped to the bottom of the board. All the squares are rememberd in the board array. We access it using the shapeAt() method.
if (curPiece.getShape() != Tetrominoes.NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
drawSquare(g, 0 + x * squareWidth(),
boardTop + (BoardHeight - y - 1) * squareHeight(),
curPiece.getShape());
}
}
In the second step, we paint the actual falling piece.
private void dropDown()
{
int newY = curY;
while (newY > 0) {
if (!tryMove(curPiece, curX, newY - 1))
break;
--newY;
}
pieceDropped();
}
If we press the space key, the piece is dropped to the bottom. We simply try to drop the piece one line down until it reaches the bottom or the top of another fallen tetris piece.
private void clearBoard()
{
for (int i = 0; i < BoardHeight * BoardWidth; ++i)
board[i] = Tetrominoes.NoShape;
}
The clearBoard() method fills the board with empty NoSpapes. This is later used at collision detection.
private void pieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
board[(y * BoardWidth) + x] = curPiece.getShape();
}

removeFullLines();

if (!isFallingFinished)
newPiece();
}
The pieceDropped() method puts the falling piece into the board array. Once again, the board holds all the squares of the pieces and remains of the pieces that has finished falling. When the piece has finished falling, it is time to check, if we can remove some lines off the board. This is the job of the removeFullLines() method. Then we create a new piece. More precisely, we try to create a new piece.
private void newPiece()
{
curPiece.setRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.minY();

if (!tryMove(curPiece, curX, curY)) {
curPiece.setShape(Tetrominoes.NoShape);
timer.stop();
isStarted = false;
statusbar.setText("game over");
}
}
The newPiece() method creates a new tetris piece. The piece gets a new random shape. Then we compute the initial curX and curY values. If we cannot move to the initial positions, the game is over. We top out. The timer is stopped. We put game over string on the statusbar.
 private boolean tryMove(Shape newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (shapeAt(x, y) != Tetrominoes.NoShape)
return false;
}

curPiece = newPiece;
curX = newX;
curY = newY;
repaint();
return true;
}
The tryMove() method tries to move the tetris piece. The method returns false, if it has reached the board boundaries or it is adjacent to the already fallen tetris pieces.
 for (int i = BoardHeight - 1; i >= 0; --i) {
boolean lineIsFull = true;

for (int j = 0; j < BoardWidth; ++j) {
if (shapeAt(j, i) == Tetrominoes.NoShape) {
lineIsFull = false;
break;
}
}

if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
}
}
}
Inside the removeFullLines() method, we check if there is any full row among all rows in the board. If there is at least one full line, it is removed. After finding a full line we increase the counter. We move all the lines above the full row one line down. This way we destroy the full line. Notice, that in our Tetris game, we use so called naive gravity. This means, that the squares may be left floating above empty gaps.
Every Tetris piece has four squares. Each of the squares is drawn with the drawSquare() method. Tetris pieces have different colors.
g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
The left and top sides of a square are drawn with a brighter color. Similarly, the bottom and right sides are drawn with darker colors. This is to simulate a 3D edge.
We control the game with a keyboard. The control mechanism is implemented with a KeyAdapter. This is an inner class that overrides the keyPressed() method.
case KeyEvent.VK_RIGHT:
tryMove(curPiece, curX + 1, curY);
break;
If we pressed the left arrow key, we try to move the falling piece one square to the left.
Tetris
Figure: Tetris
This was a Tetris game.
Continue Reading

Puzzle in Java Swing

The Puzzle in Java Swing

In this chapter, we will create a simple puzzle game in Java Swing toolkit.

Puzzle

We have an image of a Sid character from the Ice Age movie. It is cut into 12 pieces. The goal is to form the picture.
package zetcode;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;

import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Puzzle extends JFrame implements ActionListener {

private JPanel centerPanel;
private JButton button;
private JLabel label;
private Image source;
private Image image;
int[][] pos;
int width, height;

public Puzzle() {

initUI();
}

public final void initUI() {

pos = new int[][]{
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{9, 10, 11}
};


centerPanel = new JPanel();
centerPanel.setLayout(new GridLayout(4, 4, 0, 0));

ImageIcon sid = new ImageIcon(Puzzle.class.getResource("icesid.jpg"));
source = sid.getImage();

width = sid.getIconWidth();
height = sid.getIconHeight();


add(Box.createRigidArea(new Dimension(0, 5)), BorderLayout.NORTH);
add(centerPanel, BorderLayout.CENTER);


for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
if (j == 2 && i == 3) {
label = new JLabel("");
centerPanel.add(label);
} else {
button = new JButton();
button.addActionListener(this);
centerPanel.add(button);
image = createImage(new FilteredImageSource(source.getSource(),
new CropImageFilter(j * width / 3, i * height / 4,
(width / 3) + 1, height / 4)));
button.setIcon(new ImageIcon(image));
}
}
}

setSize(325, 275);
setTitle("Puzzle");
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {

JButton button = (JButton) e.getSource();
Dimension size = button.getSize();

int labelX = label.getX();
int labelY = label.getY();
int buttonX = button.getX();
int buttonY = button.getY();
int buttonPosX = buttonX / size.width;
int buttonPosY = buttonY / size.height;
int buttonIndex = pos[buttonPosY][buttonPosX];



if (labelX == buttonX && (labelY - buttonY) == size.height) {

int labelIndex = buttonIndex + 3;

centerPanel.remove(buttonIndex);
centerPanel.add(label, buttonIndex);
centerPanel.add(button, labelIndex);
centerPanel.validate();
}

if (labelX == buttonX && (labelY - buttonY) == -size.height) {

int labelIndex = buttonIndex - 3;
centerPanel.remove(labelIndex);
centerPanel.add(button, labelIndex);
centerPanel.add(label, buttonIndex);
centerPanel.validate();
}

if (labelY == buttonY && (labelX - buttonX) == size.width) {

int labelIndex = buttonIndex + 1;

centerPanel.remove(buttonIndex);
centerPanel.add(label, buttonIndex);
centerPanel.add(button, labelIndex);
centerPanel.validate();
}

if (labelY == buttonY && (labelX - buttonX) == -size.width) {

int labelIndex = buttonIndex - 1;

centerPanel.remove(buttonIndex);
centerPanel.add(label, labelIndex);
centerPanel.add(button, labelIndex);
centerPanel.validate();
}
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {

public void run() {
Puzzle puzzle = new Puzzle();
puzzle.setVisible(true);
}
});
}
}

The goal of this little game is to form the original picture. We move the buttons by clicking on them. Only buttons adjacent to the label can be moved.
 pos = new int[][] {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{9, 10, 11}
};
These are the positions of the image parts.
ImageIcon sid = new ImageIcon(Puzzle.class.getResource("icesid.jpg"));
source = sid.getImage();
We use the ImageIcon class to load the image.
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
if (j == 2 && i == 3) {
label = new JLabel("");
centerPanel.add(label);
} else {
button = new JButton();
button.addActionListener(this);
centerPanel.add(button);
image = createImage(new FilteredImageSource(source.getSource(),
new CropImageFilter(j * width / 3, i * height / 4,
(width / 3) + 1, height / 4)));
button.setIcon(new ImageIcon(image));
}
}
}
The code creates 11 buttons and one label. We crop the image into pieces and place them on the buttons.
int labelX = label.getX();
int labelY = label.getY();
int buttonX = button.getX();
int buttonY = button.getY();
We get the x, y coordinates of the button that we hit and an empty label. The x, y coordinates are important in the logic of the program.
int buttonPosX = buttonX / size.width;
int buttonPosY = buttonY / size.height;
int buttonIndex = pos[buttonPosY][buttonPosX];
Here we get the index of the button in the two dimensional array of the button positions.
if (labelX == buttonX && (labelY - buttonY) == size.height ) {

int labelIndex = buttonIndex + 3;

centerPanel.remove(buttonIndex);
centerPanel.add(label, buttonIndex);
centerPanel.add(button,labelIndex);
centerPanel.validate();
}
In this case, we check if we clicked on the button, that is right above the empty label. If it is above the label, they share the x coordinate. If the button is right above the label, the equation (labelY - buttonY) == size.height is true.
Puzzle
Figure: Puzzle
This was a puzzle game.
Continue Reading

Resizable components in Java Swing

Resizable components in Java Swing

In this part of the Java Swing tutorial, we will create a resizable component.

Resizable component

Resizable components are most often used when creating charts, diagrams and similar. The most common resizable component is a chart in a spreadsheet application. For example, when we create a chart in a OpenOffice application. The chart can be moved over the grid widget of the application and resized.
In order to create a component that can be freely dragged over a panel, we need a panel with absolute positioning enabled. We must not use a layout manager. In our example, we will create a component (a JPanel) that we can freely move over a parent window and resize.
In order to distinguish which component has a focus, we draw 8 small rectangles on the border of our resizable component. This way we know, that the component has focus. The rectangles serve as a dragging points, where we can draw the component and start resizing. I have learnt to use resizable components from this blog.
package resizablecomponent;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;


/* ResizableComponent.java */

public class ResizableComponent extends JFrame {

private JPanel panel = new JPanel(null);
private Resizable resizer;


public ResizableComponent() {

add(panel);

JPanel area = new JPanel();
area.setBackground(Color.white);
resizer = new Resizable(area);
resizer.setBounds(50, 50, 200, 150);
panel.add(resizer);


setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(new Dimension(350, 300));
setTitle("Resizable Component");
setLocationRelativeTo(null);

addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {

requestFocus();
resizer.repaint();
}
});
}

public static void main(String[] args) {
ResizableComponent rc = new ResizableComponent();
rc.setVisible(true);
}
}
The ResizableComponent sets up the panel and the component.
private JPanel panel = new JPanel(null);
We have already mentioned, that we cannot use any layout manager. We must use absolute positioning for resizable component. By providing null to the constructor, we create a panel with absolute positioning.
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {

requestFocus();
resizer.repaint();
}
});
If we press on the parent panel, e.g outside the resizable component, we grab focus and repaint the component. The rectangles over he border will disappear.
package resizablecomponent;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.SwingConstants;
import javax.swing.border.Border;

// ResizableBorder.java

public class ResizableBorder implements Border {
private int dist = 8;

int locations[] =
{
SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
SwingConstants.EAST, SwingConstants.NORTH_WEST,
SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
SwingConstants.SOUTH_EAST
};

int cursors[] =
{
Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR,
Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
};

public ResizableBorder(int dist) {
this.dist = dist;
}

public Insets getBorderInsets(Component component) {
return new Insets(dist, dist, dist, dist);
}

public boolean isBorderOpaque() {
return false;
}

public void paintBorder(Component component, Graphics g, int x, int y,
int w, int h) {
g.setColor(Color.black);
g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);

if (component.hasFocus()) {


for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
g.setColor(Color.WHITE);
g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
g.setColor(Color.BLACK);
g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
}
}
}

private Rectangle getRectangle(int x, int y, int w, int h, int location) {
switch (location) {
case SwingConstants.NORTH:
return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
case SwingConstants.SOUTH:
return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist,
dist);
case SwingConstants.WEST:
return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);
case SwingConstants.EAST:
return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist,
dist);
case SwingConstants.NORTH_WEST:
return new Rectangle(x, y, dist, dist);
case SwingConstants.NORTH_EAST:
return new Rectangle(x + w - dist, y, dist, dist);
case SwingConstants.SOUTH_WEST:
return new Rectangle(x, y + h - dist, dist, dist);
case SwingConstants.SOUTH_EAST:
return new Rectangle(x + w - dist, y + h - dist, dist, dist);
}
return null;
}

public int getCursor(MouseEvent me) {
Component c = me.getComponent();
int w = c.getWidth();
int h = c.getHeight();

for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(0, 0, w, h, locations[i]);
if (rect.contains(me.getPoint()))
return cursors[i];
}

return Cursor.MOVE_CURSOR;
}
}
The ResizableBorder is responsible for drawing the border of the component and determining the type of the cursor to use.
int locations[] = 
{
SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
SwingConstants.EAST, SwingConstants.NORTH_WEST,
SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
SwingConstants.SOUTH_EAST
};
These are locations, where we will draw rectangles. These locations are grabbing points, where we can grab the component and resize it.
g.setColor(Color.black);
g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
In the paintBorder() method, we draw the border of the resizable component. The upper code draws the outer border of the component.
if (component.hasFocus()) {

for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
g.setColor(Color.WHITE);
g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
g.setColor(Color.BLACK);
g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
}
}
The eight rectangles are drawn only in case that the resizable component has currently focus.
Finally, the getRectangle() method gets the coordinates of the rectangles and the getCursor() methods gets the cursor type for the grab point in question.
package resizablecomponent;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;

// Resizable.java

public class Resizable extends JComponent {

public Resizable(Component comp) {
this(comp, new ResizableBorder(8));
}

public Resizable(Component comp, ResizableBorder border) {
setLayout(new BorderLayout());
add(comp);
addMouseListener(resizeListener);
addMouseMotionListener(resizeListener);
setBorder(border);
}

private void resize() {
if (getParent() != null) {
((JComponent)getParent()).revalidate();
}
}

MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}

public void mouseExited(MouseEvent mouseEvent) {
setCursor(Cursor.getDefaultCursor());
}

private int cursor;
private Point startPos = null;

public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.getCursor(me);
startPos = me.getPoint();
requestFocus();
repaint();
}

public void mouseDragged(MouseEvent me) {

if (startPos != null) {

int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();

int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;

switch (cursor) {
case Cursor.N_RESIZE_CURSOR:
if (!(h - dy < 50)) {
setBounds(x, y + dy, w, h - dy);
resize();
}
break;

case Cursor.S_RESIZE_CURSOR:
if (!(h + dy < 50)) {
setBounds(x, y, w, h + dy);
startPos = me.getPoint();
resize();
}
break;

case Cursor.W_RESIZE_CURSOR:
if (!(w - dx < 50)) {
setBounds(x + dx, y, w - dx, h);
resize();
}
break;

case Cursor.E_RESIZE_CURSOR:
if (!(w + dx < 50)) {
setBounds(x, y, w + dx, h);
startPos = me.getPoint();
resize();
}
break;

case Cursor.NW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h - dy < 50)) {
setBounds(x + dx, y + dy, w - dx, h - dy);
resize();
}
break;

case Cursor.NE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h - dy < 50)) {
setBounds(x, y + dy, w + dx, h - dy);
startPos = new Point(me.getX(), startPos.y);
resize();
}
break;

case Cursor.SW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h + dy < 50)) {
setBounds(x + dx, y, w - dx, h + dy);
startPos = new Point(startPos.x, me.getY());
resize();
}
break;

case Cursor.SE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h + dy < 50)) {
setBounds(x, y, w + dx, h + dy);
startPos = me.getPoint();
resize();
}
break;

case Cursor.MOVE_CURSOR:
Rectangle bounds = getBounds();
bounds.translate(dx, dy);
setBounds(bounds);
resize();
}


setCursor(Cursor.getPredefinedCursor(cursor));
}
}

public void mouseReleased(MouseEvent mouseEvent) {
startPos = null;
}
};
}
The Resizable class represents the component, that is being resized and moved on the window.
private void resize() {
if (getParent() != null) {
((JComponent)getParent()).revalidate();
}
}
The resize() method is called, after we have resized the component. The revalidate() method will cause the component to be redrawn.
MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}
We change the cursor type, when we hover the cursor over the grip points. The cursor type changes only if the component has focus.
public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.getCursor(me);
startPos = me.getPoint();
requestFocus();
repaint();
}
If we click on the resizable component, we change the cursor, get the starting point of dragging, give focus to the component and redraw it.
int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();

int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;
In the mouseDragged() method, we determine the x, y coordinates of the cursor, width and height of the component. We calculate the distances, that we make during the mouse drag event.
case Cursor.N_RESIZE_CURSOR:
if (!(h - dy < 50)) {
setBounds(x, y + dy, w, h - dy);
resize();
}
break;
For all resizing we ensure, that the component is not smaller than 50 px. Otherwise, we could make it so small, that we would eventually hide the component. The setBounds() method relocates and resizes the component.
Resizable component
Figure: Resizable component
In this part of the Java Swing tutorial, we have created a resizable component.
Continue Reading

Painting in Java Swing

Painting in Swing

Painting is used, when we want to change or enhance an existing widget. Or if we are creating a custom widget from scratch. To do the painting, we use the painting API provided by the Swing toolkit.
The painting is done within the paintComponent() method. In the painting process, we use the Graphics2D object.

2D Vector Graphics

There are two different computer graphics. Vector and raster graphics. Raster graphics represents images as a collection of pixels. Vector graphics is the use of geometrical primitives such as points, lines, curves or polygons to represent images. These primitives are created using mathematical equations.
Both types of computer graphics have advantages and disadvantages. The advantages of vector graphics over raster are:
  • smaller size
  • ability to zoom indefinitely
  • moving, scaling, filling or rotating does not degrade the quality of an image

Types of primitives

  • points
  • lines
  • polylines
  • polygons
  • circles
  • ellipses
  • Splines

Points

The most simple graphics primitive is point. It is a single dot on the window. There is no method to draw a point in Swing. To draw a point, we use the drawLine() method. We use one point twice.
package zetcode;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


class DrawPanel extends JPanel {

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

g2d.setColor(Color.blue);

for (int i = 0; i <= 1000; i++) {
Dimension size = getSize();
Insets insets = getInsets();

int w = size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;

Random r = new Random();
int x = Math.abs(r.nextInt()) % w;
int y = Math.abs(r.nextInt()) % h;
g2d.drawLine(x, y, x, y);
}

}
}

public class PointsExample extends JFrame {

public PointsExample() {
initUI();
}

public final void initUI() {

DrawPanel dpnl = new DrawPanel();
add(dpnl);

setSize(250, 200);
setTitle("Points");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
PointsExample ex = new PointsExample();
ex.setVisible(true);
}
});
}
}
One point is difficult to observe. Why not paint 1000 of them. In our example, we do so. We draw 1000 blue points on the panel.
class DrawPanel extends JPanel {
We are drawing on a custom drawing panel, which is a JPanel component. The drawing panel will later be added to a JFrame component.
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
The painting is performed inside the paintComponent()method.
Graphics2D g2d = (Graphics2D) g;
Painting in Swing is done on the Graphics2D object.
g2d.setColor(Color.blue);
We will paint our points in blue color.
Dimension size = getSize();
Insets insets = getInsets();
The size of the window includes borders and titlebar. We don't paint there.
int w =  size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;
Here we calculate the area, where we will effectively paint our points.
Random r = new Random();
int x = Math.abs(r.nextInt()) % w;
int y = Math.abs(r.nextInt()) % h;
We get a random number in range of the size of area, that we computed above.
g2d.drawLine(x, y, x, y);
Here we draw the point. As I already said, we use a drawLine() method. We specify the same point twice.
Points
Figure: Points

Lines

A line is a simple graphics primitive. It is drawn using two points.
package zetcode;

import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;



class DrawPanel extends JPanel {

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

float[] dash1 = { 2f, 0f, 2f };
float[] dash2 = { 1f, 1f, 1f };
float[] dash3 = { 4f, 0f, 2f };
float[] dash4 = { 4f, 4f, 1f };

g2d.drawLine(20, 40, 250, 40);

BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f );

BasicStroke bs2 = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 1.0f, dash2, 2f );

BasicStroke bs3 = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f );

BasicStroke bs4 = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 1.0f, dash4, 2f );

g2d.setStroke(bs1);
g2d.drawLine(20, 80, 250, 80);

g2d.setStroke(bs2);
g2d.drawLine(20, 120, 250, 120);

g2d.setStroke(bs3);
g2d.drawLine(20, 160, 250, 160);

g2d.setStroke(bs4);
g2d.drawLine(20, 200, 250, 200);

}
}

public class LinesExample extends JFrame {

public LinesExample() {
initUI();
}

public final void initUI() {

DrawPanel dpnl = new DrawPanel();
add(dpnl);

setSize(280, 270);
setTitle("Lines");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
LinesExample ex = new LinesExample();
ex.setVisible(true);
}
});
}
}
In the example, we draw five lines. The first line is drawn using the default values. Other will have a different stroke. The stroke is created using the BasicStroke class. It defines a basic set of rendering attributes for the outlines of graphics primitives.
float[] dash1 = { 2f, 0f, 2f };
Here we create a dash, that we use in the stroke object.
BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, 
BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f )
This code creates a stroke. The stroke defines the line width, end caps, line joins, miter limit, dash and the dash phase.
Lines
Figure: Lines

Rectangles

To draw rectangles, we use the drawRect() method. To fill rectangles with the current color, we use the fillRect() method.
package zetcode;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


class DrawPanel extends JPanel {

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

g2d.setColor(new Color(212, 212, 212));
g2d.drawRect(10, 15, 90, 60);
g2d.drawRect(130, 15, 90, 60);
g2d.drawRect(250, 15, 90, 60);
g2d.drawRect(10, 105, 90, 60);
g2d.drawRect(130, 105, 90, 60);
g2d.drawRect(250, 105, 90, 60);
g2d.drawRect(10, 195, 90, 60);
g2d.drawRect(130, 195, 90, 60);
g2d.drawRect(250, 195, 90, 60);

g2d.setColor(new Color(125, 167, 116));
g2d.fillRect(10, 15, 90, 60);

g2d.setColor(new Color(42, 179, 231));
g2d.fillRect(130, 15, 90, 60);

g2d.setColor(new Color(70, 67, 123));
g2d.fillRect(250, 15, 90, 60);

g2d.setColor(new Color(130, 100, 84));
g2d.fillRect(10, 105, 90, 60);

g2d.setColor(new Color(252, 211, 61));
g2d.fillRect(130, 105, 90, 60);

g2d.setColor(new Color(241, 98, 69));
g2d.fillRect(250, 105, 90, 60);

g2d.setColor(new Color(217, 146, 54));
g2d.fillRect(10, 195, 90, 60);

g2d.setColor(new Color(63, 121, 186));
g2d.fillRect(130, 195, 90, 60);

g2d.setColor(new Color(31, 21, 1));
g2d.fillRect(250, 195, 90, 60);
}
}

public class RectanglesExample extends JFrame {

public RectanglesExample() {
initUI();
}

public final void initUI() {

DrawPanel dpnl = new DrawPanel();
add(dpnl);

setSize(360, 300);
setTitle("Rectangles");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
RectanglesExample ex = new RectanglesExample();
ex.setVisible(true);
}
});
}
}
In the example we draw nine colored rectangles.
g2d.setColor(new Color(212, 212, 212));
g2d.drawRect(10, 15, 90, 60);
...
We set the color of the outline of the rectangle to a soft gray color, so that it does not interfere with the fill color. To draw the outline of the rectangle, we use the drawRect() method. The first two parameters are the x and y values. The third and fourth are width and height.
g2d.fillRect(10, 15, 90, 60);
To fill the rectangle with a color, we use the fillRect() method.
Rectangles
Figure: Rectangles

Textures

A texture is a bitmap image applied to the surface in computer graphics. Besides colors and gradients, we can fill our graphics shapes with textures.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;


public class Textures extends JPanel {


public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

g2d.setColor(new Color(212, 212, 212));
g2d.drawRect(10, 15, 90, 60);
g2d.drawRect(130, 15, 90, 60);
g2d.drawRect(250, 15, 90, 60);
g2d.drawRect(10, 105, 90, 60);
g2d.drawRect(130, 105, 90, 60);
g2d.drawRect(250, 105, 90, 60);

BufferedImage bimage1 = null;
BufferedImage bimage2 = null;
BufferedImage bimage3 = null;
BufferedImage bimage4 = null;
BufferedImage bimage5 = null;
BufferedImage bimage6 = null;

URL url1 = ClassLoader.getSystemResource("texture1.png");
URL url2 = ClassLoader.getSystemResource("texture2.png");
URL url3 = ClassLoader.getSystemResource("texture3.png");
URL url4 = ClassLoader.getSystemResource("texture4.png");
URL url5 = ClassLoader.getSystemResource("texture5.png");
URL url6 = ClassLoader.getSystemResource("texture6.png");

try {
bimage1 = ImageIO.read(url1);
bimage2 = ImageIO.read(url2);
bimage3 = ImageIO.read(url3);
bimage4 = ImageIO.read(url4);
bimage5 = ImageIO.read(url5);
bimage6 = ImageIO.read(url6);
} catch (IOException ioe) {
ioe.printStackTrace();
}

Rectangle rect1 = new Rectangle(0, 0,
bimage1.getWidth(), bimage1.getHeight());

Rectangle rect2 = new Rectangle(0, 0,
bimage2.getWidth(), bimage2.getHeight());

Rectangle rect3 = new Rectangle(0, 0,
bimage3.getWidth(), bimage3.getHeight());

Rectangle rect4 = new Rectangle(0, 0,
bimage4.getWidth(), bimage4.getHeight());

Rectangle rect5 = new Rectangle(0, 0,
bimage5.getWidth(), bimage5.getHeight());

Rectangle rect6 = new Rectangle(0, 0,
bimage6.getWidth(), bimage6.getHeight());

TexturePaint texture1 = new TexturePaint(bimage1, rect1);
TexturePaint texture2 = new TexturePaint(bimage2, rect2);
TexturePaint texture3 = new TexturePaint(bimage3, rect3);
TexturePaint texture4 = new TexturePaint(bimage4, rect4);
TexturePaint texture5 = new TexturePaint(bimage5, rect5);
TexturePaint texture6 = new TexturePaint(bimage6, rect6);

g2d.setPaint(texture1);
g2d.fillRect(10, 15, 90, 60);

g2d.setPaint(texture2);
g2d.fillRect(130, 15, 90, 60);

g2d.setPaint(texture3);
g2d.fillRect(250, 15, 90, 60);

g2d.setPaint(texture4);
g2d.fillRect(10, 105, 90, 60);

g2d.setPaint(texture5);
g2d.fillRect(130, 105, 90, 60);

g2d.setPaint(texture6);
g2d.fillRect(250, 105, 90, 60);
}

public static void main(String[] args) {

Textures rects = new Textures();
JFrame frame = new JFrame("Textures");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(rects);
frame.setSize(360, 210);
frame.setLocationRelativeTo(null);
frame.setVisible(true);

}
}
In our example, we will draw six rectangles filled with different textures. To work with textures, Java Swing has a TexturePaint class.
BufferedImage bimage1 = null;
...
URL url1 = ClassLoader.getSystemResource("texture1.png");
...
bimage1 = ImageIO.read(url1);
We read an image into the memory.
Rectangle rect1 = new Rectangle(0, 0,
bimage1.getWidth(), bimage1.getHeight());
We get the size of the texture image.
TexturePaint texture1 = new TexturePaint(bimage1, rect1);
Here we create a TexturePaint object. The parameters are a buffered image and a rectangle of the image. The rectangle is used to anchor and replicate the image. The images are tiled.
g2d.setPaint(texture1);
g2d.fillRect(10, 15, 90, 60);
Here we apply the texture and fill the rectangle with it.
Textures
Figure: Textures

Gradients

In computer graphics, gradient is a smooth blending of shades from light to dark or from one color to another. In 2D drawing programs and paint programs, gradients are used to create colorful backgrounds and special effects as well as to simulate lights and shadows. (answers.com)
package zetcode;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class DrawPanel extends JPanel {

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;
GradientPaint gp1 = new GradientPaint(5, 5,
Color.red, 20, 20, Color.black, true);

g2d.setPaint(gp1);
g2d.fillRect(20, 20, 300, 40);

GradientPaint gp2 = new GradientPaint(5, 25,
Color.yellow, 20, 2, Color.black, true);

g2d.setPaint(gp2);
g2d.fillRect(20, 80, 300, 40);

GradientPaint gp3 = new GradientPaint(5, 25,
Color.green, 2, 2, Color.black, true);

g2d.setPaint(gp3);
g2d.fillRect(20, 140, 300, 40);

GradientPaint gp4 = new GradientPaint(25, 25,
Color.blue, 15, 25, Color.black, true);

g2d.setPaint(gp4);
g2d.fillRect(20, 200, 300, 40);

GradientPaint gp5 = new GradientPaint(0, 0,
Color.orange, 0, 20, Color.black, true);

g2d.setPaint(gp5);
g2d.fillRect(20, 260, 300, 40);
}
}

public class GradientsExample extends JFrame {

public GradientsExample() {
initUI();
}

public final void initUI() {

DrawPanel dpnl = new DrawPanel();
add(dpnl);

setSize(350, 350);
setTitle("Gradients");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
GradientsExample ex = new GradientsExample();
ex.setVisible(true);
}
});
}
}
Our code example presents five rectangles with gradients.
GradientPaint gp4 = new GradientPaint(25, 25, 
Color.blue, 15, 25, Color.black, true);
To work with gradients, we use Java Swing's GradientPaint class. By manipulating the color values and the starting end ending points, we can get interesting results.
g2d.setPaint(gp5);
The gradient is activated calling the setPaint() method.
Gradients
Figure: Gradients

Drawing text

Drawing is done with the drawString() method. We specify the string we want to draw and the position of the text on the window area.
import java.awt.Font;
package zetcode;

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class DrawPanel extends JPanel {

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

rh.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);

g2d.setRenderingHints(rh);

Font font = new Font("URW Chancery L", Font.BOLD, 21);
g2d.setFont(font);

g2d.drawString("Not marble, nor the gilded monuments", 20, 30);
g2d.drawString("Of princes, shall outlive this powerful rhyme;", 20, 60);
g2d.drawString("But you shall shine more bright in these contents",
20, 90);
g2d.drawString("Than unswept stone, besmear'd with sluttish time.",
20, 120);
g2d.drawString("When wasteful war shall statues overturn,", 20, 150);
g2d.drawString("And broils root out the work of masonry,", 20, 180);
g2d.drawString("Nor Mars his sword, nor war's quick "
+ "fire shall burn", 20, 210);
g2d.drawString("The living record of your memory.", 20, 240);
g2d.drawString("'Gainst death, and all oblivious enmity", 20, 270);
g2d.drawString("Shall you pace forth; your praise shall still "
+ "find room", 20, 300);
g2d.drawString("Even in the eyes of all posterity", 20, 330);
g2d.drawString("That wear this world out to the ending doom.", 20, 360);
g2d.drawString("So, till the judgment that yourself arise,", 20, 390);
g2d.drawString("You live in this, and dwell in lovers' eyes.", 20, 420);
}
}

public class TextExample extends JFrame {

public TextExample() {
initUI();
}

public final void initUI() {

DrawPanel dpnl = new DrawPanel();
add(dpnl);

setSize(500, 470);
setTitle("Sonnet55");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
TextExample ex = new TextExample();
ex.setVisible(true);
}
});
}
}
In our example, we draw a sonnet on the panel component.
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

rh.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);

g2d.setRenderingHints(rh);
This code is to make our text look better. We apply a technique called antialiasing.
Font font = new Font("URW Chancery L", Font.BOLD, 21);
g2d.setFont(font);
We choose a nice font for our text.
g2d.drawString("Not marble, nor the gilded monuments", 20, 30);
This is the code, that draws the text.

Images

On of the most important capabililies of a toolkit is the ability to display images. An image is an array of pixels. Each pixel represents a color at a given position. We can use components like JLabel to display an image, or we can draw it using the Java 2D API.
package zetcode;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class DrawPanel extends JPanel {

Image img;

public DrawPanel(Image img) {
this.img = img;
Dimension dm = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(dm);
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;

g2d.drawImage(this.img, 0, 0, null);
}
}

public class ImageExample extends JFrame {

public ImageExample() {
initUI();
}

public final void initUI() {

Image img = new ImageIcon("slanec.png").getImage();

DrawPanel dpnl = new DrawPanel(img);
add(dpnl);

setTitle("Image");
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {
ImageExample ex = new ImageExample();
ex.setVisible(true);
}
});
}
}
This example will draw an image on the panel. The image will fit the JFrame window.
public DrawPanel(Image img) {
this.img = img;
Dimension dm = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(dm);
}
We determine the image dimensions and set the preffered size of the panel component. This will together with the pack()method display the image to fit exactly the window.
g2d.drawImage(this.img, 0, 0, null);
The image is drawn using the drawImage() method.
Image img = new ImageIcon("slanec.png").getImage();
We load the image using the ImageIcon class. This class simplyfies the work with the images in Java Swing.
In this chapter, we did some painting.
Continue Reading

Drag and Drop in Java Swing

Drag and Drop in Swing

In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects. (Wikipedia)
Drag and drop functionality is one of the most visible aspects of the graphical user interface. Drag and drop operation enables users to do complex things intuitively.
Usually, we can drag and drop two things. Data or some graphical objects. If we drag an image from one application to another, we drag and drop binary data. If we drag a tab in Firefox and move it to another place, we drag and drop a graphical component.
The sheer amount of various classes involved with drag and drop operations in Java Swing toolkit might be overwhelming. The best way how to cope with this complexity is to create a small example for all situations. And slowly make it to more complex examples.
Drag and drop The component, where the drag operation begins must have a DragSource object registered. A DropTarget is an object responsible for accepting drops in an drag and drop operation. A Transferable encapsulates data being transferred. The transferred data can be of various type. A DataFlavor object provides information about the data being transferred.
Several Swing components have already a built-in support for drag and drop operations. In such cases, a Swing programmer uses a TransferHandler to manage the drag and drop functionality. In situations, where there is no built-in support, the programmer has to create everything from scratch.

A simple drag and drop example

We will demonstrate a simple drag and drop example. We will work with built-in drag and drop support. We will utilize a TransferHandler class.
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.TransferHandler;


public class SimpleDnD extends JFrame {

JTextField field;
JButton button;

public SimpleDnD() {

setTitle("Simple Drag & Drop");

setLayout(null);

button = new JButton("Button");
button.setBounds(200, 50, 90, 25);

field = new JTextField();
field.setBounds(30, 50, 150, 25);

add(button);
add(field);

field.setDragEnabled(true);
button.setTransferHandler(new TransferHandler("text"));

setSize(330, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
new SimpleDnD();
}
}
In our example we have a text field and a button. We can drag a text from the field and drop it onto the button.
field.setDragEnabled(true);
The text field has a built in support for dragging. We must enable it.
button.setTransferHandler(new TransferHandler("text"));
The TransferHandler is a class responsible for transfering data between components. The constructor takes a property name as a parameter.
Simple drag & drop example
Figure: Simple drag & drop example

Icon drag & drop

Some of the Java Swing components do not have built in drag support. JLabel component is such a component. We have to code the drag functionality ourselves.
We will drag and drop icons. In the previous example, we used a text property. This time we will use an icon property.
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;


public class IconDnD extends JFrame {


public IconDnD() {

setTitle("Icon Drag & Drop");

JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 50, 15));

ImageIcon icon1 = new ImageIcon("sad.png");
ImageIcon icon2 = new ImageIcon("plain.png");
ImageIcon icon3 = new ImageIcon("crying.png");

JButton button = new JButton(icon2);
button.setFocusable(false);

JLabel label1 = new JLabel(icon1, JLabel.CENTER);
JLabel label2 = new JLabel(icon3, JLabel.CENTER);

MouseListener listener = new DragMouseAdapter();
label1.addMouseListener(listener);
label2.addMouseListener(listener);

label1.setTransferHandler(new TransferHandler("icon"));
button.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));

panel.add(label1);
panel.add(button);
panel.add(label2);
add(panel);

pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}

class DragMouseAdapter extends MouseAdapter {
public void mousePressed(MouseEvent e) {
JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);
}
}

public static void main(String[] args) {
new IconDnD();
}
}
In the code example, we have two labels and a button. Each component displays an icon. The two labels enable drag gestures, the button accepts a drop gesture.
MouseListener listener = new DragMouseAdapter();
label1.addMouseListener(listener);
label2.addMouseListener(listener);
The drag support is not enabled by default for the label. We register a custom mouse adapter for both labels.
label1.setTransferHandler(new TransferHandler("icon"));
button.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));
Each of the three components has a TransferHandler class for an icon property. The TransferHandler is needed for both drag sources and drag targets as well.
JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);
These code lines initiate the drag support. We get the drag source. In our case it is a label instance. We get it's transfer handler object. And finally initiate the drag support with the exportAsDrag()method call.
Icon drag & drop example
Figure: Icon drag & drop example

Custom JList drop example

Some components do not have a default drop support. One of them is a JList component. There is a good reason for this. We don't know, if the data will be inserted into one row, or two or more rows. So we must implement manually the drop support for the list component.
The comma separated text will be inserted into two or more rows. Text without a comma will go into one row.
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;


public class ListDrop extends JFrame {

JTextField field;
DefaultListModel model;

public ListDrop() {

setTitle("ListDrop");

JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 15));

JScrollPane pane = new JScrollPane();
pane.setPreferredSize(new Dimension(180, 150));

model = new DefaultListModel();
JList list = new JList(model);

list.setDropMode(DropMode.INSERT);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setTransferHandler(new ListHandler());

field = new JTextField("");
field.setPreferredSize(new Dimension(150, 25));
field.setDragEnabled(true);

panel.add(field);
pane.getViewport().add(list);
panel.add(pane);

add(panel);

pack();

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}


private class ListHandler extends TransferHandler {
public boolean canImport(TransferSupport support) {
if (!support.isDrop()) {
return false;
}

return support.isDataFlavorSupported(DataFlavor.stringFlavor);
}

public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}

Transferable transferable = support.getTransferable();
String line;
try {
line = (String) transferable.getTransferData(DataFlavor.stringFlavor);
} catch (Exception e) {
return false;
}

JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
int index = dl.getIndex();

String[] data = line.split(",");
for (String item: data) {
if (!item.isEmpty())
model.add(index++, item.trim());
}
return true;
}
}

public static void main(String[] args) {
new ListDrop();
}
}
In the above example, we have a text field and a list component. The text in the text field can be dragged and dropped into the list. If the text is comma separated, the words will be split into rows. If not, the text will be inserted into one row.
list.setDropMode(DropMode.INSERT);
Here we specify a drop mode. The DropMode.INSERT specifies, that we are going to insert new items into the list component. If we chose DropMode.INSERT, we would drop new items onto the existing ones.
list.setTransferHandler(new ListHandler());
We set a custom transfer handler class.
field.setDragEnabled(true);
We enable the drag support for the text field component.
public boolean canImport(TransferSupport support) {
if (!support.isDrop()) {
return false;
}
return support.isDataFlavorSupported(DataFlavor.stringFlavor);
}
This method tests suitability of a drop operation. Here we filter out the clipboard paste operations and allow only String drop operations. If the method returns false, the drop operation is cancelled.
public boolean importData(TransferSupport support) {
...
}
The importData() method transfers the data from the clipboard or from the drag and drop operation to the drop location.
Transferable transferable = support.getTransferable();
The Transferable is the class, where the data is bundled.
line = (String) transferable.getTransferData(DataFlavor.stringFlavor);
We retrieve our data.
JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
int index = dl.getIndex();
We get a drop location for the list. We retrieve the index, where the data will be inserted.
String[] data = line.split(",");
for (String item: data) {
if (!item.isEmpty())
model.add(index++, item.trim());
}
Here we split the text into parts and insert it into one or more rows.
JList drop example
Figure: JList drop example
The previous examples used components with built-in drag and drop support. Next we are going to create a drag and drop functionality from scratch.

Drag Gesture

In the following example we will inspect a simple drag gesture. We will work with several classes needed to create a drag gesture. A DragSource, DragGestureEvent, DragGestureListener, Transferable.
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class DragGesture extends JFrame implements
DragGestureListener, Transferable {

public DragGesture() {

setTitle("Drag Gesture");

JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 50, 15));

JPanel left = new JPanel();
left.setBackground(Color.red);
left.setPreferredSize(new Dimension(120, 120));

DragSource ds = new DragSource();
ds.createDefaultDragGestureRecognizer(left,
DnDConstants.ACTION_COPY, this);

panel.add(left);
add(panel);

pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}

public void dragGestureRecognized(DragGestureEvent event) {
System.out.println("grag gesture");
Cursor cursor = null;
if (event.getDragAction() == DnDConstants.ACTION_COPY) {
cursor = DragSource.DefaultCopyDrop;
}
event.startDrag(cursor, this);
}

public static void main(String[] args) {
new DragGesture();
}

public Object getTransferData(DataFlavor flavor) {
return null;
}

public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[0];
}

public boolean isDataFlavorSupported(DataFlavor flavor) {
return false;
}
}
This simple example demostrates a drag gesture. The drag gesture is created, when we click on a component and move a mouse pointer, while the button is pressed. The example will show, how we can create a DragSource for a component.
public class DragGesture extends JFrame implements 
DragGestureListener, Transferable {
The DragGesture implements two interfaces. The DragGestureListener will listen for drag gestures. The Transferable handles data for a transfer operation. In the example, we will not transfer any data. We will only demonstrate a drag gesture. So the three necessary methods of the Transferable interface are left unimplemented.
DragSource ds = new DragSource();
ds.createDefaultDragGestureRecognizer(left,
DnDConstants.ACTION_COPY, this);
Here we create a DragSource object and register it for the left panel. The DragSource is the entity responsible for the initiation of the Drag and Drop operation. The createDefaultDragGestureRecognizer() associates a drag source and DragGestureListener with a particular component.
public void dragGestureRecognized(DragGestureEvent event) {

}
The dragGestureRecognized() method responds to a drag gesture.
Cursor cursor = null;
if (event.getDragAction() == DnDConstants.ACTION_COPY) {
cursor = DragSource.DefaultCopyDrop;
}
event.startDrag(cursor, this);
The startDrag() method of the DragGestureEvent finally starts the drag operation. We will specify two parameters. The cursor type and the Transferable object.
public Object getTransferData(DataFlavor flavor) {
return null;
}

public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[0];
}

public boolean isDataFlavorSupported(DataFlavor flavor) {
return false;
}
The object that implements the Transferable interface must implement these three methods. As I have already mentioned, we left these methods unimplemented for now.

A complex drag and drop example

In the following example, we create a complex drag and drop example. We create a drag source a drop target and a transferable object.
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class ComplexExample extends JFrame
implements DragGestureListener {

JPanel panel;
JPanel left;

public ComplexExample() {

setTitle("Complex Example");

panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 50, 15));

JButton openb = new JButton("Choose Color");
openb.setFocusable(false);

left = new JPanel();
left.setBackground(Color.red);
left.setPreferredSize(new Dimension(100, 100));

openb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
JColorChooser clr = new JColorChooser();
Color color = clr.showDialog(panel, "Choose Color", Color.white);
left.setBackground(color);
}
});

JPanel right = new JPanel();
right.setBackground(Color.white);
right.setPreferredSize(new Dimension(100, 100));

new MyDropTargetListener(right);

DragSource ds = new DragSource();
ds.createDefaultDragGestureRecognizer(left,
DnDConstants.ACTION_COPY, this);

panel.add(openb);
panel.add(left);
panel.add(right);
add(panel);

pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}

public void dragGestureRecognized(DragGestureEvent event) {
Cursor cursor = null;
JPanel panel = (JPanel) event.getComponent();

Color color = panel.getBackground();

if (event.getDragAction() == DnDConstants.ACTION_COPY) {
cursor = DragSource.DefaultCopyDrop;
}

event.startDrag(cursor, new TransferableColor(color));
}

class MyDropTargetListener extends DropTargetAdapter {

private DropTarget dropTarget;
private JPanel panel;

public MyDropTargetListener(JPanel panel) {
this.panel = panel;

dropTarget = new DropTarget(panel, DnDConstants.ACTION_COPY,
this, true, null);
}


public void drop(DropTargetDropEvent event) {
try {

Transferable tr = event.getTransferable();
Color color = (Color) tr.getTransferData(TransferableColor.colorFlavor);

if (event.isDataFlavorSupported(TransferableColor.colorFlavor)) {

event.acceptDrop(DnDConstants.ACTION_COPY);
this.panel.setBackground(color);
event.dropComplete(true);
return;
}
event.rejectDrop();
} catch (Exception e) {
e.printStackTrace();
event.rejectDrop();
}
}
}

public static void main(String[] args) {
new ComplexExample();
}
}


class TransferableColor implements Transferable {

protected static DataFlavor colorFlavor =
new DataFlavor(Color.class, "A Color Object");

protected static DataFlavor[] supportedFlavors = {
colorFlavor,
DataFlavor.stringFlavor,
};

Color color;

public TransferableColor(Color color) { this.color = color; }

public DataFlavor[] getTransferDataFlavors() { return supportedFlavors; }

public boolean isDataFlavorSupported(DataFlavor flavor) {
if (flavor.equals(colorFlavor) ||
flavor.equals(DataFlavor.stringFlavor)) return true;
return false;
}


public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException
{
if (flavor.equals(colorFlavor))
return color;
else if (flavor.equals(DataFlavor.stringFlavor))
return color.toString();
else
throw new UnsupportedFlavorException(flavor);
}
}
The code example shows a button and two panels. The button displays a color chooser dialog and sets a color for the first panel. The color can be dragged into the second panel.
This example will enhance the previous one. We will add a drop target and a custom transferable object.
new MyDropTargetListener(right);
We register a drop target listener with the right panel.
event.startDrag(cursor, new TransferableColor(color));
The startDrag() method has two parameters. A cursor and a Transferable object.
public MyDropTargetListener(JPanel panel) {
this.panel = panel;

dropTarget = new DropTarget(panel, DnDConstants.ACTION_COPY,
this, true, null);
}
In the MyDropTargetListener we create a drop target object.
Transferable tr = event.getTransferable();
Color color = (Color) tr.getTransferData(TransferableColor.colorFlavor);

if (event.isDataFlavorSupported(TransferableColor.colorFlavor)) {

event.acceptDrop(DnDConstants.ACTION_COPY);
this.panel.setBackground(color);
event.dropComplete(true);
return;
}
We get the data being transferred. In our case it is a color object. Here we set the color of the right panel.
event.rejectDrop();
If the conditions for a drag and drop operation are not fulfilled, we reject it.
protected static DataFlavor colorFlavor =
new DataFlavor(Color.class, "A Color Object");
In the TransferableColor, we create a new DataFlavor object.
protected static DataFlavor[] supportedFlavors = {
colorFlavor,
DataFlavor.stringFlavor,
};
Here we specify, what data flavors we support. In our case it is a custom defined color flavor and a pre-defined DataFlavor.stringFlavor.
public Object getTransferData(DataFlavor flavor) 
throws UnsupportedFlavorException
{
if (flavor.equals(colorFlavor))
return color;
else if (flavor.equals(DataFlavor.stringFlavor))
return color.toString();
else
throw new UnsupportedFlavorException(flavor);
}
Return an object for a specific data flavor.
A complex example
Figure: A complex example
This part of the Java Swing tutorial was dedicated to Swing drap and drop operations.
Continue Reading