Is there any way to create a feature which allows a user to toggle screen magnification if they are visually impaired.
I'm asking in context of a program like eclipse, where a user who is visually impaired can toggle on and off a feature which magnifies the text, icons and navigation bar.
I use this short code for a general magnifier when cropping images.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class ZoomOnMouse {
Robot robot;
int zoomFactor = 2;
PointerInfo pi;
JPanel gui;
JLabel output;
Timer t;
public ZoomOnMouse() throws AWTException {
robot = new Robot();
gui = new JPanel(new BorderLayout(2, 2));
output = new JLabel("Point at something to see it zoomed!");
gui.add(output, BorderLayout.PAGE_END);
final int size = 256;
final BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_RGB);
final JLabel zoomLabel = new JLabel(new ImageIcon(bi));
gui.add(zoomLabel, BorderLayout.CENTER);
MouseListener factorListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (zoomFactor == 2) {
zoomFactor = 4;
} else if (zoomFactor == 4) {
zoomFactor = 8;
} else if (zoomFactor == 8) {
zoomFactor = 2;
}
showInfo();
}
};
zoomLabel.addMouseListener(factorListener);
ActionListener zoomListener = (ActionEvent e) -> {
pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Rectangle r = new Rectangle(
p.x - (size / (2 * zoomFactor)),
p.y - (size / (2 * zoomFactor)),
(size / zoomFactor),
(size / zoomFactor));
BufferedImage temp = robot.createScreenCapture(r);
Graphics g = bi.getGraphics();
g.drawImage(temp, 0, 0, size, size, null);
g.setColor(new Color(255,0,0,128));
int x = (size/2)-1;
int y = (size/2)-1;
g.drawLine(0,y,size,y);
g.drawLine(x,0,x,size);
g.dispose();
zoomLabel.repaint();
showInfo();
};
t = new Timer(40, zoomListener);
t.start();
}
public void stop() {
t.stop();
}
public Component getGui() {
return gui;
}
public void showInfo() {
pi = MouseInfo.getPointerInfo();
output.setText("Zoom: " + zoomFactor + " Point: " + pi.getLocation());
}
public static void main(String[] args) {
Runnable r = () -> {
try {
final ZoomOnMouse zm = new ZoomOnMouse();
final JFrame f = new JFrame("Mouse Zoom");
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.add(zm.getGui());
f.setResizable(false);
f.pack();
f.setLocationByPlatform(true);
f.setAlwaysOnTop(true);
f.setVisible(true);
WindowListener closeListener = new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
zm.stop();
f.dispose();
}
};
f.addWindowListener(closeListener);
} catch (AWTException e) {
e.printStackTrace();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Related
I am creating a rectangle with my mouse and i'm just trying to return info from the created object with a mouseover. I've been solving this with a messabox but there's just not many country to mouseover so the number of popup can be monstrous.
I decided to use a tooltip. Thing is it's not working on my mouse over :
JTextField text = new JTextField();
if (coordX > coordXRec && coordX < coordXRec + width && coordY > coordYrec && coordY < coordYrec + height){
text.setToolTipText(i.GetInfoPays());
text.getToolTipText();
}
Naturally, i want it to show when i mouseover my rectangle created :
That green rectangle was made by the user so i can't 'preset' an event
or use it as a panel for my tooltip.
This is what excepted, but using a tooltiptext, not a messabox.
I've been using a MouseEventMoved to know if im hovering over my rectangle. Its working but i'm stuck at sort of changing my messagebox into a tooltip.
Use setToolTipText("..."); with the appropriate String when needed (e.g. during paintComponent()).
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.util.*;
public class MouseShapeDetection {
private JComponent ui = null;
MouseShapeDetection() {
initUI();
}
public final void initUI() {
if (ui != null) {
return;
}
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
ui.add(new ShapePanel());
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
MouseShapeDetection o = new MouseShapeDetection();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
class ShapePanel extends JPanel {
Point point = new Point(0, 0);
Dimension preferredSize = new Dimension(600, 300);
ArrayList<Shape> shapes = new ArrayList<>();
Color translucent = new Color(0,0,255,87);
Color selectedColor = Color.GREEN;
Color unselectedColor = Color.RED;
public ShapePanel() {
this.addMouseMotionListener(new MotionListener());
setBackground(Color.WHITE);
Random r = new Random();
int x, y, w, h, wP = preferredSize.width, hP = preferredSize.height;
for (int ii = 0; ii < 40; ii++) {
w = r.nextInt(100)+40;
h = r.nextInt(50)+20;
x = r.nextInt(wP - w);
y = r.nextInt(hP - h);
Ellipse2D.Double ellipse = new Ellipse2D.Double(x, y, w, h);
shapes.add(ellipse);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
StringBuilder sb = new StringBuilder("Shapes: ");
Graphics2D g2 = (Graphics2D)g;
for (int ii=0; ii<shapes.size(); ii++) {
Shape shape = shapes.get(ii);
g2.setColor(translucent);
g2.fill(shape);
if (shape.contains(point)) {
g2.setColor(selectedColor);
sb.append(ii);
sb.append(" ");
} else {
g2.setColor(unselectedColor);
}
g2.setStroke(new BasicStroke(2.5f));
g2.draw(shape);
}
setToolTipText(sb.toString());
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
class MotionListener extends MouseMotionAdapter {
#Override
public void mouseMoved(MouseEvent e) {
point = e.getPoint();
repaint();
}
}
}
I have difficulties in separating the logic of the game from the view. The view should be dumb and simply render the current state of the model. But I can not figure out how to do that. Could you please help me in making the code simpler and separating the logic from the form? Because I want to extend the game and add new things but I can not because of the confusing form of the code. I am new in java.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
public class ProjectileShooterTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(600, 600);
final ProjectileShooter projectileShooter = new ProjectileShooter();
ProjectileShooterPanel projectileShooterPanel = new ProjectileShooterPanel(
projectileShooter);
projectileShooter.setPaintingComponent(projectileShooterPanel);
JPanel controlPanel = new JPanel(new GridLayout(1, 0));
controlPanel.add(new JLabel("Angle"));
final JSlider angleSlider = new JSlider(0, 90, 45);
controlPanel.add(angleSlider);
controlPanel.add(new JLabel("Power"));
final JSlider powerSlider = new JSlider(0, 100, 50);
controlPanel.add(powerSlider);
JButton shootButton = new JButton("Shoot");
shootButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int angleDeg = angleSlider.getValue();
int power = powerSlider.getValue();
projectileShooter.setAngle(Math.toRadians(angleDeg));
projectileShooter.setPower(power);
projectileShooter.shoot();
}
});
controlPanel.add(shootButton);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(controlPanel, BorderLayout.NORTH);
f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
f.setVisible(true);
}
}
class ProjectileShooter {
private double angleRad = Math.toRadians(45);
private double power = 50;
private Projectile projectile;
private JComponent paintingComponent;
void setPaintingComponent(JComponent paintingComponent) {
this.paintingComponent = paintingComponent;
}
void setAngle(double angleRad) {
this.angleRad = angleRad;
}
void setPower(double power) {
this.power = power;
}
void shoot() {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
executeShot();
}
});
t.setDaemon(true);
t.start();
}
private void executeShot() {
if (projectile != null) {
return;
}
projectile = new Projectile();
Point2D velocity = AffineTransform.getRotateInstance(angleRad).transform(
new Point2D.Double(1, 0), null);
velocity.setLocation(velocity.getX() * power * 0.5, velocity.getY() * power * 0.5);
projectile.setVelocity(velocity);
long prevTime = System.nanoTime();
while (projectile.getPosition().getY() >= 0) {
long currentTime = System.nanoTime();
double dt = 3 * (currentTime - prevTime) / 1e8;
projectile.performTimeStep(dt);
prevTime = currentTime;
paintingComponent.repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
projectile = null;
paintingComponent.repaint();
}
Projectile getProjectile() {
return projectile;
}
}
class Projectile {
private final Point2D ACCELERATION = new Point2D.Double(0, -9.81 * 0.1);
private final Point2D position = new Point2D.Double();
private final Point2D velocity = new Point2D.Double();
public Point2D getPosition() {
return new Point2D.Double(position.getX(), position.getY());
}
public void setPosition(Point2D point) {
position.setLocation(point);
}
public void setVelocity(Point2D point) {
velocity.setLocation(point);
}
void performTimeStep(double dt) {
scaleAddAssign(velocity, dt, ACCELERATION);
scaleAddAssign(position, dt, velocity);
}
private static void scaleAddAssign(Point2D result, double factor, Point2D addend) {
double x = result.getX() + factor * addend.getX();
double y = result.getY() + factor * addend.getY();
result.setLocation(x, y);
}
}
class ProjectileShooterPanel extends JPanel {
private final ProjectileShooter projectileShooter;
public ProjectileShooterPanel(ProjectileShooter projectileShooter) {
this.projectileShooter = projectileShooter;
}
#Override
protected void paintComponent(Graphics gr) {
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
Projectile projectile = projectileShooter.getProjectile();
if (projectile != null) {
g.setColor(Color.RED);
Point2D position = projectile.getPosition();
int x = (int) position.getX();
int y = getHeight() - (int) position.getY();
Ellipse2D.Double gerd = new Ellipse2D.Double(x - 01, y - 10, 20, 20);
g.draw(gerd);
// g.fillOval(x-01, y-10, 20, 20);
}
Rectangle hadaf1 = new Rectangle(450, 450, 50, 50);
Rectangle hadaf2 = new Rectangle(500, 450, 50, 50);
Rectangle hadaf3 = new Rectangle(475, 400, 50, 50);
g.draw(hadaf1);
g.draw(hadaf2);
g.draw(hadaf3);
}
}
I will be so thankful.
I hope some one can help me, this is what I want to do.
I have a JTextPane and I want to take a screenshot to that specific JTextPane coordinates and size, so far I can do a screenshot with the size of the JTextPane but I can't get the specific coordinates my screenshots always gets the (0,0) coordinates.
This is my method:
void capturaPantalla ()
{
try
{
int x = txtCodigo.getX();
int y = txtCodigo.getY();
Rectangle areaCaptura = new Rectangle(x, y, txtCodigo.getWidth(), txtCodigo.getHeight());
BufferedImage capturaPantalla = new Robot().createScreenCapture(areaCaptura);
File ruta = new File("P:\\captura.png");
ImageIO.write(capturaPantalla, "png", ruta);
JOptionPane.showMessageDialog(null, "Codigo de barras guardado!");
}
catch (IOException ioe)
{
System.out.println(ioe);
}
catch(AWTException ex)
{
System.out.println(ex);
}
}
When you call getX() and getY() on any Swing component, you get the x and y relative to the component's container, not the screen. Instead you want the location of the component relative to the screen and get position based on that via getLocationOnScreen()
Point p = txtCodigo.getLocationOnScreen();
int x = p.x;
int y = p.y;
As per MadProgrammer's comment, you could simply call printAll(Graphics g) on your txtCodigo component, passing in a Graphics object obtained from a properly sized BufferedImage and forgo use of a Robot.
Dimension d = txtCodigo.getSize();
BufferedImage img = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
txtCodigo.printAll(g);
g.dispose();
// use ImageIO to write BufferedImage to file
To compare to the two methods:
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class RobotVsPrintAll extends JPanel {
private static final int WORDS = 400;
private JTextArea textArea = new JTextArea(20, 40);
private JScrollPane scrollPane = new JScrollPane(textArea);
private Random random = new Random();
public RobotVsPrintAll() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < WORDS; i++) {
int wordLength = random.nextInt(4) + 4;
for (int j = 0; j < wordLength; j++) {
char myChar = (char) (random.nextInt('z' - 'a' + 1) + 'a');
sb.append(myChar);
}
sb.append(" ");
}
textArea.setText(sb.toString());
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JButton robot1Btn = new JButton(new Robot1Action("Robot 1"));
JButton robot2Btn = new JButton(new Robot2Action("Robot 2"));
JButton printAllBtn = new JButton(new PrintAllAction("Print All"));
JPanel btnPanel = new JPanel();
btnPanel.add(robot1Btn);
btnPanel.add(robot2Btn);
btnPanel.add(printAllBtn);
setLayout(new BorderLayout());
add(scrollPane, BorderLayout.CENTER);
add(btnPanel, BorderLayout.PAGE_END);
}
private void displayImg(BufferedImage img) {
ImageIcon icon = new ImageIcon(img);
JOptionPane.showMessageDialog(this, icon, "Display Image",
JOptionPane.PLAIN_MESSAGE);
}
private class Robot1Action extends AbstractAction {
public Robot1Action(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
try {
Component comp = scrollPane.getViewport();
Point p = comp.getLocationOnScreen();
Dimension d = comp.getSize();
Robot robot = new Robot();
Rectangle screenRect = new Rectangle(p.x, p.y, d.width, d.height);
BufferedImage img = robot.createScreenCapture(screenRect);
displayImg(img);
} catch (AWTException e1) {
e1.printStackTrace();
}
}
}
private class Robot2Action extends AbstractAction {
public Robot2Action(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
try {
Component comp = textArea;
Point p = comp.getLocationOnScreen();
Dimension d = comp.getSize();
Robot robot = new Robot();
Rectangle screenRect = new Rectangle(p.x, p.y, d.width, d.height);
BufferedImage img = robot.createScreenCapture(screenRect);
displayImg(img);
} catch (AWTException e1) {
e1.printStackTrace();
}
}
}
private class PrintAllAction extends AbstractAction {
public PrintAllAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(ActionEvent e) {
Dimension d = textArea.getSize();
BufferedImage img = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
textArea.printAll(g);
g.dispose();
displayImg(img);
}
}
private static void createAndShowGui() {
RobotVsPrintAll mainPanel = new RobotVsPrintAll();
JFrame frame = new JFrame("Robot Vs PrintAll");
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();
}
});
}
}
If you print the text component with printAll, you get the entire text component, even the parts that are not displayed in the JScrollPane's viewport.
You can use the Screen Image class which does all the work for you. All you do is specify the component you want to capture.
The code would be:
BufferedImage bi = ScreenImage.createImage( component );
And you can save the image to a file using:
ScreenImage.writeImage(bi, "imageName.jpg");
This class will use the painting method of the Swing component which is more efficient than using a Robot.
What I want my app to do:
1 - Select an area of Image and get the coordinates. This code below should do this:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class ScreenCaptureRectangle {
Rectangle captureRect;
ScreenCaptureRectangle(final BufferedImage screen) {
final BufferedImage screenCopy = new BufferedImage(
screen.getWidth(),
screen.getHeight(),
screen.getType());
final JLabel screenLabel = new JLabel(new ImageIcon(screenCopy));
JScrollPane screenScroll = new JScrollPane(screenLabel);
screenScroll.setPreferredSize(new Dimension(
(int)(screen.getWidth()/3),
(int)(screen.getHeight()/3)));
JPanel panel = new JPanel(new BorderLayout());
panel.add(screenScroll, BorderLayout.CENTER);
final JLabel selectionLabel = new JLabel(
"Drag a rectangle in the screen shot!");
panel.add(selectionLabel, BorderLayout.SOUTH);
repaint(screen, screenCopy);
screenLabel.repaint();
screenLabel.addMouseMotionListener(new MouseMotionAdapter() {
Point start = new Point();
#Override
public void mouseMoved(MouseEvent me) {
start = me.getPoint();
repaint(screen, screenCopy);
selectionLabel.setText("Start Point: " + start);
screenLabel.repaint();
}
#Override
public void mouseDragged(MouseEvent me) {
Point end = me.getPoint();
captureRect = new Rectangle(start,
new Dimension(end.x-start.x, end.y-start.y));
repaint(screen, screenCopy);
screenLabel.repaint();
selectionLabel.setText("Rectangle: " + captureRect);
}
});
JOptionPane.showMessageDialog(null, panel);
System.out.println("Rectangle of interest: " + captureRect);
}
public void repaint(BufferedImage orig, BufferedImage copy) {
Graphics2D g = copy.createGraphics();
g.drawImage(orig,0,0, null);
if (captureRect!=null) {
g.setColor(Color.RED);
g.draw(captureRect);
g.setColor(new Color(255,255,255,150));
g.fill(captureRect);
}
g.dispose();
}
public static void main(String[] args) throws Exception {
Robot robot = new Robot();
final Dimension screenSize = Toolkit.getDefaultToolkit().
getScreenSize();
final BufferedImage screen = robot.createScreenCapture(
new Rectangle(screenSize));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ScreenCaptureRectangle(screen);
}
});
}
}
2 - get the coordinates and use it on getSubimage method.
double w = captureRect.getWidth();
double h = captureRect.getHeight();
double x = captureRect.getX();
double y = captureRect.getY();
int W = (int) w;
int H = (int) h;
int X = (int) x;
int Y = (int) y;
BufferedImage selectImg = screen.getSubimage(x, y, w, h);
3 - this code create a new image file and copy the imageselected.
BufferedImage img = new BufferedImage ( 5000, 5000, BufferedImage.TYPE_INT_RGB );
img.createGraphics().drawImage(selectImg, 0, 0, null);
File final_image = new File("C:/Final.jpg");
ImageIO.write(img, "jpeg", final_image);
The idea of app is:
- Select an area of the image.
- Copy that image and paste in other file. ( when I pressed any button )
- The program will continue run until I press another button.
- Every image that I copy the program will paste it beside the last one.
I think I am near to the solution. Can any one help me to "connect the parts" ?
Start by taking a look at:
How to Write a Mouse Listener
How to Use Buttons, Check Boxes, and Radio Buttons
How to Write an Action Listeners
Performing Custom Painting
Writing/Saving an Image
You need to take the concepts you have and rework them into a coherent workable solution. That is, provide functionality between the areas you need (selecting a region and saving the file) so that they work cleanly together...
The following example takes a screenshot, allows you to select an area, click save and the file will be saved. The example checks to see how many files are already in the current directory and increments the count by 1 so you are not overwriting the existing files...
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ScreenImage {
public static void main(String[] args) {
new ScreenImage();
}
public ScreenImage() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
Robot robot = new Robot();
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
final BufferedImage screen = robot.createScreenCapture(new Rectangle(screenSize));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(screen));
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (AWTException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage master;
public TestPane(BufferedImage image) {
this.master = image;
setLayout(new BorderLayout());
final ImagePane imagePane = new ImagePane(image);
add(new JScrollPane(imagePane));
JButton btnSave = new JButton("Save");
add(btnSave, BorderLayout.SOUTH);
btnSave.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
BufferedImage img = imagePane.getSubImage();
master = append(master, img);
File save = new File("Capture.png");
ImageIO.write(master, "png", save);
imagePane.clearSelection();
JOptionPane.showMessageDialog(TestPane.this, save.getName() + " was saved", "Saved", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(TestPane.this, "Failed to save capture", "Error", JOptionPane.ERROR_MESSAGE);
}
}
public BufferedImage append(BufferedImage master, BufferedImage sub) {
// Create a new image which can hold both background and the
// new image...
BufferedImage newImage = new BufferedImage(
master.getWidth() + sub.getWidth(),
Math.max(master.getHeight(), sub.getHeight()),
BufferedImage.TYPE_INT_ARGB);
// Get new image's Graphics context
Graphics2D g2d = newImage.createGraphics();
// Draw the old background
g2d.drawImage(master, 0, 0, null);
// Position and paint the new sub image...
int y = (newImage.getHeight() - sub.getHeight()) / 2;
g2d.drawImage(sub, master.getWidth(), y, null);
g2d.dispose();
return newImage;
}
});
}
}
public class ImagePane extends JPanel {
private BufferedImage background;
private Rectangle selection;
public ImagePane(BufferedImage img) {
background = img;
MouseAdapter ma = new MouseAdapter() {
private Point clickPoint;
#Override
public void mousePressed(MouseEvent e) {
clickPoint = e.getPoint();
}
#Override
public void mouseDragged(MouseEvent e) {
Point dragPoint = e.getPoint();
int x = Math.min(clickPoint.x, dragPoint.x);
int y = Math.min(clickPoint.y, dragPoint.y);
int width = Math.abs(clickPoint.x - dragPoint.x);
int height = Math.abs(clickPoint.y - dragPoint.y);
selection = new Rectangle(x, y, width, height);
repaint();
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
public void clearSelection() {
selection = null;
repaint();
}
public BufferedImage getSubImage() {
BufferedImage img = null;
if (selection != null) {
img = background.getSubimage(selection.x, selection.y, selection.width, selection.height);
}
return img;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(background.getWidth(), background.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.drawImage(background, x, y, this);
if (selection != null) {
Color stroke = UIManager.getColor("List.selectionBackground");
Color fill = new Color(stroke.getRed(), stroke.getGreen(), stroke.getBlue(), 128);
g2d.setColor(fill);
g2d.fill(selection);
g2d.setColor(stroke);
g2d.draw(selection);
}
g2d.dispose();
}
}
}
So apart from rendering the selection the hardest part would be generating the resulting image...
Basically, this done by creating a new BufferedImage and painting the old image and the new, sub, image together.
public BufferedImage append(BufferedImage master, BufferedImage sub) {
// Create a new image which can hold both background and the
// new image...
BufferedImage newImage = new BufferedImage(
master.getWidth() + sub.getWidth(),
Math.max(master.getHeight(), sub.getHeight()),
BufferedImage.TYPE_INT_ARGB);
// Get new image's Graphics context
Graphics2D g2d = newImage.createGraphics();
// Draw the old background
g2d.drawImage(master, 0, 0, null);
// Position and paint the new sub image...
int y = (newImage.getHeight() - sub.getHeight()) / 2;
g2d.drawImage(sub, master.getWidth(), y, null);
g2d.dispose();
return newImage;
}
The example replaces the previous (master) image with the one created here, so it will constantly be appending new images to the end of it...
You need more listeners for button pressed and released.. some lines in the mouseMoved also better placed in mousePressed.
You would want to update your captureRect when you release the mouse (in mouseReleased method).
Then you just write it to the file. You may adjust other things according to your needs.
And for clarity maybe it's better to add a save button into your UI.
public class ScreenCaptureRectangle {
Rectangle captureRect;
Point start = new Point();
SimpleDateFormat sdf;
ScreenCaptureRectangle(final BufferedImage screen) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
final BufferedImage screenCopy = new BufferedImage(
screen.getWidth(),
screen.getHeight(),
screen.getType());
final JLabel screenLabel = new JLabel(new ImageIcon(screenCopy));
JScrollPane screenScroll = new JScrollPane(screenLabel);
screenScroll.setPreferredSize(new Dimension(
(int) (screen.getWidth() / 3),
(int) (screen.getHeight() / 3)));
JPanel panel = new JPanel(new BorderLayout());
panel.add(screenScroll, BorderLayout.CENTER);
JButton btnSave = new JButton("SAVE");
btnSave.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
double w = captureRect.getWidth();
double h = captureRect.getHeight();
double x = captureRect.getX();
double y = captureRect.getY();
int W = (int) w;
int H = (int) h;
int X = (int) x;
int Y = (int) y;
BufferedImage selectImg = screen.getSubimage(X, Y, W, H);
try {
String fName = generateFileName();
if (fName != null) {
File f = new File(fName);
if (f.createNewFile()) {
ImageIO.write(selectImg, "jpg", f);
}
}
} catch (IOException ex) {
Logger.getLogger(ScreenCaptureRectangle.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
panel.add(btnSave, BorderLayout.AFTER_LAST_LINE);
final JLabel selectionLabel = new JLabel(
"Drag a rectangle in the screen shot!");
panel.add(selectionLabel, BorderLayout.SOUTH);
repaint(screen, screenCopy);
screenLabel.repaint();
screenLabel.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent me) {
Point end = me.getPoint();
captureRect = new Rectangle(start,
new Dimension(end.x - start.x, end.y - start.y));
repaint(screen, screenCopy);
screenLabel.repaint();
selectionLabel.setText("Rectangle: " + captureRect);
}
});
screenLabel.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent me) {
start = me.getPoint();
repaint(screen, screenCopy);
selectionLabel.setText("Start Point: " + start);
screenLabel.repaint();
}
#Override
public void mouseReleased(MouseEvent me) {
int endX = me.getX();
int endY = me.getY();
if (endX > start.x && endY > start.y) {
captureRect = new Rectangle(start.x, start.y, endX-start.x, endY-start.y);
System.out.println("Rectangle of interest: " + captureRect);
}
}
});
JOptionPane.showMessageDialog(null, panel);
}
private String generateFileName() {
return new StringBuilder("screencrop_").append(sdf.format(new Date())).append(".jpg").toString();
}
public void repaint(BufferedImage orig, BufferedImage copy) {
Graphics2D g = copy.createGraphics();
g.drawImage(orig, 0, 0, null);
if (captureRect != null) {
g.setColor(Color.RED);
g.draw(captureRect);
g.setColor(new Color(255, 255, 255, 150));
g.fill(captureRect);
}
g.dispose();
}
public static void main(String[] args) throws Exception {
Robot robot = new Robot();
final Dimension screenSize = Toolkit.getDefaultToolkit().
getScreenSize();
final BufferedImage screen = robot.createScreenCapture(
new Rectangle(screenSize));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ScreenCaptureRectangle(screen);
}
});
}
}
I have been trying to tame JDesktopPane to work nicely with a resizable GUI & a scroll pane, but am having some troubles doing so. It seems that unless the drag mode is outline, the desktop pane will not resize as expected (when an internal frame is dragged beyond the edge of the desktop pane) & therefore not produce scroll-bars.
Am I doing something very silly in this source? Have I missed a far better approach?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class MDIPreferredSize {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
}
System.out.println("maxBounds(): "
+ max);
int x1 = max.width + (max.x * 2) < prefSize.width
? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height
? prefSize.height
: max.height + (max.y * 2);
System.out.println("x,y: "
+ x1
+ ","
+ y1);
return new Dimension(x1, y1);
}
};
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
ComponentListener componentListener = new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentMoved(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentShown(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentHidden(ComponentEvent e) {
// do nothing
}
};
// causes maximized internal frames to be resized..
dt.addComponentListener(componentListener);
final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
}
}
};
outLineDragMode.addActionListener(dragModeListener);
JPanel gui = new JPanel(new BorderLayout());
gui.add(outLineDragMode, BorderLayout.PAGE_START);
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new JScrollPane(dt), BorderLayout.CENTER);
JFrame f = new JFrame("DTP Preferred");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
f.setMinimumSize(f.getSize());
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
printProperty("os.name");
printProperty("java.version");
printProperty("java.vendor");
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
public static void printProperty(String name) {
System.out.println(name + ": \t" + System.getProperty(name));
}
}
Edit
Amongst the information printed, see also the 3 system properties:
os.name: Windows 7
java.version: 1.7.0_21
java.vendor: Oracle Corporation
Those are the values here.
MouseMotionListener fixed code
Thanks to Jonathan Drapeau's suggestion of a MouseListener, this fixed example actually uses a MouseMotionListener to allow the desktop pane to be resized actively while dragging. It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known), if so, go back to the simpler technique of 'resize desktop pane on internal frame drop' (MouseListener only).
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
public class MDIPreferredSize {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
}
System.out.println("maxBounds(): "
+ max);
int x1 = max.width + (max.x * 2) < prefSize.width
? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height
? prefSize.height
: max.height + (max.y * 2);
System.out.println("x,y: "
+ x1
+ ","
+ y1);
return new Dimension(x1, y1);
}
};
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
/*final MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
dt.revalidate();
}
};
*/
final MouseMotionListener mouseMotionListener = new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
dt.revalidate();
}
};
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
//comp.addMouseListener(mouseListener);
comp.addMouseMotionListener(mouseMotionListener);
}
}
}
dt.setAutoscrolls(true);
final JCheckBox outLineDragMode =
new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
}
}
};
outLineDragMode.addActionListener(dragModeListener);
JPanel gui = new JPanel(new BorderLayout());
gui.add(outLineDragMode, BorderLayout.PAGE_START);
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new JScrollPane(dt), BorderLayout.CENTER);
JFrame f = new JFrame("DTP Preferred");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
f.setMinimumSize(f.getSize());
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
printProperty("os.name");
printProperty("java.version");
printProperty("java.vendor");
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
public static void printProperty(String name) {
System.out.println(name + ": \t" + System.getProperty(name));
}
}
Quirks
It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known).
That was then..
In full render mode, the desktop pane will grow dynamically as far as the user drags the internal frame (even off the GUI). (Good.) In outline mode, the container will only resize on drop, not drag. (Less good, but at least the scroll-bars appear/disappear reliably.)
Adding a MouseListener to the JInternalFrame title pane while in JDesktopPane.LIVE_DRAG_MODE to revalidate the JDesktopPane after release is a way to get the exact same behavior in each mode.
final MouseListener testList = new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
dt.revalidate();
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
};
// causes maximized internal frames to be resized..
dt.addComponentListener(componentListener);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.addMouseListener(testList);
}
}
}
final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.removeMouseListener(testList);
}
}
}
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.addMouseListener(testList);
}
}
}
}
}
};
I remove them in the JDesktopPane.OUTLINE_DRAG_MODE since it already reacts properly.
You should be able to use the Drag Layout to handle the resizing of the desktop pane as components are dragged.
Interesting problem for a Saturday morning :-)
No complete solution, just a couple of comments and an outline of an alternative approach:
relying on the mouse/Motion/listener is incomplete in that it doesn't handle keyboard controlled moves
per-internalframe componentListener to the rescue: works fine if not in outline mode
in outline mode the revalidation can't work anyway, because it relies on the actual frame location which is unchanged during the drag
So the real problem is the outline mode, needs to
trace the intermediate bounds of the dragged frame
let the desktop's prefSize calculation take those intermediate bounds into account
the drawing of the outline (unexpected, for me, see below [*])
The collaborator that is responsible for moving the frame is the DesktopManager.dragFrame: it's default implementation resets the frame bounds if not in outline mode or keeps track of the intermediate location and drawing the outline rectangle if in outline mode.
The obvious idea is a custom DesktopManager which overrides dragFrame:
let super do its stuff
on outline mode, get the frame's intermediate location and store it somewhere on the frame itself, f.i. as a clientProperty
Now a someone, f.i. a PropertyChangeListener can listen for changes of the intermediate location and trigger a revalidate. And the prefSize calculation of the desktopPane can account for the intermediate bounds in addition to the real bounds, something like
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
#Override
public void dragFrame(JComponent f, int newX, int newY) {
// let super handle outline drawing
super.dragFrame(f, newX, newY);
if (isOutline(f)) {
// take over the drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
// super.dragFrame(f, newX, newY);
}
}
#Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
// do the painting in the glassPane
// r.getGlassPane().setVisible(true);
}
}
#Override
public void endDraggingFrame(JComponent f) {
super.endDraggingFrame(f);
f.putClientProperty("outlineBounds", null);
if (isOutline(f)) {
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
r.getGlassPane().setVisible(false);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
Usage:
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
return new Dimension(x1, y1);
}
};
dt.setDesktopManager(new MyDesktopManager());
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
// oer-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
[*] The default outline painting flickers (to the extend that it is invisible) - reason being that the default implementation uses ... getGraphics() ... So we need to take over the outline painting, f.i. in a dedicated glassPane (that's done by the commented code) or probably better by a LayerUI on the desktop.
A crude glassPane, just as a poc which doesn't clip correctly and has some issues when the frame is moved back into the visible rect:
public static class OutlinePanel extends JPanel {
private JDesktopPane desktop;
public OutlinePanel(JDesktopPane desktop) {
this.desktop = desktop;
}
#Override
public boolean isOpaque() {
return false;
}
#Override
protected void paintComponent(Graphics g) {
JInternalFrame selected = desktop.getSelectedFrame();
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = SwingUtilities.convertRectangle(desktop, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
Update
Version with LayerUI - now we are leaving the complete new behaviour (listener registration, painting outline if needed, installing the manager) to the decoration. Advantages:
simplified usage
one location for all dirty details
The LayerUI:
public class DesktopLayerUI extends LayerUI<JDesktopPane> {
#Override
public void installUI(JComponent c) {
super.installUI(c);
final JDesktopPane dt = getDesktopPane(c);
//dt.setBorder(BorderFactory.createLineBorder(Color.RED));
dt.setDesktopManager(new MyDesktopManager());
// per-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (JInternalFrame jif : dt.getAllFrames()) {
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
}
// TBD: register container listener to update frame listeners on adding/removing
// TBD: componentListener on desktop that handles maximizing frame
// (JW: didn't really understand what that one is doing in the original)
}
#Override
public Dimension getPreferredSize(JComponent c) {
JDesktopPane dt = getDesktopPane(c);
Dimension prefSize = super.getPreferredSize(c);
//System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = dt.getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : dt.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif
.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
// TBD: cope with frames at negative locations
//System.out.println("maxBounds(): " + max);
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
//System.out.println("x,y: " + x1 + "," + y1);
return new Dimension(x1, y1);
}
#Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
JDesktopPane desktop = getDesktopPane(c);
JInternalFrame selected = desktop.getSelectedFrame();
if (selected == null) return;
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = outline; //SwingUtilities.convertRectangle(, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
protected JDesktopPane getDesktopPane(JComponent c) {
JDesktopPane desktop = ((JLayer<JDesktopPane>) c).getView();
return desktop;
}
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
#Override
public void dragFrame(JComponent f, int newX, int newY) {
if (isOutline(f)) {
// take over the outline drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
super.dragFrame(f, newX, newY);
}
}
#Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
f.putClientProperty("outlineBounds", f.getBounds());
}
}
#Override
public void endDraggingFrame(JComponent f) {
if (isOutline(f) && currentLoc != null) {
setBoundsForFrame(f, currentLoc.x, currentLoc.y, f.getWidth(), f.getHeight() );
f.putClientProperty("outlineBounds", null);
} else {
super.endDraggingFrame(f);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
}
usage:
JDesktopPane dt = new JDesktopPane();
// add internalframes
...
// decorate the pane with the layer
JLayer<JDesktopPane> layer = new JLayer<>(dt, new DesktopLayerUI());
gui.add(new JScrollPane(layer), BorderLayout.CENTER);
There's a slight catch (read: didn't yet figure out how to fix it): JLayer implements Scrollable - its implementation returns false for tracksXX (if the decorated component isn't a Scrollable itself - JDesktopPane isn't), meaning that the desktop inside a scrollPane is always sized to its prefSize which shows the grayish viewport at the trailing/bottom area if the scrollPane is larger.