I have several JLabels, each holding an ImageIcon like this:
ImageIcon icon = new ImageIcon("path/to/image.jpg");
JLabel label = new JLabel(icon);
Those images are .PNGs in a hexagonal shape. The edges "outside" the hexagon (the redundant part that exists is using a rectangular canvas) are transparent.
The JLabels, ergo the images, are ordered like in this example, so there are always three interfering images.
Since there is no "visible" layer beneath or over an other, I want to define the "clickable area" to exactly the visible layer. What is the cleverest way to do so, or is there an even more elegant solution?
I can think of three solutions for this problem:
If your hexagonal shape is just "black lines" - you can implement paintComponent and paint Polygons. You will have to have the references to them. Later on - on mouse click you will have to get shapes' component and mouse point in it. Then iterate over shapes calling contains for each of them. You will lose layout support using this solution.
Implement GlassPane/layer and dispatch mouse event to appropriate JLabel - iterating over JLabels
This would be least preferable (because of tight coupling) - extend JLabel and add references to adjacent hexagonal components. When mouse click is fired up you check if this component should "catch" this event. If not - you "forward" the event to appropriate component.
Related
Is there a way to allow the user to draw individually selectable, movable, and re-sizable shapes within JavaFX and/or canvas that I am not considering or aware of?
I am new to JavaFX (Java GUIs in general, actually) and am trying to devise a method of allowing a user to draw (click & drag) up to 100 rectangles on a pane/workspace/page. I am also looking for a method to make this scalable, but keeping static proportions for now will be fine. I have not found a great deal of comparable examples in my search, most of which explore only one of those options but leave out certain things I need to accomplish. Usually they show pre-defined nodes or canvas shapes that aren't individually workable. So I am trying to find the best method to accomplish this.
Drawing the shapes is NOT the issue, I know how to do that. It's making them individual objects within the workspace (selectable, movable, re-sizable).
My current idea is to create a Javafx pane, then create 100 canvases within the pane as predefined layers which will be the same dimensions as the entire workspace. Each drawn rectangle will occupy one of the layers/canvas. Selecting each rectangle will involve a click event within the drawn rectangle within the layer allowing the user to edit the, (move, re-size, etc.)
The following Oracle tutorial is what I am going off of for this method of layering. http://docs.oracle.com/javafx/2/canvas/jfxpub-canvas.htm
I have the feeling that my working premise for this method is flawed. One reason is it requires pre-defined number of layers. Although I want to limit this anyway, it is not very dynamic. But mostly, it just seems convoluted.
My apologies for the lack of code, and for the the conceptual nature of the question, but I've been searching and experimenting (unsuccessfully) for a couple weeks. Any help or insight would be appreciated.
Thank You
Project Context
I'm creating a basic form creator. The user (in creation mode) will drag a series of rectangles that are associated with corresponding objects indicating certain attributes, coordinates, and dimensions of each rectangle. This data will be saved and used (in form mode) for the placement of text fields for form use. Wherever rectangle were drawn on the workspace, text fields of the same location and dimension will be placed on the form.
The easiest method I can think of is by making a group, and adding rectangles to the group, on-demand. You can then attach transforms and mouselisteners to each rectangle, and have it so that the properties of the transform is updated when a drag is detected. I've put together a simple demo here. Use a right-click to make a rectangle, left mouse drag to resize, and middle mouse drag to move the rectangle.
Group root = new Group();
Scene scene = new Scene(root, 1080, 720, true);
scene.addEventFilter(MouseEvent.MOUSE_PRESSED,e->{
if(e.getButton()==MouseButton.SECONDARY)
{
//make a new rectangle
Rectangle rect = new Rectangle(100,100);
root.getChildren().add(rect);
rect.setFill(Color.CYAN);
Scale scaler = new Scale(1,1);
Translate locationCenter = new Translate(e.getX(),e.getY());
rect.getTransforms().add(locationCenter);
rect.getTransforms().add(scaler);
rect.getTransforms().add(new Translate(-50,-50));
//listen for left mouse drags
rect.addEventFilter(MouseEvent.MOUSE_DRAGGED, e2->{
if(e2.getButton()==MouseButton.PRIMARY)
{
//resize with left drag
double scaleX=e2.getSceneX()-rect.localToParent(0,0).getX();
double scaleY=e2.getSceneY()-rect.localToParent(0,0).getY();
scaler.setX(scaleX/100);
scaler.setY(scaleY/100);
} else if(e2.getButton()==MouseButton.MIDDLE)
{
//move with middle drag
locationCenter.setX(e2.getSceneX());
locationCenter.setY(e2.getSceneY());
}
});
}
});
I have an assignment where I am supposed to create a prototype drawing program to display and manipulate faces. It is supposed to use 2 windows, one to draw the face and the other to control the drawing. It is supposed to initially show the image of a face (drawn with shapes).
The face consists of a head, two eyes, a nose, and a mouth. Each of these parts is selected by clicking the mouse within the border of the object. The eyes are selected as a pair, so clicking either eye selects them both. when one face part is selected, all previous selections are forgotten.
The control window operates only upon the selected object. A click on the Change image button performs the following tasks (depending on the selected face part):
Head: Head changes from green to yellow to purple to purple, and back to green
Nose: The nose image changes another of three possible nose images
Mouth: Mouth image changes to another of three possible mouth images
Eyes: eye image changes to another of four possible eye images
Include a scroll bar with behavior:
Selected Head: Head gets wider and narrower with scroll bar value change
Selected Nose: Nose image moves up or down with scroll bar value change
Selected Mouth: Mouth gets wider or narrower with scroll bar value change
Selected Eyes: Eyes move closer together or farther apart with scroll bar value change
My problem is, I have no idea where to even start. I have watched many a tutorial on swing and awt and have even tried playing around with the palette manager in NetBeans. Can anyone point me in the right direction or maybe walk me through the code?
Your question is not a good fit here. Also, despite your instructors alleged inaccuracies, it is still conceivably possible for a person to do the research on their own to go from the rather nice set of specific requirements you have to a functioning program. I don't mean that to be as critical as it may sound.
However, out of appreciation of the fact that you approached your instructor, and also out of pity, maybe I can give you some starting points and hints.
First of all, Swing is the right way to start. Read through the relevant sections of the tutorial at http://zetcode.com/tutorials/javaswingtutorial/ and follow along with some of the projects.
You can code Swing interfaces by hand quite easily, and for your relatively simple application, and for the learning experience, it is a reasonable option. However, for future reference, many IDEs have GUI editors (NetBeans ships with the feature I think, Eclipse has the WindowBuilder plugin) - the caveat being that you have to add learning how to use the IDE to your task list (of course, this will ultimately save you time in the long run).
In any case, your first step should be to design your software. Determine what actions you need to perform, what user actions you need to respond to and how, and what information you are working with. You give the following requirements:
The control window operates only upon the selected object. A click on
the Change image button performs the following tasks (depending on the
selected face part):
Head: Head changes from green to yellow to purple to purple, and back
to green
Nose: The nose image changes another of three possible nose
images
Mouth: Mouth image changes to another of three possible mouth
images
Eyes: eye image changes to another of four possible eye images
Include a scroll bar with behavior:
Selected Head: Head gets wider and narrower with scroll bar value
change
Selected Nose: Nose image moves up or down with scroll bar
value change
Selected Mouth: Mouth gets wider or narrower with scroll
bar value change
Selected Eyes: Eyes move closer together or farther
apart with scroll bar value change
The basic component you are working with here is a face, so let's make a design decision here to have a face be an object with other properties (you could take a different approach and have the different parts of the face be your basic units). Based on your description of scroll bars, a face has the following modifiable properties:
Head width.
Vertical nose position.
Mouth width.
Distance between eyes.
A face also, of course, has other constant characteristics that are pretty much up to you to arbitrarily decide the value of, for example the diameter of the eyes, the color of the skin, etc. You will have to work those out.
In addition to modifying the above properties, there are a few things you need to be able to do with a face, also based directly on your requirements:
Draw (paint) the face.
Redraw (repaint) the face when a property changes.
Allow the user to select a part by clicking, which breaks down into:
Responding to mouse clicks.
Determining the component under the cursor given an XY location.
Providing information about the selection to the application.
The last part "providing information about the selection" is an implementation detail that you have a few options for, and you will have to decide. For example, you could create some type of event listener interface and tell the face about it, and let it call methods on that interface when the selection changes -- this is a very swing way to do it. You could also have the face internally maintain which property is currently selected, in which case you need to give it the ability to get/set the value of the currently selected property. You could also have the face directly call methods on your editor UI window. All of these ways have distinct advantages and disadvantages, and I'll leave it as an exercise to the reader to experiment.
Now, you also, as per your requirements, need to provide a scroll bar for the user to edit properties with, in a separate window. So now, think about what UI components you need and how you want them to tie together:
Face Window
Contains a single face component (or more if you want).
Editor Window
Contains a scroll bar.
Might contain a label describing the current selection, if you'd like.
Swing already provides most of these components. A window is a JFrame. A scroll bar is a JScrollBar. A label is a JLabel. Swing doesn't provide a face, so you'll need to write a custom component yourself.
As for how they tie together:
Changing the object selection in the face could:
Update the scrollbar to the current value, if you'd like.
Update the scrollbar range to appropriate values.
Update the label to describe the current selection, if you'd like.
Changing the scrollbar must:
Update the currently selected property on the face.
Ultimately cause the face to be redrawn.
The major task now, then, is to implement a Swing component that draws a face and responds to mouse clicks.
Since you are creating a new component, you'll want to pick an appropriate base, perhaps a JComponent or a JPanel, up to you. Exactly how to create new components can be easily found in many tutorials on Google (including some basics in the one I linked to above), and that is up to you to research. Essentially, though, reiterating what was said above, your custom face component has the following custom behaviors:
Draw a face.
Receive mouse events.
Determine clicked component from mouse X,Y coordinates. At its most basic level, this will require some math. If your head, eyes, mouth, and nose are ellipses, for example, it will be up to you to compute whether or not a given X,Y coordinate is within the bounds of one of those ellipses. However, you will want to look into Shape, which will allow you to easily define shapes and provides bounds checking methods already.
I think this is enough for you to get started and put it all together. The following general tasks are at hand:
Read that tutorial I linked to, a lot of basics should become clear. Try writing some simple "hello world" style applications.
Try writing some custom components; your requirements are basic and much of it is covered in the "Painting" section of that tutorial.
Check out a tutorial or some examples on how to use Shape and experiment.
If you have specific questions about code you've written, with a specific problem you may be trying to solve, you can always come back and post that on SO.
I hope this helps. More importantly, I hope this gives you at least some idea of how to go from requirements to implementation in general.
Edit:
It's been a while since I played with Shape so I put together this tiny example. Click / drag over the two shapes to select them, which will turn them yellow:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class ShapeSelect extends JPanel {
private final Shape firstShape = new Rectangle2D.Float(60, 40, 70, 70);
private final Shape secondShape = new Ellipse2D.Float(30, 30, 90, 50);
private Shape selected = null;
public ShapeSelect () {
// set up the component
setPreferredSize(new Dimension(200, 200));
addMouseMotionListener(new MouseMotionAdapter() {
#Override public void mouseDragged (MouseEvent event) {
selectShapeUnder(event.getX(), event.getY());
}
});
addMouseListener(new MouseAdapter() {
#Override public void mousePressed (MouseEvent event) {
selectShapeUnder(event.getX(), event.getY());
}
});
}
// draw our shapes, selected shape is yellow.
#Override protected void paintComponent (Graphics g) {
Graphics2D graphics = (Graphics2D)g;
graphics.setColor((selected == firstShape) ? Color.YELLOW : Color.RED);
graphics.fill(firstShape);
graphics.setColor((selected == secondShape) ? Color.YELLOW : Color.GREEN);
graphics.fill(secondShape);
}
// updates 'selected' based on x,y coordinate and redraws component on change.
public void selectShapeUnder (int x, int y) {
Shape oldSelected = selected;
// note that since second shape is draw on top of first, we give second preference.
// for overlapping shapes the selection should be consistent with the gui display.
if (secondShape.contains(x, y))
selected = secondShape;
else if (firstShape.contains(x, y))
selected = firstShape;
else
selected = null;
if (selected != oldSelected)
repaint();
}
public static final void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override public void run () {
JFrame frame = new JFrame("Shape Select");
frame.getContentPane().add(new ShapeSelect(), BorderLayout.CENTER);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
I want to make something akin to a diagram editor - an application that allows the user to create, view and edit a bunch of shapes on a canvas. My GUI has essentially three parts - a standard JMenuBar, one JPanel on the right side for showing info about the shapes and a JPanel next to it that should be used for visualizing the shapes.
Aside from that, I have a package that defines the shapes. For simplicity's sake say there is just a Square, containing the following information: coordinates on the canvas, size, user-defined name and description, color.
The main class of the project (an extension of JFrame) contains lists of Squares.
Now, I could visualize the shapes by simply drawing them in the JPanel using drawrect and whatnot, but I want to create an interactive editor - if the user right-clicks on a visualized shape, a context menu would pop-up allowing him to move it, change its properties or remove the shape altogether. Clicking an empty spot in the JPanel would allow the user (again, via a menu) to create a new shape.
Is there an automated way to do this as opposed to manually keeping a matrix mapping each pixel of the canvas to a certain shape and checking it upon right click? One where I could say, for example, draw this here within the JPanel and do something onclick...
A related question, when I edit the shape in the JPanel, how do I access the Square/Circle list in the main application class, so that I can really change it? I suppose this is simple, but right now I don't know how I would do it.
Please point me in the right direction, anything will be appreciated, tips, examples, links to relevant tutorials...
As suggested by #eugener, classes that implement the Shape interface have a contains() method that is useful for this. GraphPanel is an example that illustrates several of the features you mention.
The most common way to accomplish this is to allow shapes to determine if point is inside the shape. Hopefully your code is object oriented and each type of your shape is a class.
All you have to do is to define a method such as boolean isInside( point: Point) for each shape. Once you have those all you have to do is walk the shapes in the reverse z-order (from top to bottom) and and see where the mouse click point lends. If it does not lend on any shape - you clicked the canvas. Once you have this info you can show an appropriate menu.
Hope this helps
I am working on a project in which I have a background image with specific points of interest. Each of these specific points will have a custom button class overlaid on it so that when I click the point, I'm actually clicking the button. However, I would like to be able to rotate the background image and have the buttons rotate with the image so that the custom buttons are still overlaid on the specific points. Any tips as to how I should go about doing this?
Are you actually wanting to rotate 4 different images and move them around the square, but always keeping them upright? Or are you rotating a single image so that after one button click the single image is on its side? If the former, then that can be easily done by using a container (a JPanel) that uses BorderLayout, and having four JPanels with background images and JButtons held in the container JPanel at the four compass points of the BorderLayout: BorderLayout.EAST, BorderLayout.WEST, BorderLayout.NORTH, and BorderLayout.SOUTH (although Java gurus prefer you use the newer constants, i.e., BorderLayout.PAGE_START). Then when a button is pressed, remove components and re-add but in a rotated order.
If you want to do the latter, then things get a bit trickier in that you'll likely need to use AffineTransforms, rotate instance to rotate the container, and you'll need to perform the same transformation on the point of the mouse press/click/release, so that the rotated buttons receive correct clicks. If the container is not square, things get even trickier still.
I have a JPanel which has a line, circle, etc. Now when I click on the line, will the event get reported as a line event or a general JFrame event. I need to be able to move the line if the user clicks on the line and moves it. Is this possible in Java2D?
Yes, but you will need to do some work (see java.awt.Shape). Basically you need to track a list of Shapes. The JPanel will recieve a mouse event, which you can translate to (x,y) coordinates. You can then call Shape.contains(x,y) to see if your various shapes were clicked on.
This will work well for Circle, Polygon, Arc, etc; however in the case of Line2D, it won't work as easily, but you can use Line2D.intersects() with a small rectangle around the mouse click (this is also good UI since you don't want to force the user to click exactly on a pixel that is hard to see).
There is no such concept as a "line event" unless you decide to implement one.
I would suggest adding a MouseListener and a MouseMotionListener to the Canvas or JPanel onto which your geometric shapes are drawn. Use the MouseListener's mousePressed(MouseEvent) callback to determine whether a given shape has been clicked on. Once you've established this, use MouseMotionListener's mouseDragged(MouseEvent) method to move and redraw the shape as the mouse cursor is moved.
Here's a simple example that demonstrates some of the techniques adduced in other answers.
I created a canvas markup library in Java a few years back and if you don't need to worry about transforms on the canvas (scaling, rotation, etc.) it is very easy to do.
Basically you just need to maintain a collection of the canvas shapes in a List (not a Set because Z order is probably important). The mouse listener will be on your canvas, and not on individual shapes. Add new items to the beginning of your collection (or iterate the list backwards later).
When the canvas receives a mouse down event iterate through your collection of shapes until you find one that is underneath your mouse coordinates. The easiest way to do this is to have your shapes implement an interface that defines some sort of hitPoint(int x, int y) method. That way your rectangles can implement a contains(), lines can do intersects() or graphics paths, you can account for some hit padding, etc.
Taking it one step further, your shapes should define their own draw(Graphics2D g) method so that you can easily do things like selection boxes, or set the paint mode to XOR to make shape 'moving' easier. The paintComponent method of your canvas would just have to iterate through your collection of shapes, calling shape.draw(g) on each one, passing in the graphics instance provided to the paintComponent method.