From df6e2d88153be63b85aa8c0dfd4dae47762b6f0e Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Thu, 14 Mar 2019 22:10:08 +0100 Subject: [PATCH] gui: add a Properties page --- src/be/nikiroo/fanfix/reader/BasicReader.java | 87 +++++++ .../reader/tui/TuiReaderStoryWindow.java | 72 +----- .../fanfix/reader/ui/GuiReaderBook.java | 216 ++---------------- .../fanfix/reader/ui/GuiReaderFrame.java | 115 +++++++++- 4 files changed, 227 insertions(+), 263 deletions(-) diff --git a/src/be/nikiroo/fanfix/reader/BasicReader.java b/src/be/nikiroo/fanfix/reader/BasicReader.java index 71f19a1..83f2d3c 100644 --- a/src/be/nikiroo/fanfix/reader/BasicReader.java +++ b/src/be/nikiroo/fanfix/reader/BasicReader.java @@ -4,6 +4,13 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map.Entry; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.bundles.Config; @@ -14,6 +21,7 @@ import be.nikiroo.fanfix.library.BasicLibrary; import be.nikiroo.fanfix.library.LocalLibrary; import be.nikiroo.fanfix.supported.BasicSupport; import be.nikiroo.utils.Progress; +import be.nikiroo.utils.StringUtils; import be.nikiroo.utils.serial.SerialUtils; /** @@ -199,6 +207,45 @@ public abstract class BasicReader implements Reader { return source; } + /** + * Describe a {@link Story} from its {@link MetaData} and return a list of + * title/value that represent this {@link Story}. + * + * @param meta + * the {@link MetaData} to represent + * + * @return the information + */ + public static List> getMetaDesc(MetaData meta) { + List> metaDesc = new ArrayList>(); + + // TODO: i18n + + StringBuilder tags = new StringBuilder(); + for (String tag : meta.getTags()) { + if (tags.length() > 0) { + tags.append(", "); + } + tags.append(tag); + } + + metaDesc.add(new SimpleEntry("Author", meta.getAuthor())); + metaDesc.add(new SimpleEntry("Publication date", + formatDate(meta.getDate()))); + metaDesc.add(new SimpleEntry("Published on", meta + .getPublisher())); + metaDesc.add(new SimpleEntry("URL", meta.getUrl())); + metaDesc.add(new SimpleEntry("Word count", format(meta + .getWords()))); + metaDesc.add(new SimpleEntry("Source", meta.getSource())); + metaDesc.add(new SimpleEntry("Subject", meta + .getSubject())); + metaDesc.add(new SimpleEntry("Language", meta.getLang())); + metaDesc.add(new SimpleEntry("Tags", tags.toString())); + + return metaDesc; + } + /** * Open the {@link Story} with an external reader (the program will be * passed the main file associated with this {@link Story}). @@ -307,4 +354,44 @@ public abstract class BasicReader implements Reader { } } } + + static private String format(long value) { + String display = ""; + + while (value > 0) { + if (!display.isEmpty()) { + display = "." + display; + } + display = (value % 1000) + display; + value = value / 1000; + } + + return display; + } + + static private String formatDate(String date) { + long ms = 0; + + try { + ms = StringUtils.toTime(date); + } catch (ParseException e) { + } + + if (ms <= 0) { + SimpleDateFormat sdf = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ssXXX"); + try { + ms = sdf.parse(date).getTime(); + } catch (ParseException e) { + } + } + + if (ms > 0) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(new Date(ms)); + } + + // :( + return date; + } } diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java index 6ab9234..59842ad 100644 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java +++ b/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java @@ -1,11 +1,9 @@ package be.nikiroo.fanfix.reader.tui; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; +import java.util.Map; import jexer.TAction; import jexer.TButton; @@ -19,9 +17,9 @@ import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Paragraph; import be.nikiroo.fanfix.data.Paragraph.ParagraphType; import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.reader.BasicReader; import be.nikiroo.jexer.TSizeConstraint; import be.nikiroo.jexer.TTable; -import be.nikiroo.utils.StringUtils; /** * This window will contain the {@link Story} in a readable format, with a @@ -173,69 +171,21 @@ class TuiReaderStoryWindow extends TWindow { setCurrentTitle(meta.getTitle()); - StringBuilder tags = new StringBuilder(); - for (String tag : meta.getTags()) { - if (tags.length() > 0) { - tags.append(", "); - } - tags.append(tag); + List> metaDesc = BasicReader + .getMetaDesc(meta); + String[][] metaDescObj = new String[metaDesc.size()][2]; + int i = 0; + for (Map.Entry entry : metaDesc) { + metaDescObj[i][0] = " " + entry.getKey(); + metaDescObj[i][1] = entry.getValue(); + i++; } - table.setRowData(new String[][] { // - new String[] { " Author", meta.getAuthor() }, // - new String[] { " Publication date", formatDate(meta.getDate()) }, - new String[] { " Published on", meta.getPublisher() }, - new String[] { " URL", meta.getUrl() }, - new String[] { " Word count", format(meta.getWords()) }, - new String[] { " Source", meta.getSource() }, - new String[] { " Subject", meta.getSubject() }, - new String[] { " Language", meta.getLang() }, - new String[] { " Tags", tags.toString() } // - }); + table.setRowData(metaDescObj); table.setHeaders(Arrays.asList("key", "value"), false); table.toTop(); } - private String format(long value) { - String display = ""; - - while (value > 0) { - if (!display.isEmpty()) { - display = "." + display; - } - display = (value % 1000) + display; - value = value / 1000; - } - - return display; - } - - private String formatDate(String date) { - long ms = 0; - - try { - ms = StringUtils.toTime(date); - } catch (ParseException e) { - } - - if (ms <= 0) { - SimpleDateFormat sdf = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ssXXX"); - try { - ms = sdf.parse(date).getTime(); - } catch (ParseException e) { - } - } - - if (ms > 0) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - return sdf.format(new Date(ms)); - } - - // :( - return date; - } - /** * Append the current chapter. * diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java index dc27595..4683f71 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java @@ -1,36 +1,20 @@ package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Date; import java.util.EventListener; import java.util.List; -import javax.imageio.ImageIO; -import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; -import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ui.ImageUtilsAwt; -import be.nikiroo.utils.ui.UIUtils; /** * A book item presented in a {@link GuiReaderFrame}. @@ -73,20 +57,8 @@ class GuiReaderBook extends JPanel { 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; - private static final int SPINE_HEIGHT = 5; - private static final int HOFFSET = 20; - private static final Color SPINE_COLOR_BOTTOM = new Color(180, 180, 180); - private static final Color SPINE_COLOR_RIGHT = new Color(100, 100, 100); - 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 JLabel icon; private JLabel title; @@ -95,7 +67,6 @@ class GuiReaderBook extends JPanel { private Date lastClick; private List listeners; - private Reader reader; private MetaData meta; private boolean cached; @@ -113,7 +84,6 @@ class GuiReaderBook extends JPanel { */ public GuiReaderBook(Reader reader, MetaData meta, boolean cached, boolean seeWordCount) { - this.reader = reader; this.cached = cached; this.meta = meta; @@ -142,15 +112,17 @@ class GuiReaderBook extends JPanel { optSecondary = ""; } - icon = new JLabel(generateCoverIcon()); + icon = new JLabel(GuiReaderCoverImager.generateCoverIcon( + reader.getLibrary(), getMeta())); title = new JLabel( String.format( "" + "" + "%s" + "
" + "" + "%s" + "" + "" + "", - TEXT_WIDTH, TEXT_HEIGHT, meta.getTitle(), AUTHOR_COLOR, - optSecondary)); + GuiReaderCoverImager.TEXT_WIDTH, + GuiReaderCoverImager.TEXT_HEIGHT, meta.getTitle(), + AUTHOR_COLOR, optSecondary)); setLayout(new BorderLayout(10, 10)); add(icon, BorderLayout.CENTER); @@ -181,6 +153,15 @@ class GuiReaderBook extends JPanel { } } + /** + * The item mouse-hover state. + * + * @return TRUE if it is mouse-hovered + */ + private boolean isHovered() { + return this.hovered; + } + /** * The item mouse-hover state. * @@ -306,172 +287,7 @@ class GuiReaderBook extends JPanel { @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. - * - * @param g - * the {@link Graphics} to paint onto - */ - 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) - 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[] { 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[] { 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 (!isEnabled()) { - } else if (selected && !hovered) { - color = new Color(80, 80, 100, 40); - } else if (!selected && hovered) { - color = new Color(230, 230, 255, 100); - } else if (selected && hovered) { - color = new Color(200, 200, 255, 100); - } - - 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 {@link MetaData}. - * - * @return the icon - */ - private ImageIcon generateCoverIcon() { - BufferedImage resizedImage = null; - String id = getIconId(meta); - - InputStream in = Instance.getCache().getFromCache(id); - if (in != null) { - try { - resizedImage = ImageUtilsAwt.fromImage(new Image(in)); - in.close(); - in = null; - } catch (IOException e) { - Instance.getTraceHandler().error(e); - } - } - - if (resizedImage == null) { - try { - Image cover = null; - if (meta.getLuid() != null) { - cover = reader.getLibrary().getCover(meta.getLuid()); - } - if (cover == null) { - cover = reader.getLibrary() - .getSourceCover(meta.getSource()); - } - - 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 (cover != null) { - BufferedImage coverb = ImageUtilsAwt.fromImage(cover); - g.drawImage(coverb, 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(); - - if (id != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ImageIO.write(resizedImage, "png", out); - byte[] imageBytes = out.toByteArray(); - in = new ByteArrayInputStream(imageBytes); - Instance.getCache().addToCache(in, id); - in.close(); - in = null; - } - } catch (MalformedURLException e) { - Instance.getTraceHandler().error(e); - } catch (IOException e) { - Instance.getTraceHandler().error(e); - } - } - - if (resizedImage != null) { - return new ImageIcon(resizedImage); - } - - return null; - } - - /** - * Manually clear the icon set for this item. - * - * @param meta - * the meta of the story or source (if luid is null) - */ - public static void clearIcon(MetaData meta) { - String id = getIconId(meta); - Instance.getCache().removeFromCache(id); - } - - /** - * Get a unique ID from this meta (note that if the luid is null, it is - * considered a source and not a {@link Story}). - * - * @param meta - * the meta - * @return the unique ID - */ - private static String getIconId(MetaData meta) { - String id = null; - - String key = meta.getUuid(); - if (meta.getLuid() == null) { - // a fake meta (== a source) - key = "source_" + meta.getSource(); - } - - id = key + ".thumb_" + SPINE_WIDTH + "x" + COVER_WIDTH + "+" - + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@" + HOFFSET; - - return id; + GuiReaderCoverImager.paintOverlay(g, isEnabled(), isSelected(), + isHovered(), isCached()); } } diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java index b8db8f7..8d3142c 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java @@ -2,6 +2,7 @@ package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Font; import java.awt.Frame; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; @@ -20,7 +21,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import javax.swing.BorderFactory; import javax.swing.BoxLayout; +import javax.swing.ImageIcon; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; @@ -31,6 +34,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; @@ -308,6 +312,8 @@ class GuiReaderFrame extends JFrame { popup.add(createMenuItemRedownload()); popup.addSeparator(); popup.add(createMenuItemDelete()); + popup.addSeparator(); + popup.add(createMenuItemProperties()); popup.show(e.getComponent(), e.getX(), e.getY()); } @@ -653,7 +659,8 @@ class GuiReaderFrame extends JFrame { reader.clearLocalReaderCache(selectedBook.getMeta() .getLuid()); selectedBook.setCached(false); - GuiReaderBook.clearIcon(selectedBook.getMeta()); + GuiReaderCoverImager.clearIcon(selectedBook + .getMeta()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -796,6 +803,110 @@ class GuiReaderFrame extends JFrame { return delete; } + /** + * Create the properties menu item. + * + * @return the item + */ + private JMenuItem createMenuItemProperties() { + JMenuItem delete = new JMenuItem("Properties", KeyEvent.VK_P); + delete.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedBook != null) { + outOfUi(null, new Runnable() { + @Override + public void run() { + final MetaData meta = selectedBook.getMeta(); + new JFrame() { + private static final long serialVersionUID = 1L; + @SuppressWarnings("unused") + private Object init = init(); + + private Object init() { + // Borders + int top = 20; + int space = 10; + + // Image + ImageIcon img = GuiReaderCoverImager + .generateCoverIcon( + reader.getLibrary(), meta); + + // frame + setTitle(meta.getLuid() + ": " + + meta.getTitle()); + + setSize(800, img.getIconHeight() + 2 * top); + setLayout(new BorderLayout()); + + // Main panel + JPanel mainPanel = new JPanel( + new BorderLayout()); + JPanel mainPanelKeys = new JPanel(); + mainPanelKeys.setLayout(new BoxLayout( + mainPanelKeys, BoxLayout.Y_AXIS)); + JPanel mainPanelValues = new JPanel(); + mainPanelValues.setLayout(new BoxLayout( + mainPanelValues, BoxLayout.Y_AXIS)); + + mainPanel.add(mainPanelKeys, + BorderLayout.WEST); + mainPanel.add(mainPanelValues, + BorderLayout.CENTER); + + List> infos = BasicReader + .getMetaDesc(meta); + + Color trans = new Color(0, 0, 0, 1); + for (Entry info : infos) { + JTextArea key = new JTextArea(info + .getKey()); + key.setFont(new Font(key.getFont() + .getFontName(), Font.BOLD, key + .getFont().getSize())); + key.setEditable(false); + key.setLineWrap(false); + key.setBackground(trans); + mainPanelKeys.add(key); + + JTextArea value = new JTextArea(info + .getValue()); + value.setEditable(false); + value.setLineWrap(false); + value.setBackground(trans); + mainPanelValues.add(value); + } + + // Image + JLabel imgLabel = new JLabel(img); + imgLabel.setVerticalAlignment(JLabel.TOP); + + // Borders + mainPanelKeys.setBorder(BorderFactory + .createEmptyBorder(top, space, 0, 0)); + mainPanelValues.setBorder(BorderFactory + .createEmptyBorder(top, space, 0, 0)); + imgLabel.setBorder(BorderFactory + .createEmptyBorder(0, space, 0, 0)); + + // Add all + add(imgLabel, BorderLayout.WEST); + add(mainPanel, BorderLayout.CENTER); + + return null; + } + + }.setVisible(true); + } + }); + } + } + }); + + return delete; + } + /** * Create the open menu item for a book or a source (no LUID). * @@ -838,7 +949,7 @@ class GuiReaderFrame extends JFrame { selectedBook.getMeta().getLuid()); MetaData source = selectedBook.getMeta().clone(); source.setLuid(null); - GuiReaderBook.clearIcon(source); + GuiReaderCoverImager.clearIcon(source); } } }); -- 2.27.0