Skip to content

Commit ebfda66

Browse files
authored
Add files via upload
1 parent fc41fcd commit ebfda66

File tree

5 files changed

+903
-0
lines changed

5 files changed

+903
-0
lines changed

source/OutlineComponent.java

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import java.awt.BasicStroke;
2+
import java.awt.Color;
3+
import java.awt.Dimension;
4+
import java.awt.Graphics;
5+
import java.awt.Graphics2D;
6+
import java.awt.Insets;
7+
import java.awt.Rectangle;
8+
import java.awt.RenderingHints;
9+
import java.awt.Shape;
10+
import javax.swing.JComponent;
11+
12+
/**
13+
* A component that paints the outline of a Shape object. Click detection will
14+
* be determined by the Shape itself, not the bounding Rectangle of the Shape.
15+
*
16+
* Shape objects can be created with an X/Y offset. These offsets will
17+
* be ignored and the Shape outline will always be painted at (0, 0) so the
18+
* outline is fully contained within the component.
19+
*/
20+
public class OutlineComponent extends JComponent
21+
{
22+
private Shape shape;
23+
private int thickness = 1;
24+
private boolean antiAliasing = true;
25+
26+
private BasicStroke stroke;
27+
private Rectangle strokeBounds;
28+
29+
/**
30+
* Create a OutlineComponent with a 1 pixel BLACK outline
31+
*
32+
* @param shape the shape to be painted
33+
*/
34+
public OutlineComponent(Shape shape)
35+
{
36+
this(shape, Color.BLACK, 1);
37+
}
38+
39+
/**
40+
* Create a OutlineComponent with a 1 pixel outline painted in the
41+
* specified color
42+
*
43+
* @param shape the shape to be painted
44+
* @param color the outline color of the shape
45+
*/
46+
public OutlineComponent(Shape shape, Color color)
47+
{
48+
this(shape, color, 1);
49+
}
50+
51+
/**
52+
* Create a OutlineComponent with a BLACK outline painted at the
53+
* specified thickness
54+
*
55+
* @param shape the shape to be painted
56+
* @param thickness the thickness of the outline
57+
*/
58+
public OutlineComponent(Shape shape, int thickness)
59+
{
60+
this(shape, Color.BLACK, thickness);
61+
}
62+
63+
/**
64+
* Create a OutlineComponent with the outline painted in the specified
65+
* color and thickness
66+
*
67+
* @param shape the Shape outline to be painted
68+
* @param color the color of the outline
69+
* @param thicknes the thickness of the outline
70+
*/
71+
public OutlineComponent(Shape shape, Color color, int thickness)
72+
{
73+
setShape( shape );
74+
setForeground( color );
75+
setThickness( thickness );
76+
77+
setOpaque( false );
78+
}
79+
80+
/**
81+
* Get the Shape of the component
82+
*
83+
* @returns the the Shape of the compnent
84+
*/
85+
public Shape getShape()
86+
{
87+
return shape;
88+
}
89+
90+
/**
91+
* Set the Shape for this component
92+
*
93+
* @param shape the Shape of the component
94+
*/
95+
public void setShape(Shape shape)
96+
{
97+
this.shape = shape;
98+
resetStroke();
99+
revalidate();
100+
repaint();
101+
}
102+
103+
/**
104+
* Get the thickness of the Shape outline when painted
105+
*
106+
* @return the Shape outline thickness
107+
*/
108+
public int getThickness()
109+
{
110+
return thickness;
111+
}
112+
113+
/**
114+
* Set the Shape outline thickness
115+
*
116+
* @param thickness the Shape outline in pixels
117+
*/
118+
public void setThickness(int thickness)
119+
{
120+
this.thickness = thickness;
121+
resetStroke();
122+
revalidate();
123+
repaint();
124+
}
125+
126+
/**
127+
* Use AntiAliasing when painting the shape
128+
*
129+
* @returns true for AntiAliasing false otherwise
130+
*/
131+
public boolean isAntiAliasing()
132+
{
133+
return antiAliasing;
134+
}
135+
136+
/**
137+
* Set AntiAliasing property for painting the Shape
138+
*
139+
* @param antiAliasing true for AntiAliasing, false otherwise
140+
*/
141+
public void setAntiAliasing(boolean antiAliasing)
142+
{
143+
this.antiAliasing = antiAliasing;
144+
revalidate();
145+
repaint();
146+
}
147+
148+
private void resetStroke()
149+
{
150+
stroke = new BasicStroke( thickness );
151+
strokeBounds = stroke.createStrokedShape(shape).getBounds();
152+
}
153+
154+
/**
155+
* {@inheritDoc}
156+
*/
157+
@Override
158+
public Dimension getPreferredSize()
159+
{
160+
// Include Border insets and Shape bounds
161+
162+
Insets insets = getInsets();
163+
int adjustment = getAdjustment();
164+
165+
// Determine the preferred size
166+
167+
int width = insets.left + insets.right + strokeBounds.width + adjustment;
168+
int height = insets.top + insets.bottom + strokeBounds.height + adjustment;
169+
170+
return new Dimension(width, height);
171+
}
172+
173+
private int getAdjustment()
174+
{
175+
// For odd thicknesses
176+
177+
if (thickness % 2 == 1)
178+
return -1;
179+
180+
// For even thicknesses we also need to check for anti aliasing
181+
182+
return (isAntiAliasing()) ? 1 : 0;
183+
}
184+
185+
/**
186+
* {@inheritDoc}
187+
*/
188+
@Override
189+
public Dimension getMinimumSize()
190+
{
191+
return getPreferredSize();
192+
}
193+
194+
/**
195+
* {@inheritDoc}
196+
*/
197+
@Override
198+
public Dimension getMaximumSize()
199+
{
200+
return getPreferredSize();
201+
}
202+
203+
@Override
204+
protected void paintComponent(Graphics g)
205+
{
206+
super.paintComponent(g);
207+
208+
// Graphics2D is required for antialiasing and painting Shapes
209+
210+
Graphics2D g2d = (Graphics2D)g.create();
211+
212+
if (isAntiAliasing())
213+
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
214+
215+
// The draw() method will paint the Shape outline (up/left) of the
216+
// filled Shape. Since we want all the painting to be done at a zero
217+
// offset we must manully translate the Shape (down/right) before it
218+
// is painted.
219+
220+
int shift = getShift();
221+
222+
// Shape translation (ie. non-zero X/Y position in bounding rectangle)
223+
// and Border insets.
224+
225+
Insets insets = getInsets();
226+
227+
// Do all translations at once
228+
229+
int translateX = insets.left - strokeBounds.x + shift;
230+
int translateY = insets.top - strokeBounds.y + shift;
231+
g2d.translate(translateX, translateY);
232+
233+
// Draw the Shape with the specified Color and thickness
234+
235+
g2d.setStroke( stroke );
236+
g2d.setColor( getForeground());
237+
g2d.draw( shape );
238+
239+
g2d.dispose();
240+
}
241+
242+
/**
243+
* This method will only determine if the point is in the bounds of the
244+
* Shape. The outline of the Shape as painted by the draw() method
245+
* is not considered part of the Shape.
246+
*
247+
* {@inheritDoc}
248+
*/
249+
@Override
250+
public boolean contains(int x, int y)
251+
{
252+
Insets insets = getInsets();
253+
int shift = getShift();
254+
255+
// Check to see if the Shape contains the point. Take into account
256+
// the Shape X/Y coordinates, Border insets and Shape translation.
257+
258+
int translateX = x + strokeBounds.x - insets.left;
259+
int translateY = y + strokeBounds.y - insets.top;
260+
261+
return shape.contains(translateX, translateY);
262+
}
263+
264+
/**
265+
* The painting of the Shape outline may be shifted depending on the
266+
* stroke thickness. The painting is shifted to make sure it starts
267+
* at the top/left of the component.
268+
*/
269+
private int getShift()
270+
{
271+
int shift = (thickness % 2 == 0) ? 0 : -1;
272+
return shift;
273+
}
274+
}

0 commit comments

Comments
 (0)