X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Freader%2FLocalReaderBook.java;h=9a4290de201912ca0e12f6b0357e1ced2cbc1176;hp=e7bea6aa5c80736661cc7a852000c57743030b8a;hb=2284842831ea46e89b97dd22b6e294caad361f30;hpb=92fb0719f84f5b6734b51e528332546d78e9ccec diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java index e7bea6a..9a4290d 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java @@ -19,6 +19,8 @@ import javax.swing.JLabel; import javax.swing.JPanel; import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.utils.ui.UIUtils; /** * A book item presented in a {@link LocalReaderFrame}. @@ -47,8 +49,21 @@ class LocalReaderBook extends JPanel { * the {@link LocalReaderBook} itself */ public void action(LocalReaderBook book); + + /** + * A popup menu was requested for this {@link LocalReaderBook}. + * + * @param book + * the {@link LocalReaderBook} itself + * @param e + * the {@link MouseEvent} that generated this call + */ + public void popupRequested(LocalReaderBook book, MouseEvent e); } + private static final long serialVersionUID = 1L; + + // TODO: export some of the configuration options? private static final int COVER_WIDTH = 100; private static final int COVER_HEIGHT = 150; private static final int SPINE_WIDTH = 5; @@ -59,39 +74,40 @@ class LocalReaderBook extends JPanel { private static final int TEXT_WIDTH = COVER_WIDTH + 40; private static final int TEXT_HEIGHT = 50; private static final String AUTHOR_COLOR = "#888888"; + private static final Color BORDER = Color.black; + private static final long doubleClickDelay = 200; // in ms + // - private static final long serialVersionUID = 1L; private JLabel icon; - private JLabel tt; + private JLabel title; private boolean selected; private boolean hovered; private Date lastClick; - private long doubleClickDelay = 200; // in ms + private List listeners; + private MetaData meta; + private boolean cached; - public LocalReaderBook(MetaData meta) { - if (meta.getCover() != null) { - BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH - + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET, - BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D g = resizedImage.createGraphics(); - g.setColor(Color.white); - g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT); - g.drawImage(meta.getCover(), 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT, - null); - g.dispose(); - - icon = new JLabel(new ImageIcon(resizedImage)); - } else { - // TODO: a big black "X" ? - icon = new JLabel(" [ no cover ] "); - } + /** + * Create a new {@link LocalReaderBook} item for the given {@link Story}. + * + * @param meta + * the story {@code}link MetaData} + * @param cached + * TRUE if it is locally cached + */ + public LocalReaderBook(MetaData meta, boolean cached) { + this.cached = cached; + this.meta = meta; String optAuthor = meta.getAuthor(); if (optAuthor != null && !optAuthor.isEmpty()) { optAuthor = "(" + optAuthor + ")"; } - tt = new JLabel( + + icon = new JLabel(generateCoverIcon(meta.getCover())); + + title = new JLabel( String.format( "" + "" @@ -100,18 +116,17 @@ class LocalReaderBook extends JPanel { TEXT_WIDTH, TEXT_HEIGHT, meta.getTitle(), AUTHOR_COLOR, optAuthor)); - this.setLayout(new BorderLayout(10, 10)); - this.add(icon, BorderLayout.CENTER); - this.add(tt, BorderLayout.SOUTH); + setLayout(new BorderLayout(10, 10)); + add(icon, BorderLayout.CENTER); + add(title, BorderLayout.SOUTH); setupListeners(); - setSelected(false); } /** * The book current selection state. * - * @return the selected + * @return the selection state */ public boolean isSelected() { return selected; @@ -121,25 +136,45 @@ class LocalReaderBook extends JPanel { * The book current selection state. * * @param selected - * the selected to set + * TRUE if it is selected */ public void setSelected(boolean selected) { - this.selected = selected; - repaint(); + if (this.selected != selected) { + this.selected = selected; + repaint(); + } } + /** + * The item mouse-hover state. + * + * @param hovered + * TRUE if it is mouse-hovered + */ private void setHovered(boolean hovered) { - this.hovered = hovered; - repaint(); + if (this.hovered != hovered) { + this.hovered = hovered; + repaint(); + } } + /** + * Setup the mouse listener that will activate {@link BookActionListener} + * events. + */ private void setupListeners() { listeners = new ArrayList(); addMouseListener(new MouseListener() { public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + popup(e); + } } public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + popup(e); + } } public void mouseExited(MouseEvent e) { @@ -151,55 +186,130 @@ class LocalReaderBook extends JPanel { } public void mouseClicked(MouseEvent e) { - Date now = new Date(); - if (lastClick != null - && now.getTime() - lastClick.getTime() < doubleClickDelay) { - click(true); - } else { - click(false); + if (isEnabled()) { + Date now = new Date(); + if (lastClick != null + && now.getTime() - lastClick.getTime() < doubleClickDelay) { + click(true); + } else { + click(false); + } + + lastClick = now; } - lastClick = now; } - }); - } - private void click(boolean doubleClick) { - for (BookActionListener listener : listeners) { - if (doubleClick) { - listener.action(this); - } else { - listener.select(this); + private void click(boolean doubleClick) { + for (BookActionListener listener : listeners) { + if (doubleClick) { + listener.action(LocalReaderBook.this); + } else { + listener.select(LocalReaderBook.this); + } + } } - } + + private void popup(MouseEvent e) { + for (BookActionListener listener : listeners) { + listener.select((LocalReaderBook.this)); + listener.popupRequested(LocalReaderBook.this, e); + } + } + }); } + /** + * Add a new {@link BookActionListener} on this item. + * + * @param listener + * the listener + */ public void addActionListener(BookActionListener listener) { listeners.add(listener); } + /** + * The Library {@code}link MetaData} of the book represented by this item. + * + * @return the meta + */ + public MetaData getMeta() { + return meta; + } + + /** + * This item {@link LocalReader} library cache state. + * + * @return TRUE if it is present in the {@link LocalReader} cache + */ + public boolean isCached() { + return cached; + } + + /** + * This item {@link LocalReader} library cache state. + * + * @param cached + * TRUE if it is present in the {@link LocalReader} cache + */ + public void setCached(boolean cached) { + if (this.cached != cached) { + this.cached = cached; + repaint(); + } + } + + /** + * Paint the item, then call {@link LocalReaderBook#paintOverlay(Graphics)}. + */ @Override public void paint(Graphics g) { super.paint(g); + paintOverlay(g); + } + + /** + * Draw a partially transparent overlay if needed depending upon the + * selection and mouse-hover states on top of the normal component, as well + * as a possible "cached" icon if the item is cached. + */ + public void paintOverlay(Graphics g) { + Rectangle clip = g.getClipBounds(); + if (clip.getWidth() <= 0 || clip.getHeight() <= 0) { + return; + } int h = COVER_HEIGHT; int w = COVER_WIDTH; - int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 4; + int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 1; + int yOffset = HOFFSET; + + if (BORDER != null) { + if (BORDER != null) { + g.setColor(BORDER); + g.drawRect(xOffset, yOffset, COVER_WIDTH, COVER_HEIGHT); + } + + xOffset++; + yOffset++; + } int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH, xOffset + w + SPINE_WIDTH, xOffset + w }; - int[] ys = new int[] { HOFFSET + h, HOFFSET + h + SPINE_HEIGHT, - HOFFSET + h + SPINE_HEIGHT, HOFFSET + h }; + int[] ys = new int[] { yOffset + h, yOffset + h + SPINE_HEIGHT, + yOffset + h + SPINE_HEIGHT, yOffset + h }; g.setColor(SPINE_COLOR_BOTTOM); g.fillPolygon(new Polygon(xs, ys, xs.length)); xs = new int[] { xOffset + w, xOffset + w + SPINE_WIDTH, xOffset + w + SPINE_WIDTH, xOffset + w }; - ys = new int[] { HOFFSET, HOFFSET + SPINE_HEIGHT, - HOFFSET + h + SPINE_HEIGHT, HOFFSET + h }; + ys = new int[] { yOffset, yOffset + SPINE_HEIGHT, + yOffset + h + SPINE_HEIGHT, yOffset + h }; g.setColor(SPINE_COLOR_RIGHT); g.fillPolygon(new Polygon(xs, ys, xs.length)); Color color = new Color(255, 255, 255, 0); - if (selected && !hovered) { + if (!isEnabled()) { + } else if (selected && !hovered) { color = new Color(80, 80, 100, 40); } else if (!selected && hovered) { color = new Color(230, 230, 255, 100); @@ -207,8 +317,40 @@ class LocalReaderBook extends JPanel { color = new Color(200, 200, 255, 100); } - Rectangle clip = g.getClipBounds(); g.setColor(color); g.fillRect(clip.x, clip.y, clip.width, clip.height); + + if (cached) { + UIUtils.drawEllipse3D(g, Color.green.darker(), COVER_WIDTH + + HOFFSET + 30, 10, 20, 20); + } + } + + /** + * Generate a cover icon based upon the given cover image (which may be + * NULL). + * + * @param image + * the cover image, or NULL for none + * + * @return the icon + */ + private ImageIcon generateCoverIcon(BufferedImage image) { + BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH + + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D g = resizedImage.createGraphics(); + g.setColor(Color.white); + g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT); + if (image != null) { + g.drawImage(image, 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT, null); + } else { + g.setColor(Color.black); + g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET + COVER_HEIGHT); + g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET + COVER_HEIGHT); + } + g.dispose(); + + return new ImageIcon(resizedImage); } }