Home Java Delving deeper into Graphics2D

Delving deeper into Graphics2D

by admin

Good afternoon, Habragers!
Delving deeper into Graphics2D
Today I will again try to draw your attention to some sides and subtleties of working with graphics in Java. I’ve already briefly described in previous article some of the available tools and ways to create components and UIs, but that’s just the tip of the iceberg. That’s why I want to devote special attention (and an article)to working with graphics. Of course I mean Graphics2D – Java 3D is a big separate topic (probably it will be mentioned later but not today).
So, from the previous article you should already know some basics about building components – let’s try to extend that knowledge.
To begin with, if we consider any component in terms of MVC – it consists of 3 parts :
Model – model, which stores data about the state of the component and from which the appearance is built
View – Directly visual display component
Controller – is responsible for component control (events from keyboard, mouse and other input devices)
In fact, all standard Swing components are built using MVC pattern. For example, in the JButton, the ButtonModel is responsible for the behavior and state of the button (Controller and Model), and the ButtonUI in turn is responsible for its external representation (View). As a result, the JButton class itself is pretty much left out. In most cases, it’s the Graphics2D implementation, which actually serves as the basis for drawing the entire interface.
I will not argue that there is a lot of different material on this topic, but it is so fragmented and scattered over the Web that I think it is not superfluous to collect it all in one place and present it consistently.

The way you dress

No matter what anyone says, interface has always played, is playing, and will continue to play an important role in the success of any application. Depending on the audience of the app, the interface can be either marginal or paramount. With games and editing apps for example, the interface and usability is everything (partly because it is something you use a lot or have been using for a long time).
So, today we will take a look at the standard tools provided by Graphics2D and some mteodics and tricks for rendering components of any complexity.
Of course, without a clever idea there’s nothing you can do, but alas, I’m powerless here. Probably a designer/UX-specialist can help you if your fantasy is really bad. To be honest, it can be quite difficult to "squeeze out" what a new component will look like. Sometimes it’s even more difficult and time-consuming than actually writing working code and debugging it.
Anyway, let’s get to the point…

A small table of contents

  1. Figures
  2. RenderingHints
  3. Fill
  4. Alignment when drawing
  5. Stroke
  6. Shadows
  7. Correct "clipping"
  8. Animation
  9. A few tricks
  10. WebLookAndFeel
  11. In conclusion…
  12. Resources

Figures

You can’t do anything without shapes. For any component, any simple thing, you need to draw the outlines of the parts. It’s not nice to do it manually from pixel to pixel. Especially if you need to add anti-aliasing and other effects to drawing. Thank God you don’t need to do it manually – all the standard shapes (Line2D, Rectangle2D, RoundRectangle2D, Ellipse2D, Arc2D etc.)that you may need for drawing are already implemented – just specify their coordinates and size for drawing in a certain place.
In addition to the standard shapes, there are also specific ones (like GeneralPath) that let you quickly create your own shapes.
There are also some individual projects that have different specific forms :
http://java-sl.com/shapes.html
http://geosoft.no/graphics/
http://designervista.com/shapes/index.php
http://www.jfree.org/jcommon/
There is no point in describing each individual figure – you can read about them in detail here or here (both descriptions also include examples of use).
I just want to clarify a little bit about how a shape is drawn and what affects the final result. Suppose you have a shape (Shape shape):

  • You can use 2 different methods – g2d.fill(shape) and g2d.draw(shape). Fill – Fills all the pixels inside the shape, draw – draws the outline of the shape.
  • The color or colors in which the drawing takes place is the responsibility of the set heir of the Paint class (g2d.setPaint(Paint paint)) – It provides the paintbrush with colors for each individual pixel of the area. The simplest version of this mode is any color (Color) – it simply returns the same color for each pixel. More complicated examples are, for example, gradient and texture fills (GradientPaint, TexturePaint, etc.).
  • The thickness, frequency (or dash), and type of merging at the corners and edges of the line boundary of the shape being drawn is affected by the Stroke setting (g2d.setStroke(Stroke)).
  • Anti-aliasing, body and text quality, and other things are affected by the different options in the chapter below.

RenderingHints

Smoothing is part of the standard toolkit that comes "bundled" with Graphics2D:
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
This is enough to enable anti-aliasing for all the figures to be rendered later.
The main thing is to remember to turn off antialiasing after your operations, if you don’t want anything drawn afterwards to also use antialiasing – for example if you want to create your own rendering of the button background and don’t want to antialiase the default rendered text.
The value of this parameter (RenderingHints.KEY_ANTIALIASING) also affects text anti-aliasing if it has a default selection.
It is possible to separately turn on/off the need for text anti-aliasing :
g2d.setRenderingHint ( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
If you set this parameter to anything other than VALUE_TEXT_ANTIALIAS_DEFAULT, it will ignore the value of RenderingHints.KEY_ANTIALIASING.
Here is a small and simple example showing the differences between rendering with and without antialiasing :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
If you look carefully, you will also notice many other settings available in the RenderingHints:
KEY_ANTIALIASING – shapes (and text) anti-aliasing settings
KEY_RENDERING – adjust the quality/speed of rendering
KEY_DITHERING – color mixing for limited palette
KEY_TEXT_ANTIALIASING – text anti-aliasing
KEY_TEXT_LCD_CONTRAST – text contrast (100 to 250) when rendering with special text anti-aliasing
KEY_FRACTIONALMETRICS – adjust the "accuracy" of the text characters drawing
KEY_INTERPOLATION is a setting which modifies the image pixels when rendering (e.g., when you rotate the image)
KEY_ALPHA_INTERPOLATION – adjust the quality/speed of alpha processing
KEY_COLOR_RENDERING – adjust the quality/speed of color processing
KEY_STROKE_CONTROL – allows you to modify the geometry of shapes to improve the final look
Most of these settings are usually left in the "default" state. However, they can be very useful in some specific cases.
For example, setting KEY_INTERPOLATION to VALUE_INTERPOLATION_BILINEAR lets you avoid quality loss when you modify the image (rotate/shrink/shift etc.), or you can improve the text contrast in your background by changing KEY_TEXT_LCD_CONTRAST without affecting the text rendering code.
In any case, you should use these settings carefully and avoid them "overriding" your rendering method, since, for example, the same enabled antialiasing in JTextField will cause text to flatten and change (and probably shift).

Fill

There are several available heirs to the Paint class that allow you to paint/fill the shapes you draw in different ways:

  • Color – is the simplest, allowing you to draw or fill a shape with a single color.
  • TexturePaint – allows an existing BufferedImage to be used as a background image for filling/drawing.
  • GradientPaint – a simple linear gradient from one point to another with a start and end color.
  • LinearGradientPaint – a more complex linear gradient from one point to another with any number of intermediate colors with redefinable distances between them.
  • RadialGradientPaint – a circular gradient with the center of the circle and the radius, as well as the colors and distances between them, similar to LinearGradientPaint.

For illustration – a small example using 3 different gradients to fill three equal parts on a component :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
By the way, probably not everyone knows that you can also specify transparency (alpha) when specifying a color in any fill/draw.
For example new Color ( 255, 255, 255, 128 ) – 50% transparent white color. Alpha in this case is 128. It can vary from 0 to 255. 0 is completely transparent, 255 is completely opaque (the default).

Alignment when drawing

So, we’re systematically moving on to more complicated things…
Composite lets you specify different "modes" for combining new shapes/images you want to render with pixels you already have on the canvas.
I was able to find the most successful illustrations of the different "modes" of combination, as well as an example of their real influence on the rendered figures here here There’s also a detailed description of each of the "modes".
In practice personally I mostly use AlphaComposite.SRC_OVER option and setting transparency for rendering further elements in certain transparency on top of already rendered stuff. Also there are some interesting examples of using some modes when working with images, but more about that later.
In addition to Composite, it is also possible to create your own shapes using the standard shapes and combining them with different geometric operations. A small example on this topic :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
Only 2 lines were needed to create this figure :

Area area = new Area ( new Ellipse2D.Double (0, 0, 75, 75 ));
area.add ( new Area ( new Ellipse2D.Double ( 0, 50, 75, 75 )));

The most interesting thing is that the boundary of the newfigure doesn’t include the inside of the ellipse boundaries (the ones that are inside the opposite ellipse).
In general, Area can be used fora lot of different things: it can not only add newshapes to existing ones, but also create an intersection area or exclude some shapes from others, it’s possible to get the general bounds of a shape from it.Also with its help you can quickly and easily create complex shapes from any other available simple shapes.
If you need a particular shape that you use quite often, it’s probably best to create a descendant of the Shape class and implement the shape in it. This saves both time and size.

Stroke

or as prompted by TheShock {"circle".
In fact, Stroke gives you the ability to set the bordering style for any call to the draw method of the graphic.
In the standard JDK there is only 1 implementation of Stroke interface – BasicStroke. It allows you to set the line width, how lines are joined at corners and how they look at the ends, and to create dashed lines.
To set Stroke in the code, do the following :

g2d.setStroke ( new BasicStroke ( 2f ));

This example will cause all subsequent borders to be drawn with a width of 2 pixels.
By the way, don’t be alarmed that the width and some other parameters can be set to float(although pixels should be integers)- non-integer numbers will only create "smeared" lines/outlines when drawing, which may even be useful in some cases.
You can read more about the features of BasicStroke, e.g, here
Although there is only one implementation of Stroke in the JDK, there are other projects and examples which describe the real possibilities of this tool, but more about that later.

Shadows

There is no standard implementation of shadows in Java2D, but there are quite a few ways to achieve the "effect" of a shadow – which is what I will talk about in this chapter.
Let’s start with the simplest options…
A shadow obtained by a slight shift/modification of the original figure
Delving deeper into Graphics2D
The second image is a narrower and tidier version of this type of shadow.
(An example and source code can be downloaded here )
The shadow obtained by this method is applicable only ifit goes no more than 1-3 pixels beyond the edge of the original figure, otherwise it starts to look unnatural. Also this method requires quite cumbersome code – for any single figure you have to repeatedly check and adjust the shadow.
Iterative shadow
A shadow obtained by repeatedly drawing a modified shape. Each subsequent iteration increases (as an option)the size of the figure and decreases transparency (or changes color).
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
This option is good because it doesn’t limit your shadow size, but it’s even more cumbersome and harder to modify. It’s also the most suboptimal of all the options in terms of rendering speed.
Gradient shadow
This option is based on using gradient fills around the edges of the shape.
In fact, we have 8 parts around the edges in the case of a rectangle, which will require a fill :
Delving deeper into Graphics2D
LinearGradientPaintin 4 cases, and RadialGradientPaintin the other 4 cases. The result is a neat shadow like this :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
You can also vary the location of the gradient and get other interesting options, such as this :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
The advantage of this variant is the speed and quality of shadow rendering. However, the size of the code again suffers, as you can see from the example.
The shadow obtained by Stroke modification when rendering
More specifically, a shape is drawn several times in a loop with a changeable color/transparency and Stroke, which gives you a kind of shadow:
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
Since the shape of the figure has absolutely no effect on the rendering of the shadow, this technique can be used for any even the most cunning figures:
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
You can also easily put the shadow itself into a separate independent method :

private void drawShade ( Graphics2D g2d, RoundRectangle2D rr, Color shadeColor, int width )
{
Composite comp = g2d.getComposite ();
Stroke old = g2d.getStroke ();
width = width * 2;
for ( int i = width; i > = 2; i -= 2 )
{
float opacity = ( float ) ( width - i ) / ( width - 1 );
g2d.setColor ( shadeColor );
g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_OVER, opacity ) );
g2d.setStroke ( new BasicStroke ( i ) );
g2d.draw ( rr );
}
g2d.setStroke ( old );
g2d.setComposite ( comp );
}

Shadow image
We can not get a figure to draw the shadow with any of the previous methods if the image is not just rectangular, but for example round or even amorphous. To create a shadow in this case we will approach it from a slightly different angle – using AlphaComposite we will create a one-color copy of the original image and use it as a shadow:
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
Of course, in some cases such a shadow will do, but I would like to get smoother/gradient edges of the shadow itself. In solving this problem, we will use filtering. To be more precise – we use a special filter on the "shadow" of the image for its more realistic look:
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
To be honest, for this example I took a prefabricated version of the filter given here But even without any tools you can quickly and easily blur the shadow from the first example and put it under the image.
By the way, you can use this shadow for shapes, too, if you put them on a separate image beforehand, on the basis of which the filter will work. That could be quite applicable if the figure(s) don’t change dynamically when executing an application – it’s enough to "capture" it once and use that image when rendering. However, I personally don’t like this variant because it is resource consuming, so I excluded it from the list.

Correct "clipping"

I would like to tell separately about one more important aspect, necessary when working with graphics and creating "correct" components – working with the clip, or – cutting off unnecessary parts when rendering.
To use this tool, all you have to do is set the form in which the "cutoff" will take place:

g.setClip (x, y, width, height );
g.setClip (
new Rectangle ( x, y, width, height ) );
g.setClip ( new Rectangle2D.Double ( x, y, width, height ) );

All three of these methods will result in the same rectangular cutoff area.
There are many instances where this tool can come in handy.
First of all, when you draw any component, there is always a certain form of clipping (usually it’s the component’s bounds) preset – it keeps the component from "going over the top" of its boundaries. It must be taken into account when setting your own specific cutoff area. It’s easy enough to do it like this :

Shape oldClip = g.getClip ();
Shape newClip =
new Rectangle ( x, y, width, height );
Area clip = new Area ( oldClip );
clip.intersect ( new Area ( newClip ) );
g.setClip ( clip );

You are actually merging the existing clipping area with the new one. That way you don’t lose the constraint as a component boundary, but you add the new one you need.
If you go back to the chapter on shadow creation, or more precisely to point 4, it can be improved by possibly cutting off part of the shadow:

public static void drawShade ( Graphics2D g2d, Shape shape, Color shadeColor, int width,
Shape clip, boolean round )
{
Shape oldClip = g2d.getClip ();
if (clip != null )
{
Area finalClip = new Area ( clip );
finalClip.intersect ( new Area ( oldClip ));
g2d.setClip ( finalClip );
}
Composite comp = g2d.getComposite ();
float currentComposite = 1f;
if ( comp instanceof AlphaComposite )
{
currentComposite = ( ( AlphaComposite ) comp ).getAlpha ();
}
Stroke old = g2d.getStroke();
width = width * 2;
for ( int i = width;i >= 2;i -= 2 )
{
float opacity = ( float ) ( width - i ) / ( width - 1 );
g2d.setColor ( shadeColor );
g2d.setComposite ( AlphaComposite
getInstance ( AlphaComposite.SRC_OVER, opacity * currentComposite ) );
g2d.setStroke (
new BasicStroke ( i, round ? BasicStroke.CAP_ROUND : BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND ) );
g2d.draw ( shape );
}
g2d.setStroke ( old );
g2d.setComposite ( comp );
if ( clip != null )
{
g2d.setClip ( oldClip );
}
}

So it becomes possible to additionally pass the desired cutoff area to thismethod – the rest is done by the method itself.
As shown by some tests – cutting off unpainted parts (for example, what goes off the screen) does not give any significant speedup gain. But this is understandable, because all calculations such as "what, where and how to draw" and rendering itself still work, even ifyou set clip to 1 "available" pixel. So "manual" optimization will be much more useful in a case like this.
Clip for the most part is designed for easy rendering of complex parts and proper boundary limitation, so that neighboring components do not overlap each other.

Animation

So, it’s time to combine some of the knowledge you’ve gained and do something more interesting.
The animation itself is simple enough to understand and represents only the change in rendered objects over time. In practice, however, there are many more questions and problems.
Depending on the type of animation, you may need quite a bit of additional code responsible for "developments" and displaying changes. It is also important not to forget about optimization when repainting – that is, it is desirable to repaintonly those areas of the animated component in which changes occurred. To do this, just call the repaint( newRectangle ( x, y, width, height ) method.)
Let’s see a small animation example: we’ll create the effect of a glare running through the text of a JLabel. To do this, we first need to define what it will look like, so that we know exactly what we need to do.
We’ll use a gradient-filled circle (from light gray in the center to the color of the font (black) on the edge) as our "flare". Also we need a separate timer to move this circle from the beginning to the end of the component.
So, this is what the rendering of the component will look like :

private boolean animating = false ;
private int animationX = 0;
private int animationLength = 140;
private float [] fractions = {0f, 1f };
private Color[] colors = new Color[]{ new Color ( 200, 200, 200 ), new Color ( 0, 0, 0 ) };
protected void paintComponent ( Graphics g )
{
//create an image with only text without a background
BufferedImage bi =
new BufferedImage ( getWidth (), getHeight (), BufferedImage.TYPE_INT_ARGB );
Graphics2D g2d = bi.createGraphics();
g2d.setFont( g.getFont () );
//draw text
super.paintComponent (g2d );
//When animation is running, draw a glare
if ( animating )
{
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_IN ) );
g2d.setPaint ( new RadialGradientPaint ( animationX - animationLength / 2,
getHeight () / 2, animationLength / 2, fractions, colors ) );
g2d.fillRect ( animationX - animationLength, 0, animationLength, getHeight () );
}
//transfer this image to the original component
g2d.dispose ();
g.drawImage ( bi, 0, 0, null );
}

The main point is hidden in creating a separate image on which to draw the text, and setting Composite when rendering the glare.
The image is required to have only those pixels with text on it, otherwise the standard rendering on the coming in method Graphics AlphaComposite.SRC_IN will fill the entire filled rectangle with the specified gradient, because in addition to the text on the graphic will already be present rendered by the underlying panel (panels) background.
So now it remains for us to implement a timer that triggers, say, when the cursor enters the JLabel area:

private static class AnimatedLabel extends JLabel
{
public AnimatedLabel ( String text )
{
super ( text );
setupSettings ();
}
private void setupSettings ()
{
//To hide the background
setOpaque ( false );
// For a more obvious display of the effect
setFont ( getFont ().deriveFont ( Font BOLD ).deriveFont ( 30f ) );
//listener, initiating animation
addMouseListener ( new MouseAdapter()
{
public void mouseEntered ( MouseEvent e )
{
startAnimation ();
}
});
}
private Timer animator = null ;
private boolean animating = false ;
private int animationX = 0;
private int animationLength = 140;
private float [] fractions = {0f, 1f };
private Color[] colors = new Color[]{ new Color ( 200, 200, 200 ), new Color ( 0, 0, 0 ) };
private void startAnimation ()
{
// ifthe animation is already running, ignore the request
if ( animator != null animator.isRunning () )
{
return ;
}
// Starting animation
animating = true ;
animationX = 0;
animator = new Timer ( 1000 / 48, new ActionListener()
{
public void actionPerformed ( ActionEvent e )
{
// Increase the coordinate until it reaches the end of the component
if ( animationX < getWidth () + animationLength )
{
animationX += 10;
AnimatedButton. this repaint ();
}
else
{
animator.stop ();
}
}
});
animator.start ();
}
protected void paintComponent ( Graphics g )
{
//
}
}

I doubt that anything needs to be explained in this piece of code (beyond what is described by the comments).
What we end up with is this like this funny effect.
Of course, this example is just the tip of the iceberg. With a good imagination you can create a lot of interesting static or animated things.

A few tricks

Sometimes it’s not enough to know the standard tools to do everything you need to do. You have to invent different "tricks". I would like to share what I could find on the web and "invented bikes" with you in a few individual examples. So, let’s get down to business…
Smoothing the edge of the image
Suppose we need to crop an image to a certain shape, but the standard tools like clip’s will lead to unfortunate results. In this case you should use the AlphaComposite :

ImageIcon icon = new ImageIcon ( iconPath );
BufferedImage roundedImage = new BufferedImage ( icon.getIconWidth (), icon.getIconHeight (),
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2d = roundedImage.createGraphics ();
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setPaint( Color.WHITE );
g2d.fillRoundRect ( 0, 0, icon.getIconWidth (), icon.getIconHeight (), 10, 10 );
g2d.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_IN ));
g2d.drawImage ( icon.getImage (), 0, 0, null );
g2d.dispose ();
ImageIcon roundedIcon = new ImageIcon ( roundedImage );

So we first draw a rectangle smoothed in the corners and then use it as a stencil to superimpose the image on top.
Using the customizable Stroke
Quite a while ago I found a very interesting article on writing your own Stroke class.
In some cases, a Stroke like this can make rendering a lot easier.
Using blur/shadow filters
On the same resource you can also find a very interesting article on blur.
May be useful to those who do image work.
Using the GlyphVector
One of the "hardest" parts of working with graphics is rendering text, especially ifthat text is subject to change. To position the text correctly you have to calculate its dimensions and draw based on them.
There are two tools for these calculations :
1. FontMetrics
You can get it directly from the Graphics2D instance (g2d.getFontMetrics ()).
It allows you to define different indentation sizes and heights for a set font.
2. GlyphVector
This variant is more useful when you want to center the text at the Y-coordinate, because it lets you know the exact size of the text:

FontMetrics fm = g2d.getFontMetrics ();
GlyphVector gv = g2d.getFont ().createGlyphVector ( fm.getFontRenderContext (),
"Text" );
Rectangle visualBounds = gv.getVisualBounds ().getBounds ();

You can also get quite a lot of useful information from the GlyphVector, such as the outer border of the text (the shape of the text itself):

Shape textOutline = gv.getOutline ();

Creating your own Tooltip manager
There are quite a few unsightly things in Swing, which, however, can be easily ironed out by changing the UI of components or by working with their layout. In some cases, it’s not that simple.
The tooltip system implemented for all J-components belongs to such cases. Since all of the displayed tooltips are displayed on a separate strictly rectangular popup (lightweight or heavyweight depending on whether the tooltip falls within the window boundaries) – we’re limited to that area and shape, which is quite sad. And that’s in the era of neat web interfaces and rounded shapes!
Of course you can break the standard TooltipManager, find places to create windows, set them to the right shape through newfeatures, but that first – still does not work on all OSes, and second – this is not a good and optimal option.
So I had the idea of using the GlassPane, which is present in all standard windows, as a display area for the tooltips (and later, all kinds of internal windows).
The idea itself has several parts :
1. Separate manager ( TooltipManager.java ), which would create a GlassPane for the specific window on which the tooltip opens and remember it, ifnecessary. Further creation of the tooltip would happen directly in the GlassPane.
2. GlassPane ( TooltipGlassPane.java ) which is a transparent tooltip that skips all events and displays the tooltips at the right moment
3. The tooltip itself ( CustomTooltip.java ) is a standard J-component which displays any content in a nice-looking way, depending on its location on the GlassPane
As a result, the displayed tooltips will look something like this :
Delving deeper into Graphics2D
You can see a full realization of this idea in library described below. I didn’t cite it because of the sheer volume of code. The names of the classes that implement the idea were noted above and correspond to the classnames in the project.
Editable list
In addition to adding your own "bikes", in some cases you can supplement the functionality of existing components quite easily and elegantly, using standard tools.
I think everybody knows Jlist component, which, by the way, originally didn’t have a possibility to edit. So now I’ll show you how you can fix this problem.
First, you need to create an interfacethat will implement the editor itself :

public interface ListEditor
{
public void installEditor ( JList list, Runnable startEdit );
public boolean isCellEditable ( JList list, int index, Object value );
public JComponent createEditor ( JList list, int index, Object value );
public Rectangle getEditorBounds ( JList list, int index, Object value , Rectangle cellBounds );
public void setupEditorActions ( JList list, Object value , Runnable cancelEdit,
Runnable finishEdit );
public Object getEditorValue ( JList list, int index, ObjectoldValue );
public boolean updateModelValue ( JList list, int index, Object value , boolean updateSelection );
public void editStarted ( JList list, int index );
public void editFinished ( JList list, int index, ObjectoldValue, Object newValue );
public void editCancelled ( JList list, int index );
}

The successor of this interface will provide everything you need to create and display an editor on the list. Except that it’s quite expensive to inherit and define a complete set of these functions every time, let’s make an abstractclass that implements more or less the common part for different editors :

public abstract class AbstractListEditor implements ListEditor
{
protected int editedCell = -1;
public void installEditor ( final JList list, final Runnable startEdit )
{
// Listeners initiating the edit
list.addMouseListener ( new MouseAdapter()
{
public void mouseClicked ( MouseEvent e )
{
if ( e.getClickCount () == 2 SwingUtilities.isLeftMouseButton ( e ) )
{
startEdit.run ();
}
}
});
list.addKeyListener ( new KeyAdapter()
{
public void keyReleased ( KeyEvent e )
{
if ( e.getKeyCode () == KeyEvent.VK_F2 )
{
startEdit.run ();
}
}
});
}
public boolean isCellEditable ( JList list, int index, Object value )
{
return true ;
}
public Rectangle getEditorBounds ( JList list, int index, Object value , Rectangle cellBounds )
{
// Standard dimensions of the editor are identical to the dimensions of the cell being edited
return new Rectangle ( 0, 0, cellBounds.width, cellBounds.height + 1 );
}
public boolean updateModelValue ( JList list, int index, Object value , boolean updateSelection )
{
// Updating the model at the end of editing
ListModel model = list.getModel ();
if (model instanceof DefaultListModel )
{
( ( DefaultListModel )model ).setElementAt ( value , index );
list.repaint();
return true ;
}
else if ( model instanceof AbstractListModel )
{
finalObject[] values = new Object[ model.getSize ()];
for ( int i = 0;i <model.getSize ();i++ )
{
if ( editedCell != i )
{
values[ i ] =model.getElementAt ( i );
}
else
{
values[ i ] = value ;
}
}
list.setModel ( new AbstractListModel()
{
public int getSize ()
{
return values.length;
}
public Object getElementAt ( int index )
{
return values[ index ];
}
});
return true ;
}
else
{
return false ;
}
}
public void editStarted ( JList list, int index )
{
// Saving the index of the cell to be edited
editedCell = index;
}
public void editFinished ( JList list, int index, ObjectoldValue, Object newValue )
{
//clearing the index of the cell to be edited
editedCell = -1;
}
public void editCancelled ( JList list, int index )
{
//clearing the index of the cell to be edited
editedCell = -1;
}
public boolean isEditing ()
{
// Checking editor activity
return editedCell != -1;
}
}

Now on the basis of this abstract classit will be very easy to implement, for example, a text editor for a list – it will look something like this WebStringListEditor.java
There is only one last thing left: the method of installing the editor to the list. Let’s put it in a separate class and make it staticfor convenience:

public class ListUtils
{
public static void installEditor ( final JList list, final ListEditor listEditor )
{
//the actual code that starts editing in the list
final Runnable startEdit = new Runnable()
{
public void run ()
{
// check for a highlighted cell
final int index = list.getSelectedIndex ();
if ( list.getSelectedIndices ().length != 1 || index == -1 )
{
return ;
}
// check that the selected cell can be edited
final Object value = list.getModel ().getElementAt ( index );
if ( !listEditor.isCellEditable ( list, index, value ))
{
return ;
}
// Creating an editor
final JComponent editor = listEditor.createEditor ( list, index, value );
//set its size and listener for resizing
editor.setBounds ( computeCellEditorBounds ( index, value , list, listEditor ) );
list.addComponentListener ( new ComponentAdapter()
{
public void componentResized ( ComponentEvent e )
{
checkEditorBounds ();
}
private void checkEditorBounds ()
{
Rectangle newBounds =
computeCellEditorBounds ( index, value , list, listEditor );
if ( newBounds != null !newBounds.equals ( editor.getBounds () ) )
{
editor.setBounds ( newBounds );
list.revalidate ();
list.repaint ();
}
}
});
//add a component on top of the list
list.add ( editor );
list.revalidate ();
list.repaint ();
// Taking the focus to the editor
if ( editor.isFocusable () )
{
editor.requestFocus ();
editor.requestFocusInWindow ();
}
//create methods to cancel and finish editing
final Runnable cancelEdit = new Runnable()
{
public void run ()
{
list.remove ( editor );
list.revalidate ();
list.repaint ();
listEditor.editCancelled ( list, index );
}
};
final Runnable finishEdit = new Runnable()
{
public void run ()
{
Object newValue = listEditor.getEditorValue ( list, index, value );
boolean changed =
listEditor.updateModelValue ( list, index, newValue, true );
list.remove ( editor );
list.revalidate ();
list.repaint ();
if ( changed )
{
listEditor.editFinished ( list, index, value , newValue );
}
else
{
listEditor.editCancelled ( list, index );
}
}
};
listEditor.setupEditorActions ( list, value , cancelEdit, finishEdit );
//Announcement of the beginning of editing
listEditor.editStarted ( list, index );
}
};
listEditor.installEditor ( list, startEdit );
}
private static Rectangle computeCellEditorBounds ( int index, Object value , JList list,
ListEditor listEditor )
{
// method that returns the location of the editor on the list
Rectangle cellBounds = list.getCellBounds ( index, index );
if ( cellBounds != null )
{
Rectangle editorBounds = listEditor.getEditorBounds ( list, index, value , cellBounds );
return new Rectangle ( cellBounds.x + editorBounds.x, cellBounds.y + editorBounds.y,
editorBounds.width, editorBounds.height );
}
else
{
return null ;
}
}
}

That’s it, now we can install the editor on any available list with one line of code :
Delving deeper into Graphics2D
The main thing is not to forget to change the methods for setting/receiving the value to/from the editor, if you use non String in the model. To do this, just override two methods (of course, depending on the complexity of the editor you need) in WebStringListEditor.java – createEditor and getEditorValue.

Web Look And Feel

Delving deeper into Graphics2D
Since I spend quite a bit of time working with Swing and graphics (especially lately), I had the idea of creating a separate library of UI, advanced components and utilities, often so necessary in various places of code. And little by little this idea began to come to life in the form of a separate library. WebLookAndFeel
As a basis I have taken various techniques and practices that I have already written about in this article, as well as some others, which I will describe in detail later.
By the way, given that I have a second version of our commercial product , this idea was once again spurred on by the build need for similar functionality and features.
Actually, the library includes most of the techniques described in this article, as well as many other interesting and useful things for working with graphics :

  • Web LaF proper – a separate full-fledged cross-platform pure-java LaF
  • Set of additional components (also stylized for Web LaF)- FileChooser/ColorChooser/Gallery/Calendar and many others (lots of them!)
  • A set of class utilities for: working with graphics, files, text, images, and many other things (for example, to ease downloading files by URL, reading images, performing operations on them, etc.)

Also here are some technical "pros" and features :

  • Fully cross-platform and easily customizable LookAndFeel (directly – tested on Windows XP/Vista/7, Mac OS X 10.6.7, Ubuntu 9/11)
  • Minimal memory requirements (will run easily on -Xms16m -Xmx16m and will do even heavy image manipulations, not to mention LaF and various utilities)
  • Single-jar bundle – i.e.everything you need is in one jar library, working for all operating systems
  • Requires JDK 1.6 update 20 or newer (nothing to say about JDK 7 – I haven’t tried it, but in my opinion no deprecated things, no test features are used)

Read more about it at separate site
To many people this library may seem like some kind of "bicycle" and someone will probably claim that similar functionality is already present in other well-known libraries…
To this I can say two things :
First, – other libraries provide their components in the best case with the possibility of stylization for this or that UI (and often with crutches). This library, on the other hand, already contains a set of additional components stylized for the general WebLaF look, which will be expanded over time.
Second – the various things I’ve added and am about to add to the library are nowhere to be found. I haven’t found a single sensible ColorChooser implementation on the net which could replace the awful JColorChooser. There are no other implementations of JFileChooser at all. Of course there is SWT, but to be honest with him there are other problems, difficulties and restrictions, which I suggest not to go into yet and discard this option – after all we are talking about Swing.
So, to be able to "feel" the interface and components, I added a class to the library with a small demonstration of completed components :
Delving deeper into Graphics2D
(An example and source code can be downloaded here )
Full source code and distribution of the library is available at :
http://weblookandfeel.com/download/
At the moment the library is incomplete and there are some minor flaws in LookAndFeel, "unsmoothed" places in the code, as well as some functionality and appearance remains very "questionable" and will still be changed for the better.
In connection with all of the above – would be glad to hear any comments, suggestions and constructive criticism on the topic 🙂

In conclusion…

Hopefully now your knowledge in terms of graphics in Java has become a bit more structured and tangible and applicable in practice.
I hope the source codes of the WebLookAndFeel library above can help you in mastering graphics. There’s much more stuff there (like implementing UI-classes for each of the standard Swing components, organizing LookAndFeel, global managers etc.) than I covered in this article, so I strongly suggest to study it (if you have time and will, of course).
I also apologize for the rather long break in publishing articles – gathering materials, alas, took much longer than I expected, not to mention bringing the WebLaF library to a more or less usable form.
The next article is likely to be dedicated to writing your own LookAndFeel (with examples and pictures, of course), as well as some UI peculiarities of some J-components.

Resources

Various third-party resources on topics :
(including those cited in the article)
MVC
http://lib.juga.ru/article/articleview/163/1/68/
Shapes
http://java.sun.com/developer/technicalArticles/GUI/java2d/java2dpart1.html
http://www.datadisk.co.uk/html_docs/java/graphics_java2d.htm
Extended Shapes
http://java-sl.com/shapes.html
http://geosoft.no/graphics/
http://designervista.com/shapes/index.php
http://www.jfree.org/jcommon/
Composite
http://download.oracle.com/javase/tutorial/2d/advanced/compositing.html
BasicStroke
http://www.projava.net/Glava9/Index13.htm
Extended Strokes
http://www.jhlabs.com/java/java2d/strokes/
Blurring
http://www.jhlabs.com/ip/blurring.html
Libraries used in WebLookAndFeel :
java-image-scaling
http://code.google.com/p/java-image-scaling/
TableLayout
http://java.net/projects/tablelayout
Data Tips
Unfortunately, there are currently no resources available for this library
Jericho HTML Parser
http://jericho.htmlparser.net/docs/index.html
and Icon Sets :
Fugue icons
http://code.google.com/p/fugue-icons-src/
Fatcow icons
http://www.fatcow.com/free-icons

Special thanks to…

to the project Source Code Highlighter for readable code highlighter :
http://virtser.net/blog/post/source-code-highlighter.aspx
as well as Picture hosting For storing images :
http://hostingkartinok.com/
Upd1: I corrected links and illustrations a bit
Upd2: The library distributions were updated with fixes for various inaccuracies and problems that occurred
Upd3: Fixed inaccuracies and crooked images in the article

You may also like