import be.nikiroo.fanfix.library.LocalLibrary;
import be.nikiroo.fanfix.reader.BasicReader;
import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
+import be.nikiroo.fanfix_swing.gui.viewer.Viewer;
import be.nikiroo.utils.Progress;
public class Actions {
- static public void openExternal(final BasicLibrary lib, MetaData meta,
+ static public void openBook(final BasicLibrary lib, MetaData meta,
final Container parent, final Runnable onDone) {
Container parentWindow = parent;
while (!(parentWindow instanceof Window) && parentWindow != null) {
final SwingWorker<File, Void> worker = new SwingWorker<File, Void>() {
private File target;
+ private Story story;
@Override
protected File doInBackground() throws Exception {
target = lib.getFile(luid, null);
+ story = lib.getStory(luid, null);
return null;
}
protected void done() {
try {
get();
- openExternal(target, isImageDocument);
+ boolean internalImg = Instance
+ .getInstance()
+ .getUiConfig()
+ .getBoolean(
+ UiConfig.IMAGES_DOCUMENT_USE_INTERNAL_READER,
+ true);
+ boolean internalNonImg = Instance
+ .getInstance()
+ .getUiConfig()
+ .getBoolean(
+ UiConfig.NON_IMAGES_DOCUMENT_USE_INTERNAL_READER,
+ true);
+
+ if (isImageDocument && internalImg || !isImageDocument
+ && internalNonImg) {
+ openInternal(story);
+ } else {
+ openExternal(target, isImageDocument);
+ }
} catch (Exception e) {
// TODO: i18n
UiHelper.error(parent, e.getLocalizedMessage(),
worker.execute();
}
+ /**
+ * Open the {@link Story} with an internal reader.
+ * <p>
+ * Asynchronous.
+ *
+ * @param story
+ * the story to open
+ */
+ static private void openInternal(Story story) {
+ Viewer viewer = new Viewer(Instance.getInstance().getLibrary(), story);
+ viewer.setVisible(true);
+ }
+
/**
* Open the {@link Story} with an external reader (the program will be
* passed the given target file).
* @throws IOException
* in case of I/O error
*/
- static public void openExternal(File target, boolean isImageDocument)
+ static private void openExternal(File target, boolean isImageDocument)
throws IOException {
String program = null;
if (isImageDocument) {
}
}
if (!ok) {
- throw new IOException(
- "Cannot find a program to start the file");
+ throw new IOException("Cannot find a program to start the file");
}
} else {
Instance.getInstance().getTraceHandler()
.trace("starting external program: " + program);
- proc = Runtime.getRuntime()
- .exec(new String[] { program, target.getAbsolutePath() });
+ proc = Runtime.getRuntime().exec(
+ new String[] { program, target.getAbsolutePath() });
}
if (proc != null && sync) {
} catch (IOException e) {
pg.done();
if (e instanceof UnknownHostException) {
- UiHelper.error(parent,
- Instance.getInstance().getTransGui().getString(
- StringIdGui.ERROR_URL_NOT_SUPPORTED,
- url),
- Instance.getInstance().getTransGui().getString(
- StringIdGui.TITLE_ERROR),
+ UiHelper.error(
+ parent,
+ Instance.getInstance()
+ .getTransGui()
+ .getString(
+ StringIdGui.ERROR_URL_NOT_SUPPORTED,
+ url),
+ Instance.getInstance().getTransGui()
+ .getString(StringIdGui.TITLE_ERROR),
null);
} else {
- UiHelper.error(parent,
- Instance.getInstance().getTransGui().getString(
- StringIdGui.ERROR_URL_IMPORT_FAILED,
- url, e.getMessage()),
+ UiHelper.error(
+ parent,
+ Instance.getInstance()
+ .getTransGui()
+ .getString(
+ StringIdGui.ERROR_URL_IMPORT_FAILED,
+ url, e.getMessage()),
Instance.getInstance().getTransGui()
- .getString(StringIdGui.TITLE_ERROR),
- e);
+ .getString(StringIdGui.TITLE_ERROR), e);
}
}
final BookInfo book = data.get(index);
BasicLibrary lib = Instance.getInstance().getLibrary();
- Actions.openExternal(lib, book.getMeta(), BooksPanel.this,
+ Actions.openBook(lib, book.getMeta(), BooksPanel.this,
new Runnable() {
@Override
public void run() {
public void actionPerformed(ActionEvent e) {
final BookInfo book = informer.getUniqueSelected();
if (book != null) {
- Actions.openExternal(lib, book.getMeta(),
+ Actions.openBook(lib, book.getMeta(),
BookPopup.this.getParent(), new Runnable() {
@Override
public void run() {
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.viewer;
+
+import java.awt.Color;
+import java.awt.LayoutManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import be.nikiroo.fanfix.Instance;
+
+/**
+ * A Swing-based navigation bar, that displays first/previous/next/last page
+ * buttons.
+ *
+ * @author niki
+ */
+public class NavBar extends JPanel {
+ private static final long serialVersionUID = 1L;
+
+ private JLabel label;
+ private int index = 0;
+ private int min = 0;
+ private int max = 0;
+ private JButton[] navButtons;
+ String extraLabel = null;
+
+ private List<ActionListener> listeners = new ArrayList<ActionListener>();
+
+ /**
+ * Create a new navigation bar.
+ * <p>
+ * The minimum must be lower or equal to the maximum.
+ * <p>
+ * Note than a max of "-1" means "infinite".
+ *
+ * @param min
+ * the minimum page number (cannot be negative)
+ * @param max
+ * the maximum page number (cannot be lower than min, except if
+ * -1 (infinite))
+ *
+ * @throws IndexOutOfBoundsException
+ * if min > max and max is not "-1"
+ */
+ public NavBar(int min, int max) {
+ if (min > max && max != -1) {
+ throw new IndexOutOfBoundsException(String.format(
+ "min (%d) > max (%d)", min, max));
+ }
+
+ LayoutManager layout = new BoxLayout(this, BoxLayout.X_AXIS);
+ setLayout(layout);
+
+ // TODO:
+ // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
+ // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
+
+ navButtons = new JButton[4];
+
+ navButtons[0] = createNavButton("<<", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setIndex(NavBar.this.min);
+ fireEvent();
+ }
+ });
+ navButtons[1] = createNavButton(" < ", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setIndex(index - 1);
+ fireEvent();
+ }
+ });
+ navButtons[2] = createNavButton(" > ", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setIndex(index + 1);
+ fireEvent();
+ }
+ });
+ navButtons[3] = createNavButton(">>", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setIndex(NavBar.this.max);
+ fireEvent();
+ }
+ });
+
+ for (JButton navButton : navButtons) {
+ add(navButton);
+ }
+
+ label = new JLabel("");
+ add(label);
+
+ this.min = min;
+ this.max = max;
+ this.index = min;
+
+ updateEnabled();
+ updateLabel();
+ fireEvent();
+ }
+
+ /**
+ * The current index, must be between {@link NavBar#min} and
+ * {@link NavBar#max}, both inclusive.
+ *
+ * @return the index
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * The current index, must be between {@link NavBar#min} and
+ * {@link NavBar#max}, both inclusive.
+ *
+ * @param index
+ * the new index
+ */
+ public void setIndex(int index) {
+ if (index != this.index) {
+ if (index < min || (index > max && max != -1)) {
+ throw new IndexOutOfBoundsException(String.format(
+ "Index %d but min/max is [%d/%d]", index, min, max));
+ }
+
+ this.index = index;
+ updateLabel();
+ }
+
+ updateEnabled();
+ }
+
+ /**
+ * The minimun page number. Cannot be negative.
+ *
+ * @return the min
+ */
+ public int getMin() {
+ return min;
+ }
+
+ /**
+ * The minimum page number. Cannot be negative.
+ * <p>
+ * May update the index if needed (if the index is < the new min).
+ * <p>
+ * Will also (always) update the label and enable/disable the required
+ * buttons.
+ *
+ * @param min
+ * the new min
+ */
+ public void setMin(int min) {
+ this.min = min;
+ if (index < min) {
+ index = min;
+ }
+ updateEnabled();
+ updateLabel();
+
+ }
+
+ /**
+ * The maximum page number. Cannot be lower than min, except if -1
+ * (infinite).
+ *
+ * @return the max
+ */
+ public int getMax() {
+ return max;
+ }
+
+ /**
+ * The maximum page number. Cannot be lower than min, except if -1
+ * (infinite).
+ * <p>
+ * May update the index if needed (if the index is > the new max).
+ * <p>
+ * Will also (always) update the label and enable/disable the required
+ * buttons.
+ *
+ * @param max
+ * the new max
+ */
+ public void setMax(int max) {
+ this.max = max;
+ if (index > max && max != -1) {
+ index = max;
+ }
+ updateEnabled();
+ updateLabel();
+ }
+
+ /**
+ * The current extra label to display with the default
+ * {@link NavBar#computeLabel(int, int, int)} implementation.
+ *
+ * @return the current label
+ */
+ public String getExtraLabel() {
+ return extraLabel;
+ }
+
+ /**
+ * The current extra label to display with the default
+ * {@link NavBar#computeLabel(int, int, int)} implementation.
+ *
+ * @param currentLabel
+ * the new current label
+ */
+ public void setExtraLabel(String currentLabel) {
+ this.extraLabel = currentLabel;
+ updateLabel();
+ }
+
+ /**
+ * Add a listener that will be called on each page change.
+ *
+ * @param listener
+ * the new listener
+ */
+ public void addActionListener(ActionListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove the given listener if possible.
+ *
+ * @param listener
+ * the listener to remove
+ * @return TRUE if it was removed, FALSE if it was not found
+ */
+ public boolean removeActionListener(ActionListener listener) {
+ return listeners.remove(listener);
+ }
+
+ /**
+ * Remove all the listeners.
+ */
+ public void clearActionsListeners() {
+ listeners.clear();
+ }
+
+ /**
+ * Notify a change of page.
+ */
+ public void fireEvent() {
+ for (ActionListener listener : listeners) {
+ try {
+ listener.actionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_FIRST, "page changed"));
+ } catch (Exception e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ }
+ }
+ }
+
+ /**
+ * Create a single navigation button.
+ *
+ * @param text
+ * the text to display
+ * @param action
+ * the action to take on click
+ * @return the button
+ */
+ private JButton createNavButton(String text, ActionListener action) {
+ JButton navButton = new JButton(text);
+ navButton.addActionListener(action);
+ navButton.setForeground(Color.BLUE);
+ return navButton;
+ }
+
+ /**
+ * Update the label displayed in the UI.
+ */
+ private void updateLabel() {
+ label.setText(computeLabel(index, min, max));
+ }
+
+ /**
+ * Update the navigation buttons "enabled" state according to the current
+ * index value.
+ */
+ private void updateEnabled() {
+ navButtons[0].setEnabled(index > min);
+ navButtons[1].setEnabled(index > min);
+ navButtons[2].setEnabled(index < max || max == -1);
+ navButtons[3].setEnabled(index < max || max == -1);
+ }
+
+ /**
+ * Return the label to display for the given index.
+ * <p>
+ * Swing HTML (HTML3) is supported if surrounded by <HTML> and
+ * </HTML>.
+ * <p>
+ * By default, return "Page 1/5: current_label" (with the current index and
+ * {@link NavBar#getCurrentLabel()}).
+ *
+ * @param index
+ * the new index number
+ * @param mix
+ * the minimum index (inclusive)
+ * @param max
+ * the maximum index (inclusive)
+ * @return the label
+ */
+ protected String computeLabel(int index,
+ @SuppressWarnings("unused") int min, int max) {
+
+ String base = " <B>Page <SPAN COLOR='#444466'>%d</SPAN> ";
+ if (max >= 0) {
+ base += "/ %d";
+ }
+ base += "</B>";
+
+ String ifLabel = ": %s";
+
+ String display = base;
+ String label = getExtraLabel();
+ if (label != null && !label.trim().isEmpty()) {
+ display += ifLabel;
+ }
+
+ display = "<HTML>" + display + "</HTML>";
+
+ if (max >= 0) {
+ return String.format(display, index, max, label);
+ }
+
+ return String.format(display, index, label);
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.viewer;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.LayoutManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringIdGui;
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.library.BasicLibrary;
+import be.nikiroo.fanfix_swing.gui.PropertiesPanel;
+
+/**
+ * An internal, Swing-based {@link Story} viewer.
+ * <p>
+ * Works on both text and image document (see {@link MetaData#isImageDocument()}
+ * ).
+ *
+ * @author niki
+ */
+public class Viewer extends JFrame {
+ private static final long serialVersionUID = 1L;
+
+ private Story story;
+ private MetaData meta;
+ private JLabel title;
+ private PropertiesPanel descPane;
+ private ViewerPanel mainPanel;
+ private NavBar navbar;
+
+ /**
+ * Create a new {@link Story} viewer.
+ *
+ * @param lib
+ * the {@link BasicLibrary} to load the cover from
+ * @param story
+ * the {@link Story} to display
+ */
+ public Viewer(BasicLibrary lib, Story story) {
+ setTitle(Instance
+ .getInstance()
+ .getTransGui()
+ .getString(StringIdGui.TITLE_STORY, story.getMeta().getLuid(),
+ story.getMeta().getTitle()));
+
+ setSize(800, 600);
+
+ this.story = story;
+ this.meta = story.getMeta();
+
+ initGuiBase(lib);
+ initGuiNavButtons();
+
+ setChapter(-1);
+ }
+
+ /**
+ * Initialise the base panel with everything but the navigation buttons.
+ *
+ * @param lib
+ * the {@link BasicLibrary} to use to retrieve the cover image in
+ * the description panel
+ */
+ private void initGuiBase(BasicLibrary lib) {
+ setLayout(new BorderLayout());
+
+ title = new JLabel();
+ title.setFont(new Font(Font.SERIF, Font.BOLD,
+ title.getFont().getSize() * 3));
+ title.setText(meta.getTitle());
+ title.setHorizontalAlignment(SwingConstants.CENTER);
+ title.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ add(title, BorderLayout.NORTH);
+
+ JPanel contentPane = new JPanel(new BorderLayout());
+ add(contentPane, BorderLayout.CENTER);
+
+ descPane = new PropertiesPanel(lib, meta);
+ contentPane.add(descPane, BorderLayout.NORTH);
+
+ mainPanel = new ViewerPanel(story);
+ contentPane.add(mainPanel, BorderLayout.CENTER);
+ }
+
+ /**
+ * Create the 4 navigation buttons in {@link Viewer#navButtons} and
+ * initialise them.
+ */
+ private void initGuiNavButtons() {
+ navbar = new NavBar(-1, story.getChapters().size() - 1) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String computeLabel(int index, int min, int max) {
+ int chapter = index;
+ Chapter chap;
+ if (chapter < 0) {
+ chap = meta.getResume();
+ descPane.setVisible(true);
+ } else {
+ chap = story.getChapters().get(chapter);
+ descPane.setVisible(false);
+ }
+
+ String chapterDisplay = Instance
+ .getInstance()
+ .getTransGui()
+ .getString(StringIdGui.CHAPTER_HTML_UNNAMED,
+ chap.getNumber(), story.getChapters().size());
+ if (chap.getName() != null && !chap.getName().trim().isEmpty()) {
+ chapterDisplay = Instance
+ .getInstance()
+ .getTransGui()
+ .getString(StringIdGui.CHAPTER_HTML_NAMED,
+ chap.getNumber(),
+ story.getChapters().size(), chap.getName());
+ }
+
+ return "<HTML>" + chapterDisplay + "</HTML>";
+ }
+ };
+
+ navbar.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setChapter(navbar.getIndex());
+ }
+ });
+
+ JPanel navButtonsPane = new JPanel();
+ LayoutManager layout = new BoxLayout(navButtonsPane, BoxLayout.X_AXIS);
+ navButtonsPane.setLayout(layout);
+
+ add(navbar, BorderLayout.SOUTH);
+ }
+
+ /**
+ * Set the current chapter, 0-based.
+ * <p>
+ * Chapter -1 is reserved for the description page.
+ *
+ * @param chapter
+ * the chapter number to set
+ */
+ private void setChapter(int chapter) {
+ Chapter chap;
+ if (chapter < 0) {
+ chap = meta.getResume();
+ descPane.setVisible(true);
+ } else {
+ chap = story.getChapters().get(chapter);
+ descPane.setVisible(false);
+ }
+
+ mainPanel.setChapter(chap);
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.viewer;
+
+import java.awt.BorderLayout;
+import java.awt.EventQueue;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.SwingConstants;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringIdGui;
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.utils.Image;
+import be.nikiroo.utils.ui.ImageUtilsAwt;
+
+/**
+ * A {@link JPanel} that will show a {@link Story} chapter on screen.
+ *
+ * @author niki
+ */
+public class ViewerPanel extends JPanel {
+ private static final long serialVersionUID = 1L;
+
+ private boolean imageDocument;
+ private Chapter chap;
+ private JScrollPane scroll;
+ private ViewerTextOutput htmlOutput;
+
+ // text only:
+ private JEditorPane text;
+
+ // image only:
+ private JLabel image;
+ private JProgressBar imageProgress;
+ private int currentImage;
+ private JButton left;
+ private JButton right;
+
+ /**
+ * Create a new viewer.
+ *
+ * @param story
+ * the {@link Story} to work on
+ */
+ public ViewerPanel(Story story) {
+ this(story.getMeta(), story.getMeta().isImageDocument());
+ }
+
+ /**
+ * Create a new viewer.
+ *
+ * @param meta
+ * the {@link MetaData} of the story to show
+ * @param isImageDocument
+ * TRUE if it is an image document, FALSE if not
+ */
+ public ViewerPanel(MetaData meta, boolean isImageDocument) {
+ super(new BorderLayout());
+
+ this.imageDocument = isImageDocument;
+
+ this.text = new JEditorPane("text/html", "");
+ text.setEditable(false);
+ text.setAlignmentY(TOP_ALIGNMENT);
+ htmlOutput = new ViewerTextOutput();
+
+ image = new JLabel();
+ image.setHorizontalAlignment(SwingConstants.CENTER);
+
+ scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ scroll.getVerticalScrollBar().setUnitIncrement(16);
+
+ // TODO:
+ // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
+ // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
+
+ if (!imageDocument) {
+ add(scroll, BorderLayout.CENTER);
+ } else {
+ imageProgress = new JProgressBar();
+ imageProgress.setStringPainted(true);
+ add(imageProgress, BorderLayout.SOUTH);
+
+ JPanel main = new JPanel(new BorderLayout());
+ main.add(scroll, BorderLayout.CENTER);
+
+ left = new JButton("<HTML> < </HTML>");
+ left.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setImage(--currentImage);
+ }
+ });
+ main.add(left, BorderLayout.WEST);
+
+ right = new JButton("<HTML> > </HTML>");
+ right.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setImage(++currentImage);
+ }
+ });
+ main.add(right, BorderLayout.EAST);
+
+ add(main, BorderLayout.CENTER);
+ main.invalidate();
+ }
+
+ setChapter(meta.getResume());
+ }
+
+ /**
+ * Load the given chapter.
+ * <p>
+ * Will always be text for a non-image document.
+ * <p>
+ * Will be an image and left/right controls for an image-document, except
+ * for chapter 0 which will be text (chapter 0 = resume).
+ *
+ * @param chap
+ * the chapter to load
+ */
+ public void setChapter(Chapter chap) {
+ this.chap = chap;
+
+ if (!imageDocument) {
+ setText(chap);
+ } else {
+ left.setVisible(chap.getNumber() > 0);
+ right.setVisible(chap.getNumber() > 0);
+ imageProgress.setVisible(chap.getNumber() > 0);
+
+ imageProgress.setMinimum(0);
+ imageProgress.setMaximum(chap.getParagraphs().size() - 1);
+
+ if (chap.getNumber() == 0) {
+ setText(chap);
+ } else {
+ setImage(0);
+ }
+ }
+ }
+
+ /**
+ * Will set and display the current chapter text.
+ *
+ * @param chap
+ * the chapter to display
+ */
+ private void setText(final Chapter chap) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ final String content = htmlOutput.convert(chap);
+ // Wait until size computations are correct
+ while (!scroll.isValid()) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ setText(content);
+ }
+ }).start();
+ }
+
+ /**
+ * Actually set the text in the UI.
+ * <p>
+ * Do <b>NOT</b> use this method from the UI thread.
+ *
+ * @param content
+ * the text
+ */
+ private void setText(final String content) {
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ text.setText(content);
+ text.setCaretPosition(0);
+ scroll.setViewportView(text);
+ }
+ });
+ }
+
+ /**
+ * Will set and display the current image, take care about the progression
+ * and update the left and right cursors' <tt>enabled</tt> property.
+ *
+ * @param i
+ * the image index to load
+ */
+ private void setImage(int i) {
+ left.setEnabled(i > 0);
+ right.setEnabled(i + 1 < chap.getParagraphs().size());
+
+ if (i < 0 || i >= chap.getParagraphs().size()) {
+ return;
+ }
+
+ imageProgress.setValue(i);
+ imageProgress.setString(Instance
+ .getInstance()
+ .getTransGui()
+ .getString(StringIdGui.IMAGE_PROGRESSION, i + 1,
+ chap.getParagraphs().size()));
+
+ currentImage = i;
+
+ final Image img = chap.getParagraphs().get(i).getContentImage();
+
+ // prepare the viewport to get the right sizes later on
+ image.setIcon(null);
+ scroll.setViewportView(image);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Wait until size computations are correct
+ while (!scroll.isValid()) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ if (img == null) {
+ setText("Error: cannot render image.");
+ } else {
+ setImage(img);
+ }
+ }
+ }).start();
+ }
+
+ /**
+ * Actually set the image in the UI.
+ * <p>
+ * Do <b>NOT</b> use this method from the UI thread.
+ *
+ * @param img
+ * the image to set
+ */
+ private void setImage(Image img) {
+ try {
+ int scrollWidth = scroll.getWidth()
+ - scroll.getVerticalScrollBar().getWidth();
+
+ BufferedImage buffImg = ImageUtilsAwt.fromImage(img);
+
+ int iw = buffImg.getWidth();
+ int ih = buffImg.getHeight();
+ double ratio = ((double) ih) / iw;
+
+ int w = scrollWidth;
+ int h = (int) (ratio * scrollWidth);
+
+ BufferedImage resizedImage = new BufferedImage(w, h,
+ BufferedImage.TYPE_4BYTE_ABGR);
+
+ Graphics2D g = resizedImage.createGraphics();
+ try {
+ g.drawImage(buffImg, 0, 0, w, h, null);
+ } finally {
+ g.dispose();
+ }
+
+ final Icon icon = new ImageIcon(resizedImage);
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ image.setIcon(icon);
+ scroll.setViewportView(image);
+ }
+ });
+ } catch (Exception e) {
+ Instance.getInstance().getTraceHandler()
+ .error(new Exception("Failed to load image into label", e));
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ text.setText("Error: cannot load image.");
+ }
+ });
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.viewer;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.output.BasicOutput;
+
+/**
+ * This class can export a chapter into HTML3 code ready for Java Swing support.
+ *
+ * @author niki
+ */
+public class ViewerTextOutput {
+ private StringBuilder builder;
+ private BasicOutput output;
+ private Story fakeStory;
+
+ /**
+ * Create a new {@link ViewerTextOutput} that will convert a
+ * {@link Chapter} into HTML3 suited for Java Swing.
+ */
+ public ViewerTextOutput() {
+ builder = new StringBuilder();
+ fakeStory = new Story();
+
+ output = new BasicOutput() {
+ private boolean paraInQuote;
+
+ @Override
+ protected void writeChapterHeader(Chapter chap) throws IOException {
+ builder.append("<HTML>");
+
+ builder.append("<H1>");
+ builder.append("Chapter ");
+ builder.append(chap.getNumber());
+ builder.append(": ");
+ builder.append(chap.getName());
+ builder.append("</H1>");
+
+ builder.append("<DIV align='justify'>");
+ }
+
+ @Override
+ protected void writeChapterFooter(Chapter chap) throws IOException {
+ if (paraInQuote) {
+ builder.append("</DIV>");
+ }
+ paraInQuote = false;
+
+ builder.append("</DIV>");
+ builder.append("</HTML>");
+ }
+
+ @Override
+ protected void writeParagraph(Paragraph para) throws IOException {
+ if ((para.getType() == ParagraphType.QUOTE) == !paraInQuote) {
+ paraInQuote = !paraInQuote;
+ if (paraInQuote) {
+ builder.append("<BR>");
+ builder.append("<DIV>");
+ } else {
+ builder.append("</DIV>");
+ builder.append("<BR>");
+ }
+ }
+
+ switch (para.getType()) {
+ case NORMAL:
+ builder.append(" ");
+ builder.append(decorateText(para.getContent()));
+ builder.append("<BR>");
+ break;
+ case BLANK:
+ builder.append("<BR><BR>");
+ break;
+ case BREAK:
+ builder.append("<BR><P COLOR='#7777DD' ALIGN='CENTER'><B>");
+ builder.append("* * *");
+ builder.append("</B></P><BR><BR>");
+ break;
+ case QUOTE:
+ builder.append("<DIV>");
+ builder.append(" ");
+ builder.append("— ");
+ builder.append(decorateText(para.getContent()));
+ builder.append("</DIV>");
+
+ break;
+ case IMAGE:
+ }
+ }
+
+ @Override
+ protected String enbold(String word) {
+ return "<B COLOR='#7777DD'>" + word + "</B>";
+ }
+
+ @Override
+ protected String italize(String word) {
+ return "<I COLOR='GRAY'>" + word + "</I>";
+ }
+ };
+ }
+
+ /**
+ * Convert the chapter into HTML3 code.
+ *
+ * @param chap
+ * the {@link Chapter} to convert.
+ *
+ * @return HTML3 code tested with Java Swing
+ */
+ public String convert(Chapter chap) {
+ builder.setLength(0);
+ try {
+ fakeStory.setChapters(Arrays.asList(chap));
+ output.process(fakeStory, null, null);
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ }
+ return builder.toString();
+ }
+}