Java Swing/AWT - painting objects with context menus - java

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

Related

How to draw selectable, movable, and re-sizable rectangles on JavaFX canvas?

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());
}
});
}
});

Identify which of three partially overlapping images is clicked

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.

How can you detect a mouse-click event on an Image object in Java?

Implementing "Kings' Corners" (glorified multiplayer Solitaire) in Java.
I'm trying to allow a player to drag a card (image) from their hand to somewhere else on the table. The problem is that the player's hand is "fanned" so the images of the cards are rotated and they overlap.
Here is an example of a hand:
I've considered making each card a JPanel, but the issue then is that I'd have to paint the card rotated inside its rectangular JPanel, as they themselves can't be rotated. Ideally I'd like to avoid mouse-x,y formulas to determine which card is being chosen.
Using an event-driven approach, how can I determine which card is chosen from the hand?
AWT (and Swing) components are normally rectangular (aligned to the axes).
But this does not have to be the case - while the real bounds must be rectangular, the actual area which a component uses can be smaller. Component supports a contains(Point) method, which will get called by the event dispatch mechanism whenever the question arises to which component a point belongs - for example, for mouse clicks. (Overlapping of different components will be handled by the z-order inside the parent container.)
You can implement this method based on the Shape.contains() method, using a affine transformed rectangle as your shape. Each of your rotated components would know its own shape (or generate it on the fly from its AffineTransform, the same one which would also be used for painting itself).
Have a custom LayoutManager which arranges your cards, too. (Don't use CardLayout, despite the name.)
I'm not sure I would follow the way of having separate components for each card, but you certainly need some objects which represent the rotated rectangles.

how to extract information from jpanel

I am trying a project called white board sharing in which I need to get the information from a panel and that information is like some drawings so how can I get it from a panel. In that panel I am drawing some rectangles circles.
In case you want to react on mouse events you might want to investigate the following sections of the Java API.
MouseListener
MouseMotionListener
Also if you want to react on mouse events that are triggered when the users moves his mouse over a drawn element you might be interested in Graphics2D and the classes that implement Shape.
You can pass a Graphics object to the panel for it to paint the items that were drawn.
It may help to think of the board as a view of some model that records an abstract representation of the board's content. As a concrete example, this GraphPanel has a very simple model containing nothing more than a List<Node> and a List<Edge>. These two members could be wrapped and serialized as discussed in this tutorial.

Java2D: Capturing an event on a Line object

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.

Categories