1 package be
.nikiroo
.utils
.ui
;
3 import java
.awt
.BorderLayout
;
5 import java
.awt
.Dimension
;
6 import java
.awt
.Graphics
;
7 import java
.awt
.Graphics2D
;
9 import java
.awt
.Rectangle
;
10 import java
.awt
.image
.BufferedImage
;
11 import java
.util
.HashMap
;
14 import javax
.swing
.BorderFactory
;
15 import javax
.swing
.JComponent
;
16 import javax
.swing
.JLabel
;
17 import javax
.swing
.JPanel
;
18 import javax
.swing
.SwingConstants
;
21 * A graphical item that can be presented in a list and supports user
24 * Can be selected, hovered...
28 abstract public class Item
extends JPanel
{
29 static private final long serialVersionUID
= 1L;
31 static private Map
<Dimension
, BufferedImage
> empty
= new HashMap
<Dimension
, BufferedImage
>();
32 static private Map
<Dimension
, BufferedImage
> error
= new HashMap
<Dimension
, BufferedImage
>();
33 static private Map
<Color
, JComponent
> statuses
= new HashMap
<Color
, JComponent
>();
36 private boolean selected
;
37 private boolean hovered
;
39 private String mainTemplate
;
40 private String secondaryTemplate
;
42 private boolean hasImage
;
44 private JLabel secondary
;
45 private JLabel statusIndicatorOn
;
46 private JLabel statusIndicatorOff
;
47 private JLabel statusIndicatorUnknown
;
49 private boolean imageError
;
51 private String cachedMain
;
52 private String cachedOptSecondary
;
53 private Integer cachedStatus
;
56 * Create a new {@link Item}
59 * an ID that represents this {@link Item} (can be NULL)
61 * this {@link Item} will contain an image
63 public Item(String id
, boolean hasImage
) {
65 this.hasImage
= hasImage
;
71 protected int getMaxDisplaySize() {
75 protected int getCoverWidth() {
79 protected int getCoverHeight() {
83 protected int getTextWidth() {
84 return getCoverWidth() + 40;
87 protected int getTextHeight() {
91 protected int getCoverVOffset() {
95 protected int getCoverHOffset() {
99 protected int getHGap() {
103 /** Colour used for the secondary item (author/word count). */
104 protected Color
getSecondaryColor() {
105 return new Color(128, 128, 128);
109 * Return a display-ready version of the main information to show.
111 * Note that you can make use of {@link Item#limit(String)}.
113 * @return the main info in a ready-to-display version, cannot be NULL
115 abstract protected String
getMainInfoDisplay();
118 * Return a display-ready version of the secondary information to show.
120 * Note that you can make use of {@link Item#limit(String)}.
122 * @return the main info in a ready-to-display version, cannot be NULL
124 abstract protected String
getSecondaryInfoDisplay();
127 * The current status for the status indicator.
129 * Note that NULL and negative values will create "hollow" indicators, while
130 * other values will create "filled" indicators.
132 * @return the status which can be NULL, presumably for "Unknown"
134 abstract protected Integer
getStatus();
137 * Get the background colour to use according to the given state.
139 * Since it is an overlay, an opaque colour will of course mask everything.
142 * the item is enabled
144 * the item is selected
146 * the mouse cursor currently hovers over the item
148 * @return the correct background colour to use
150 abstract protected Color
getOverlayColor(boolean enabled
, boolean selected
,
154 * Get the colour to use for the status indicator.
156 * Return NULL if you don't want a status indicator for this state.
159 * the current status as returned by {@link Item#getStatus()}
161 * @return the base colour to use, or NULL for no status indicator
163 abstract protected Color
getStatusIndicatorColor(Integer status
);
166 * Initialise this {@link Item}.
168 private void init(boolean hasImage
) {
170 title
= new JLabel();
171 mainTemplate
= "${MAIN}";
172 secondary
= new JLabel();
173 secondaryTemplate
= "${SECONDARY}";
174 secondary
.setForeground(getSecondaryColor());
176 JPanel idTitle
= null;
177 if (id
!= null && !id
.isEmpty()) {
178 JLabel idLabel
= new JLabel(id
);
179 idLabel
.setPreferredSize(new JLabel(" 999 ").getPreferredSize());
180 idLabel
.setForeground(Color
.gray
);
181 idLabel
.setHorizontalAlignment(SwingConstants
.CENTER
);
183 idTitle
= new JPanel(new BorderLayout());
184 idTitle
.setOpaque(false);
185 idTitle
.add(idLabel
, BorderLayout
.WEST
);
186 idTitle
.add(title
, BorderLayout
.CENTER
);
189 setLayout(new BorderLayout());
191 add(idTitle
, BorderLayout
.CENTER
);
192 add(secondary
, BorderLayout
.EAST
);
195 title
= new JLabel();
196 secondary
= new JLabel();
197 secondaryTemplate
= "";
199 String color
= String
.format("#%X%X%X", getSecondaryColor()
200 .getRed(), getSecondaryColor().getGreen(),
201 getSecondaryColor().getBlue());
202 mainTemplate
= String
204 + "<body style='width: %d px; height: %d px; text-align: center;'>"
205 + "${MAIN}" + "<br>" + "<span style='color: %s;'>"
206 + "${SECONDARY}" + "</span>" + "</body>"
207 + "</html>", getTextWidth(), getTextHeight(), color
);
209 int ww
= Math
.max(getCoverWidth(), getTextWidth());
210 int hh
= getCoverHeight() + getCoverVOffset() + getHGap()
213 JPanel placeholder
= new JPanel();
215 .setPreferredSize(new Dimension(ww
, hh
- getTextHeight()));
216 placeholder
.setOpaque(false);
218 JPanel titlePanel
= new JPanel(new BorderLayout());
219 titlePanel
.setOpaque(false);
220 titlePanel
.add(title
, BorderLayout
.NORTH
);
222 titlePanel
.setBorder(BorderFactory
.createEmptyBorder());
224 setLayout(new BorderLayout());
225 add(placeholder
, BorderLayout
.NORTH
);
226 add(titlePanel
, BorderLayout
.CENTER
);
229 // Cached values are NULL, so it will be updated
234 * The book current selection state.
236 * @return the selection state
238 public boolean isSelected() {
243 * The book current selection state,
246 * TRUE if it is selected
248 public void setSelected(boolean selected
) {
249 if (this.selected
!= selected
) {
250 this.selected
= selected
;
256 * The item mouse-hover state.
258 * @return TRUE if it is mouse-hovered
260 public boolean isHovered() {
265 * The item mouse-hover state.
268 * TRUE if it is mouse-hovered
270 public void setHovered(boolean hovered
) {
271 if (this.hovered
!= hovered
) {
272 this.hovered
= hovered
;
278 * Update the title, paint the item.
281 public void paint(Graphics g
) {
282 Rectangle clip
= g
.getClipBounds();
283 if (clip
== null || clip
.getWidth() <= 0 || clip
.getHeight() <= 0) {
291 Image img
= image
== null ?
getBlank(false) : image
;
293 img
= getBlank(true);
295 int xOff
= getCoverHOffset() + (getWidth() - getCoverWidth()) / 2;
296 g
.drawImage(img
, xOff
, getCoverVOffset(), null);
298 Integer status
= getStatus();
299 boolean filled
= status
!= null && status
> 0;
300 Color indicatorColor
= getStatusIndicatorColor(status
);
301 if (indicatorColor
!= null) {
302 UIUtils
.drawEllipse3D(g
, indicatorColor
, getCoverWidth() + xOff
303 + 10, 10, 20, 20, filled
);
307 Color bg
= getOverlayColor(isEnabled(), isSelected(), isHovered());
309 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
313 * The image to display on image {@link Item} (NULL for non-image
316 * @return the image or NULL for the empty image or for non image
319 public Image
getImage() {
320 return hasImage ? image
: null;
324 * Change the image to display (does not work for non-image {@link Item}s).
326 * NULL is allowed, an empty image will then be shown.
329 * the new {@link Image} or NULL
332 public void setImage(Image image
) {
333 this.image
= hasImage ? image
: null;
337 * Use the ERROR image instead of the real one or the empty one.
339 * @return TRUE if we force use the error image
341 public boolean isImageError() {
346 * Use the ERROR image instead of the real one or the empty one.
349 * TRUE to force use the error image
351 public void setImageError(boolean imageError
) {
352 this.imageError
= imageError
;
356 * Make the given {@link String} display-ready (i.e., shorten it if it is
362 * @return the display-ready value
364 protected String
limit(String value
) {
368 if (value
.length() > getMaxDisplaySize()) {
369 value
= value
.substring(0, getMaxDisplaySize() - 3) + "...";
376 * Update the title with the currently registered information.
378 private void updateData() {
379 String main
= getMainInfoDisplay();
380 String optSecondary
= getSecondaryInfoDisplay();
381 Integer status
= getStatus();
383 // Cached values can be NULL the first time
384 if (!main
.equals(cachedMain
)
385 || !optSecondary
.equals(cachedOptSecondary
)
386 || status
!= cachedStatus
) {
387 title
.setText(mainTemplate
//
388 .replace("${MAIN}", main
) //
389 .replace("${SECONDARY}", optSecondary
) //
391 secondary
.setText(secondaryTemplate
//
392 .replace("${MAIN}", main
) //
393 .replace("${SECONDARY}", optSecondary
) //
396 Color bg
= getOverlayColor(isEnabled(), isSelected(), isHovered());
400 remove(statusIndicatorUnknown
);
401 remove(statusIndicatorOn
);
402 remove(statusIndicatorOff
);
404 Color k
= getStatusIndicatorColor(getStatus());
405 JComponent statusIndicator
= statuses
.get(k
);
406 if (!statuses
.containsKey(k
)) {
407 statusIndicator
= generateStatusIndicator(k
);
408 statuses
.put(k
, statusIndicator
);
411 if (statusIndicator
!= null)
412 add(statusIndicator
, BorderLayout
.WEST
);
418 this.cachedMain
= main
;
419 this.cachedOptSecondary
= optSecondary
;
420 this.cachedStatus
= status
;
424 * Generate a status indicator for the given colour.
429 * @return a status indicator ready to be used
431 private JLabel
generateStatusIndicator(final Color color
) {
432 JLabel indicator
= new JLabel(" ") {
433 private static final long serialVersionUID
= 1L;
436 public void paint(Graphics g
) {
440 Dimension sz
= statusIndicatorOn
.getSize();
441 int s
= Math
.min(sz
.width
, sz
.height
);
442 int x
= Math
.max(0, (sz
.width
- sz
.height
) / 2);
443 int y
= Math
.max(0, (sz
.height
- sz
.width
) / 2);
445 UIUtils
.drawEllipse3D(g
, color
, x
, y
, s
, s
, true);
450 indicator
.setBackground(color
);
454 private Image
getBlank(boolean error
) {
455 Dimension key
= new Dimension(getCoverWidth(), getCoverHeight());
456 Map
<Dimension
, BufferedImage
> images
= error ? Item
.error
: Item
.empty
;
458 BufferedImage blank
= images
.get(key
);
460 blank
= new BufferedImage(getCoverWidth(), getCoverHeight(),
461 BufferedImage
.TYPE_4BYTE_ABGR
);
463 Graphics2D g
= blank
.createGraphics();
465 g
.setColor(Color
.white
);
466 g
.fillRect(0, 0, getCoverWidth(), getCoverHeight());
468 g
.setColor(error ? Color
.red
: Color
.black
);
469 g
.drawLine(0, 0, getCoverWidth(), getCoverHeight());
470 g
.drawLine(getCoverWidth(), 0, 0, getCoverHeight());
474 images
.put(key
, blank
);