I'm currently working on an image editor application.
I'm trying to provide the option in my application to work with different layers like in GIMP or Photoshop. My approach is to add a Canvas for each layer the user added.
Everything is working pretty nice but somehow my dynamically added canvases don't show up.
In my classes constructor I added 1 general canvas which holds a background-image and which can't be edited. This canvas(which is global) does show up and works properly. All layers that can be edited are stored in a List<Canvas> canvasLayers.
This is the method which should add a canvas to my List and Formpanel.
public void addCanvasLayer(String layerName, int id){
Canvas newCanvas = Canvas.createIfSupported();
newCanvas.setWidth(this.scaledImageWidth + "px");
newCanvas.setHeight(this.scaledImageHeight + "px");
newCanvas.setCoordinateSpaceWidth(this.scaledImageWidth);
newCanvas.setCoordinateSpaceHeight(this.scaledImageHeight);
newCanvas.getElement().setId(layerName);
newCanvas.getElement().getStyle().setZIndex(id);
this.fpCanvas.add(newCanvas, new AbsoluteData(0, 0));
this.canvasLayers.add(newCanvas);
this.addCanvasHandler(newCanvas);
context = newCanvas.getContext2d();
}
All it does is creating a canvas, setting its size to the main width and height, setting its id and z-index, adding the canvas to my canvaslist, adding handlers to the canvas(clickhandler, onMouseDownHandler,...) and adding the canvas to the main context.
ScaledImageWidth and -Height definitely are NOT null or 0 when adding the canvas.
Any ideas?
Edit: Solved the problem by adding fpCanvas.layout(); to the end of the method.
My formpanel just had to be refreshed <.< ....
Here is how I manage layers of canvas:
public abstract class Layer
extends Composite
{
/**
* The canvas used in this layer
*/
private Canvas m_canvas;
public Layer()
{
m_canvas = Canvas.createIfSupported();
initWidget( m_canvas );
}
/**
* Get the name of the layer (debug purpose only)
* #return the name of the layer
*/
protected abstract String getLayerName();
/**
* Set the z-index of the layer
* #param zindex the new z-index
*/
public void setZ( int zindex )
{
m_canvas.getElement().getStyle().setZIndex( zindex );
}
public int getZ()
{
return Integer.parseInt( m_canvas.getElement().getStyle().getZIndex() );
}
#Override
public void setPixelSize( int width, int height )
{
super.setPixelSize( width, height );
m_canvas.setCoordinateSpaceWidth( width );
m_canvas.setCoordinateSpaceHeight( height );
}
/**
* Draw the layer
*/
public void draw()
{
}
/**
* Get the context2d where this layer will be drawn
*
* #return the Context2d
*/
public final Context2d getContext2d()
{
return m_canvas.getContext2d();
}
/**
* Clear the layer
*/
public void clear()
{
Context2d context2d = getContext2d();
if ( m_canvas != null && m_canvas.isAttached() )
{
context2d.clearRect( 0, 0, m_canvas.getOffsetWidth(), m_canvas.getOffsetHeight() );
}
}
public LayoutPanel getEnclosingLayoutPanel()
{
return (LayoutPanel)getParent();
}
}
and to add it:
public void addLayer( Layer layer, int zindex )
{
// m_main is a LayoutPanel
int width = m_main.getOffsetWidth();
int height = m_main.getOffsetHeight();
m_layers.add( layer );
layer.setZ( zindex );
if ( m_main.getWidgetCount() > 0 )
{
m_main.insert( layer, 1 );
}
else
{
m_main.add( layer );
}
layer.setPixelSize( width, height );
(...)
Solved the problem by adding fpCanvas.layout(); to the end of the method. My formpanel just had to be refreshed <.< ....
Related
I am studying Java AWT to create GUI applications. I am working on the below code where I cannot make the panel visible inside the frame. Here is my code:
import java.awt.*;
import java.awt.event.*;
/**
*
* #author kiran
*/
public class UserInterface
{
Frame UI;
private static double UIWidth, UIHeight;
/**
* Constructs User Interface
*/
public UserInterface()
{
UI = new Frame("frame");
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
UIWidth = screenSize.getWidth();
UIHeight = screenSize.getHeight();
buildFrame();
buildMessageInputArea();
}
/**
* returns the width of the UI
* #return returns the width of the UI
*/
public static double getUIWidth()
{
return UIWidth;
}
/**
* returns the width of the UI
* #return returns the width of the UI
*/
public static double getUIHeight()
{
return UIHeight;
}
/**
* Builds the frame
*/
private void buildFrame()
{
UI.setSize((int)UIWidth,(int)UIHeight*96/100);
UI.setVisible(true);
UI.setLayout(new FlowLayout());
UI.addWindowListener(new Actions());
}
private void buildMessageInputArea()
{
Panel current = new TextAreaPanel().getPanel();
current.setVisible(true);
UI.add(current);
}
}
class TextAreaPanel extends Frame
{
private Panel textAreaPanel;
TextArea msgInputArea;
public TextAreaPanel()
{
textAreaPanel = new Panel();
msgInputArea = new TextArea(1000,(int)UserInterface.getUIWidth() * 80/100);
}
private void addTextArea()
{
textAreaPanel.add(msgInputArea);
}
public Panel getPanel()
{
return textAreaPanel;
}
}
class Actions extends WindowAdapter
{
#Override
public void windowClosing(WindowEvent c)
{
System.exit(0);
}
}
How can I make the panel visible inside the frame?
How do I make a panel visible inside frame in Java AWT?
There were two fundamental problems with the code as seen, which can be fixed by changing the following:
Add the panel/text area to the GUI before setVisible(true) is called on the top level container.
While it is possible to add components to a container after it has been made visible, they require special handling, and it is not necessary in this case.
Add the text area to the panel!
Here is the code, turned into a Minimal, Complete, and Verifiable example by adding a main(String[]) method, with those two changes implemented, as well as more explanatory comments on other aspects of the code.
import java.awt.*;
import java.awt.event.*;
public class UserInterface {
Frame UI;
private static double UIWidth, UIHeight;
public static void main(String[] args) {
Runnable r = () -> {
new UserInterface();
};
EventQueue.invokeLater(r);
}
/**
* Constructs User Interface
*/
public UserInterface() {
UI = new Frame("frame");
// setting a GUI to full screen while accounting for the task
// bar can be achieved in a single line of code.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
UIWidth = screenSize.getWidth();
UIHeight = screenSize.getHeight();
// these need to be called in the reverse order to ensure the
// components are added before the GUI is set visible.
buildMessageInputArea();
buildFrame();
}
/**
* returns the width of the UI
*
* #return returns the width of the UI
*/
public static double getUIWidth() {
return UIWidth;
}
/**
* returns the width of the UI
*
* #return returns the width of the UI
*/
public static double getUIHeight() {
return UIHeight;
}
/**
* Builds the frame
*/
private void buildFrame() {
UI.setSize((int) UIWidth, (int) UIHeight * 96 / 100);
UI.setVisible(true);
UI.setLayout(new FlowLayout());
UI.addWindowListener(new Actions());
}
private void buildMessageInputArea() {
Panel current = new TextAreaPanel().getPanel();
current.setVisible(true);
UI.add(current);
}
}
// does not need to be a fram
//class TextAreaPanel extends Frame {
class TextAreaPanel {
private Panel textAreaPanel;
TextArea msgInputArea;
public TextAreaPanel() {
textAreaPanel = new Panel();
// these number represent columns and rows, not pixels!
//msgInputArea = new TextArea(1000, (int) UserInterface.getUIWidth() * 80 / 100);
msgInputArea = new TextArea(40, 60);
// add the text area to the panel!
textAreaPanel.add(msgInputArea);
}
/** not called by anything else
private void addTextArea() {
textAreaPanel.add(msgInputArea);
}
**/
public Panel getPanel() {
return textAreaPanel;
}
}
// This can be achieved in a single line of code
class Actions extends WindowAdapter {
#Override
public void windowClosing(WindowEvent c) {
System.exit(0);
}
}
To add to / expand on the comments of #mKorbel & #camickr:
Why use AWT? See this answer for many good reasons to abandon AWT components in favor of Swing.
See Composition over inheritance.
The use of static in GUIs more commonly causes problems, than fixes them. Most (if not all) of the methods marked as static should be reduced to non-static with the code using an instance of the object to call the method.
- = UPDATE = -
It turns out the issue was not with Java but with my Apple keyboard. Holding down a letter key brings up a menu that breaks my Java programs. By disabling that menu popup, my KeyListener and my Key Bindings both work as they should. Thank you all for your answers.
Question
I have searched on Google and on StackOverflow for an answer to my question, but to no avail. All of the questions that I've found have the main class extending JComponent, JFrame, JPanel, etc., and not Canvas.
Now for my question:
I am having trouble getting my Java KeyListener to cooperate while my program runs. When I start my program, everything works as usual. However, as I start pressing keys and moving things around (with said keys), the program begins to delay and take more time for the key presses to register. All of a sudden, they KeyListener breaks altogether and I get no input (even a System.out.println statement in the keyPressed method shows no activity). I have three classes that have to do with my KeyListener in any way.
If it helps, the goal of this program is to use BufferedImage class to plot points from different mathematical functions, like a sine wave. I have commented the best I can without being Super-Comment-Man, but I can clarify on the purpose of any code to the best of my ability.
First, my Screen class (draws stuff on the JFrame with a BufferStrategy):
package com.elek.waves.graphics;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.elek.waves.graphics.math.Controller;
import com.elek.waves.graphics.math.Graph;
import com.elek.waves.input.Keyboard;
/**
* The engine of the entire Waves project. Processes the BufferedImage and puts the JFrame
* on the screen. Uses other classes to calculate where to put pixels (what color each pixel
* in the array should be) and get keyboard input.
*
* #author my name
* #version 1.0
*/
public class Screen extends Canvas {
/**
* Holds some *important* number that makes Java happy.
*/
private static final long serialVersionUID = 1L;
/**
* Constant (and static) dimensions of the window.
*/
public static final int WIDTH = 800, HEIGHT = 800;
/**
* Frame that will contain the BufferedImage and all its glory.
*/
private JFrame frame;
/**
* BufferedImage processes the pixel array and translates it into fancy screen magic.
*/
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
/**
* Holds color data for each pixel on the screen. Each pixel has an integer value equivalent
* to the hexadecimal RGB value for whatever color this pixel is supposed to be.
*/
private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
/**
* Graph object to draw the lines on.
*/
private Graph graph;
/**
* Controller object to control the graph.
*/
private Controller controller;
/**
* Keybaord object to use as a key-listener.
*/
private Keyboard key;
/* -- Constructor -- */
/**
* Creates a new Screen object. Initializes the JFrame object.
*/
public Screen() {
frame = new JFrame("Magic!");
graph = new Graph(pixels);
key = new Keyboard();
controller = new Controller(key, graph);
addKeyListener(key);
}
/* -- Methods -- */
/**
* Called once and only once by the main method. Repeatedly calls the update and render methods
* until the program stops running.
*/
private void start() {
this.requestFocus();
this.requestFocusInWindow();
while (true) {
update();
render();
}
}
/**
* Called by the start method repeatedly. First, clears the screen of the previous image in
* order to prevent ghost-imaging or blurring. Then, updates the pixel array to whatever it
* needs to be for the next iteration of the render method.
*/
private void update() {
// Update the keyboard input
key.update();
// Update the controller
controller.update();
// Clean up the screen and then graph the line
clearScreen();
graph.drawWave();
}
/**
* Called by the start method repeatedly. Draws the pixel array onto the JFrame using the
* BufferedImage magic.
*/
private void render() {
// Initialize buffer strategies
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(2);
return;
}
// Physically update the actual pixels on the image
Graphics g = (Graphics2D) bs.getDrawGraphics();
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.dispose();
bs.show();
}
/**
* Clears the screen by setting every pixel in the pixel array to black. Used to prevent
* ghost-images or blurring.
*/
public void clearScreen() {
for (int i = 0; i < pixels.length; i++)
pixels[i] = 0;
}
/**
* Main method to run the program. Creates a Screen object with a BufferedImage to display
* pixels however the other classes decide to. All this does is set up the JFrame with the
* proper parameters and properties to get it up and running.
*
* #param args A String array of random arguments that Java requires or it gets fussy
*/
public static void main(String[] args) {
// Create Screen object
Screen screen = new Screen();
screen.frame.add(screen);
screen.frame.pack();
screen.frame.setSize(WIDTH, HEIGHT);
screen.frame.setLocationRelativeTo(null);
screen.frame.setResizable(false);
screen.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
screen.frame.setVisible(true);
screen.start();
}
}
Second, my Keyboard class (KeyListener that breaks):
package com.elek.waves.input;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* Gets the user's key strokes and determines which keys are down at a given time.
*
* #author my name
* #version 1.0
*/
public class Keyboard implements KeyListener {
/**
* Holds the state of 120 keys (true if they're down, false if they're not).
*/
private boolean[] keys = new boolean[120];
/**
* Holds the state of the "useful" keys (true if down, false if not).
*/
public boolean w, a, s, d, up, down, left, right;
/**
* Determines if the "useful" keys are down or not. Sets the variables to true if they're down and
* false if they're up.
*/
public void update() {
w = keys[KeyEvent.VK_W];
a = keys[KeyEvent.VK_A];
s = keys[KeyEvent.VK_S];
d = keys[KeyEvent.VK_D];
up = keys[KeyEvent.VK_UP];
down = keys[KeyEvent.VK_DOWN];
left = keys[KeyEvent.VK_LEFT];
right = keys[KeyEvent.VK_RIGHT];
}
/**
* Changes the state of the pressed key's corresponding boolean in the array to true.
*/
public void keyPressed(KeyEvent e) {
keys[e.getKeyCode()] = true;
}
/**
* Changes the state of the pressed key's corresponding boolean in the array to false.
*/
public void keyReleased(KeyEvent e) {
keys[e.getKeyCode()] = false;
}
public void keyTyped(KeyEvent e) {
}
}
Third, my Controller class (uses the KeyListener to control the program):
package com.elek.waves.graphics.math;
import com.elek.waves.input.Keyboard;
/**
* Controls the graph's transformation properties (stretching and shifting). Directly changes the
* transformation variables in the Graph class to achieve this.
*
* #author my name
* #version 1.0
*/
public class Controller {
/**
* Keyboard object to get the user's key-inputs.
*/
private Keyboard input;
/**
* Graph object that this Controller will control.
*/
private Graph graph;
/* -- Constructor -- */
/**
* Create a new Controller object with the specific keyboard input parameter.
* <pre>Sets the starting parameters as the following:
* Vertical Scale: 1
* Horizontal Scale: 1
* Vertical Shift = 0
* Horizontal Shift = 0</pre>
*
* #param input The Keybaord object from which the controller will get input
*/
public Controller(Keyboard input, Graph parent) {
// Initialize keybaord input and graph parent
this.input = input;
graph = parent;
// Initialize transformation variables
graph.vScale = 50;
graph.hScale = 0.05;
graph.vShift = 0;
graph.hShift = 0;
}
/* -- Methods -- */
/**
* Updates the shifting of the graph (moving around) and the scaling of the graph (stretching)
* from the keyboard input. <strong>WASD</strong> keys control shifting, and <strong>up, down,
* left, and right</strong> keys control stretching.
*/
public void update() {
// Update shifting
if (input.w) graph.vShift += 0.5;
if (input.s) graph.vShift -= 0.5;
if (input.a) graph.hShift -= 0.04;
if (input.d) graph.hShift += 0.04;
// Update scaling
if (input.up) graph.vScale += 0.5;
if (input.down) graph.vScale -= 0.5;
if (input.left) graph.hScale += 0.0001;
if (input.right) graph.hScale -= 0.0001;
}
}
I have found several helpful people saying to use KeyBindings as opposed to a KeyListener. However, I have used a KeyListener successfully in the past, and I'd like to get it to work again if possible. If KeyBindings are absolutely necessary, I supposed I can make the switch, but I'd prefer if that didn't have to be the case.
Thank you all in advance!
Canvas will suffer the same issues that all the other components suffer from, loss of keyboard focus, this is why we generally don't recommend KeyListener.
First you need to make the Canvas focusable, see Canvas#setFocusable
The next, more difficult issue, is requesting keyboard focus, you can use Canvas#requestFocusInWindow but any component which requires keyboard focus will steal it.
Depending on what you are doing, you might be able to simply place the call in the update loop, but you need to be aware that if you want to ask input from the user, within the same window, you will have issues (with the canvas stealing the focus)
Update
I had some issues with index of bounds due to the use of an array in the keyboard controller, which I switched over to Set instead...
public class Keyboard implements KeyListener {
/**
* Holds the state of 120 keys (true if they're down, false if they're
* not).
*/
// private boolean[] keys = new boolean[120];
/**
* Holds the state of the "useful" keys (true if down, false if not).
*/
private Set<Integer> keys;
/**
* Determines if the "useful" keys are down or not. Sets the variables
* to true if they're down and false if they're up.
*/
public void update() {
keys = new HashSet<>(8);
}
public boolean isKeyPressed(int key) {
return keys.contains(key);
}
public boolean isWPressed() {
return isKeyPressed(KeyEvent.VK_W);
}
public boolean isAPressed() {
return isKeyPressed(KeyEvent.VK_A);
}
public boolean isSPressed() {
return isKeyPressed(KeyEvent.VK_S);
}
public boolean isDPressed() {
return isKeyPressed(KeyEvent.VK_D);
}
public boolean isUpPressed() {
return isKeyPressed(KeyEvent.VK_UP);
}
public boolean isDownPressed() {
return isKeyPressed(KeyEvent.VK_DOWN);
}
public boolean isLeftPressed() {
return isKeyPressed(KeyEvent.VK_LEFT);
}
public boolean isRightPressed() {
return isKeyPressed(KeyEvent.VK_RIGHT);
}
/**
* Changes the state of the pressed key's corresponding boolean in the
* array to true.
*/
public void keyPressed(KeyEvent e) {
System.out.println("Pressed = " + e.getKeyCode());
keys.add(e.getKeyCode());
}
/**
* Changes the state of the pressed key's corresponding boolean in the
* array to false.
*/
public void keyReleased(KeyEvent e) {
System.out.println("Released = " + e.getKeyCode());
keys.remove(e.getKeyCode());
}
public void keyTyped(KeyEvent e) {
}
}
I also added a small delay into the render loop so you're not chocking the system
private void start() {
setFocusable(true);
while (true) {
this.requestFocusInWindow();
update();
render();
try {
Thread.sleep(16);
} catch (InterruptedException ex) {
}
}
}
try this
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class Main {
public static void main(String[] argv) throws Exception {
JTextField textField = new JTextField();
textField.addKeyListener(new MKeyListener());
JFrame jframe = new JFrame();
jframe.add(textField);
jframe.setSize(700, 700);
jframe.setVisible(true);
}
}
class MKeyListener extends KeyAdapter {
#Override
public void keyPressed(KeyEvent event) {
System.out.println(event.getKeyCode)
if(event.getKeyCode() = \\key code here\\){
System.out.println("True")
}else{System.out.println("False")
Allthough this was ran in package java but there shouldnt be anything wrong with it
I'm starting to teach myself Java, and it's possible my reach doth exceed my grasp. Still, that's the best way to learn, right?
I'm trying to play around with simple graphics. I've found various resources on how to draw shapes into a JFrame, and made them work, but as far as I can tell the code always needs to go inside the paint method, which gets called automatically, which means I can't figure out how to go about passing it arguments.
What I'm trying to do is write some code that can take co-ordinates as arguments, and then place a simple shape (let's go with a 10x10 pixel square) at those co-ordinates. I feel sure that must be something that's possible, but I cannot for the life of me work out how.
Code so far, incorporating help from both #resueman and #Mikle:
public class Robots {
public static void main(String[] args) {
Window window = new Window();
window.Window();
new PlayArea();
Point p = new Point(50,50);
Entity player1 = new Entity();
player1.Entity(p);
}
}
public class Window extends JFrame{
int x;
int y;
public void Window(){
Window window = new Window();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setTitle("Robots");
window.setSize(800,600);
window.getContentPane().add(new PlayArea());
window.setLocationRelativeTo(null);
window.setBackground(Color.white);
window.setVisible(true);
}
}
public class PlayArea extends JComponent{
// I keep all added and displayed entities here for easier access
public final List<Entity> entities = new ArrayList<Entity> ();
public PlayArea(){
super();
}
public void addEntity(final Entity entity){
//add new entity and repaint area
entities.add(entity);
}
#Override
protected void paintComponent (final Graphics g)
{
super.paintComponent (g);
// Painting entities
final Graphics2D g2d = (Graphics2D) g;
for (final Entity entity : entities)
{
g2d.setPaint ( Color.BLACK );
g2d.fill (getEntityShape (entity));
}
}
protected Shape getEntityShape ( final Entity entity )
{
// Simple entity shape, you can replace it with any other shape
return new Rectangle ( entity.entPos.x - 5, entity.entPos.y - 5, 10, 10 );
}
}
public class Entity extends JComponent{
protected Point entPos = null;
public void Entity(Point p){
entPos = p;
repaint();
}
#Override
public void paintComponent (Graphics g){
super.paintComponent(g);
if (entPos != null){
g.fillRect(entPos.x, entPos.y, 10,10);
}
}
}
I want to be able to create an object in the Entity class, and put it in the window at the x,y co-ordinates.
I will eventually want to be able to move Entities around, but I'll work that one out once I've figured out how to draw them in the first place!
Still not drawing anything in the window. I'm probably missing something really obvious though.
There are a lot of possible approaches to solve your task.
Here are the first two options which come to mind:
Use a custom component and paint all Entity objects in it
This one will be easy and fast to implement.
Implementing entities drag also won't be a big issue. There are also a few options how painting can be done here. This approach is good in case you are going to have just a few simple elements painted on the area and don't want to make your code too complicated.
Paint each Entity on a separate component, and place them using layout managers
This is harder to achieve, but it will use much less resources and will be painted faster due to Swing repainting optimizations. This approach is much better if you are aiming at a large amount of elements or large area size. But that might be an overkill for your case.
Here is a simple example of the 1st approach (including entities drag):
public class SingleComponent extends JFrame
{
public SingleComponent () throws HeadlessException
{
super ();
setTitle ( "Robots" );
setBackground ( Color.white );
// Adding our custom component into the frame
getContentPane ().add ( new EntitiesArea () );
setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
setSize ( 800, 600 );
setLocationRelativeTo ( null );
setVisible ( true );
}
public class Entity
{
int x;
int y;
public Entity ( final int x, final int y )
{
this.x = x;
this.y = y;
}
}
public class EntitiesArea extends JComponent
{
// I keep all added and displayed entities here for easier access
public final List<Entity> entities = new ArrayList<Entity> ();
public EntitiesArea ()
{
super ();
// Adding mouse adapter that will add/drag entities
final MouseAdapter mouseAdapter = new MouseAdapter ()
{
// Currently dragged entity
private Entity dragged = null;
#Override
public void mousePressed ( final MouseEvent e )
{
// Looking for entity under mouse
Entity underPoint = null;
for ( final Entity entity : entities )
{
if ( getEntityShape ( entity ).contains ( e.getPoint () ) )
{
underPoint = entity;
break;
}
}
// Either dragging existing entity or adding new one
if ( underPoint != null )
{
dragged = underPoint;
}
else
{
addEntity ( new Entity ( e.getX (), e.getY () ) );
}
}
#Override
public void mouseDragged ( final MouseEvent e )
{
if ( dragged != null )
{
// Change entity coordinate and repaint area
dragged.x = e.getX ();
dragged.y = e.getY ();
repaint ();
}
}
#Override
public void mouseReleased ( final MouseEvent e )
{
if ( dragged != null )
{
dragged = null;
}
}
};
addMouseListener ( mouseAdapter );
addMouseMotionListener ( mouseAdapter );
}
public void addEntity ( final Entity entity )
{
// Add new entity and repaint area
entities.add ( entity );
repaint ();
}
#Override
protected void paintComponent ( final Graphics g )
{
super.paintComponent ( g );
// Painting entities
final Graphics2D g2d = ( Graphics2D ) g;
for ( final Entity entity : entities )
{
g2d.setPaint ( Color.BLACK );
g2d.fill ( getEntityShape ( entity ) );
}
}
protected Shape getEntityShape ( final Entity entity )
{
// Simple entity shape, you can replace it with any other shape
return new Rectangle ( entity.x - 20, entity.y - 20, 40, 40 );
}
}
public static void main ( final String[] args )
{
new SingleComponent ();
}
}
I didn't add any repaint optimizations to this example to make it as simple as it could be, but keep in mind that any action in this example repaints the whole area and forces each entity to be painted from a scratch. This won't even be noticeable until a large amount of elements appear on the area, but make sure you take care of optimized repaint(...) calls later as this is one of the things that make Swing so fast and so good.
If you want to draw independently of the presentation / rendering, you can draw on a BufferedImage for example. You can acquire a Graphics object associated with the BufferedImage with its getGraphics() and createGraphics() methods.
And if later you want to display this, you can with the Graphics.drawImage() methods inside JComponent.paintComponent().
I'd like to create a custom FieldEditor for a preference page. the aim is to override ScaleFieldEditor to provide a Scale with two labels over it (one to display the min value, the other to display the max).
It made one. It is working but the layout is not good when I add a FileFieldeditor to preference page.
It's even worst if I add the FileFieldEditor immediately after the custom ScaleFieldEditor:
http://imageshack.us/g/844/customscalelayoutok.png/
(Sorry, I cannot add images to this post).
I made a Composite which contains the 2 Labels and the Scale. I used a GridLayout:
public class ScaleWithLabelFieldEditor extends ScaleFieldEditor {
/**
* The maximum value label
*/
private Label maxLabel;
/**
* The minimum value label
*/
private Label minLabel;
/**
* A composite that contains the scale and the min & max values label
*/
private Composite controls;
public ScaleWithLabelFieldEditor(String name, String labelText,
Composite parent) {
super(name, labelText, parent);
}
public ScaleWithLabelFieldEditor(String name, String labelText,
Composite parent, int min, int max, int increment, int pageIncrement) {
super(name, labelText, parent, min, max, increment, pageIncrement);
}
#Override
protected void doFillIntoGrid(Composite parent, int numColumns) {
Control labelControl = getLabelControl(parent);
GridData gd = new GridData();
labelControl.setLayoutData(gd);
controls = getControls(parent, 2);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = numColumns -1;
gd.grabExcessHorizontalSpace = true;
controls.setLayoutData(gd);
updateControls();
parent.layout();
}
/**
* Initialize (if not done yet) the controls composite
*
* #param parent
* the parent composite
* #return the controls composite that contains the scaler and the min/max
* labels
*/
protected Composite getControls(Composite parent, int numColumns) {
if (controls == null) {
controls = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(numColumns, false);
layout.numColumns = numColumns;
controls.setLayout(layout);
// Initialize the min value label
minLabel = getMinLabel(controls);
minLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
// Initialize the max value label
maxLabel = getMaxLabel(controls);
maxLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
// Initialize the scale
scale = getScale(controls);
scale.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false, numColumns, 1));
}
return controls;
}
/**
* Nothing to do, already handle with controls composite
*/
#Override
protected void adjustForNumColumns(int numColumns) {
// Nothing to do, already handle with controls composite
}
/**
* Update the scale and the labels above
*/
protected void updateControls() {
if (controls != null && !controls.isDisposed()) {
// Update scale
scale.setMinimum(getMinimum());
scale.setMaximum(getMaximum());
scale.setIncrement(getIncrement());
scale.setPageIncrement(getPageIncrement());
// Update labels
maxLabel.setText(Integer.toString(getMaximum()));
minLabel.setText(Integer.toString(getMinimum()));
}
}
What I should do to make this field layout work? Did I miss something in GridLayout/GridData use?
The problem seems to be, that you did not implement the adjustForNumColumns(int numColumns) method. When you add the FileFieldEditor the number of columns changes from 2 to 3. And your field editor must adapt to that which it currently does not.
It seems like the horizontalSpan is not spanning until the end? So, may be you should try to remove -1 in gd.horizontalSpan = numColumns -1;
I'm trying to set tooltips on a JEditorPane. The method which I use to determine what tooltip text to show is fairly CPU intensive - and so I would like to only show it after the mouse has stopped for a short amount of time - say 1 second.
I know I can use ToolTipManager.sharedInstance().setInitialDelay(), however this will set the delay time for tooltips on all swing components at once and I don't want this.
If what you want is to make the tooltip dismiss delay much longer for a specific component, then this is a nice hack:
(kudos to tech at http://tech.chitgoks.com/2010/05/31/disable-tooltip-delay-in-java-swing/)
private final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent me) {
ToolTipManager.sharedInstance().setDismissDelay(60000);
}
public void mouseExited(MouseEvent me) {
ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout);
}
});
Well, I would recommend doing the CPU intensive task on another thread so it doesn't interrupt normal GUI tasks.
That would be a better solution. (instead of trying to circumvent the problem)
*Edit* You could possibly calculate the tootips for every word in the JEditorPane and store them in a Map. Then all you would have to do is access the tootip out of the Map if it changes.
Ideally people won't be moving the mouse and typing at the same time. So, you can calculate the tootlips when the text changes, and just pull them from the Map on mouseMoved().
You can show the popup yourself. Listen for mouseMoved() events, start/stop the timer and then show popup with the following code:
First you need PopupFactory, Popup, and ToolTip:
private PopupFactory popupFactory = PopupFactory.getSharedInstance();
private Popup popup;
private JToolTip toolTip = jEditorPane.createToolTip();
then, to show or hide the toolTip:
private void showToolTip(MouseEvent e) {
toolTip.setTipText(...);
int x = e.getXOnScreen();
int y = e.getYOnScreen();
popup = popupFactory.getPopup(jEditorPane, toolTip, x, y);
popup.show();
}
private void hideToolTip() {
if (popup != null)
popup.hide();
}
This will give you adjustable delay and a lot of troubles :)
FWIW, here is code that is based on the post by Noel. It takes that prior art and wraps it in a new class where the default is stored statically. Just in case anyone may benefit:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
/**
* Provides customizable tooltip timeouts for a {#link javax.swing.JComponent}.
*
* #see ToolTipManager#setDismissDelay(int).
*/
public final class CustomTooltipDelayer extends MouseAdapter
{
private static final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
private final int _delay;
/**
* Override the tooltip timeout for the given component in raw millis.
*
* #param component target
* #param delay the timeout duration in milliseconds
*/
public static CustomTooltipDelayer attach(JComponent component, int delay)
{
CustomTooltipDelayer delayer = new CustomTooltipDelayer(delay);
component.addMouseListener( delayer );
return delayer;
}
/**
* Override the tooltip timeout for the given component as a ratio of the JVM-wide default.
*
* #param component target
* #param ratio the timeout duration as a ratio of the default
*/
public static CustomTooltipDelayer attach(JComponent component, float ratio)
{
return attach( component, (int)(defaultDismissTimeout * ratio) );
}
/** Use factory method {#link #attach(JComponent, int)} */
private CustomTooltipDelayer(int delay)
{
_delay = delay;
}
#Override
public void mouseEntered( MouseEvent e )
{
ToolTipManager.sharedInstance().setDismissDelay(_delay);
}
#Override
public void mouseExited( MouseEvent e )
{
ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout);
}
}