Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderViewerPanel.java
CommitLineData
32ba91e9
NR
1package be.nikiroo.fanfix.reader.ui;
2
3import java.awt.BorderLayout;
908bbb35
NR
4import java.awt.EventQueue;
5import java.awt.Graphics2D;
32ba91e9
NR
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
8import java.awt.image.BufferedImage;
9
10import javax.swing.Icon;
11import javax.swing.ImageIcon;
12import javax.swing.JButton;
908bbb35 13import javax.swing.JEditorPane;
32ba91e9
NR
14import javax.swing.JLabel;
15import javax.swing.JPanel;
16import javax.swing.JProgressBar;
17import javax.swing.JScrollPane;
908bbb35 18import javax.swing.SwingConstants;
32ba91e9
NR
19
20import be.nikiroo.fanfix.Instance;
5bc9573b 21import be.nikiroo.fanfix.bundles.StringIdGui;
32ba91e9 22import be.nikiroo.fanfix.data.Chapter;
d16065ec 23import be.nikiroo.fanfix.data.MetaData;
32ba91e9
NR
24import be.nikiroo.fanfix.data.Story;
25import be.nikiroo.utils.Image;
26import be.nikiroo.utils.ui.ImageUtilsAwt;
27
28/**
29 * A {@link JPanel} that will show a {@link Story} chapter on screen.
30 *
31 * @author niki
32 */
33public class GuiReaderViewerPanel extends JPanel {
34 private static final long serialVersionUID = 1L;
35
36 private boolean imageDocument;
37 private Chapter chap;
908bbb35 38 private JScrollPane scroll;
32ba91e9 39 private GuiReaderViewerTextOutput htmlOutput;
908bbb35
NR
40
41 // text only:
42 private JEditorPane text;
43
44 // image only:
45 private JLabel image;
32ba91e9
NR
46 private JProgressBar imageProgress;
47 private int currentImage;
48 private JButton left;
49 private JButton right;
50
51 /**
52 * Create a new viewer.
53 *
54 * @param story
d16065ec 55 * the {@link Story} to work on
32ba91e9
NR
56 */
57 public GuiReaderViewerPanel(Story story) {
d16065ec
NR
58 this(story.getMeta(), story.getMeta().isImageDocument());
59 }
60
61 /**
62 * Create a new viewer.
63 *
64 * @param meta
65 * the {@link MetaData} of the story to show
66 * @param isImageDocument
67 * TRUE if it is an image document, FALSE if not
68 */
69 public GuiReaderViewerPanel(MetaData meta, boolean isImageDocument) {
32ba91e9
NR
70 super(new BorderLayout());
71
d16065ec 72 this.imageDocument = isImageDocument;
908bbb35
NR
73
74 this.text = new JEditorPane("text/html", "");
3530eefe 75 text.setEditable(false);
908bbb35 76 text.setAlignmentY(TOP_ALIGNMENT);
32ba91e9
NR
77 htmlOutput = new GuiReaderViewerTextOutput();
78
908bbb35
NR
79 image = new JLabel();
80 image.setHorizontalAlignment(SwingConstants.CENTER);
32ba91e9 81
908bbb35
NR
82 scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
83 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
84 scroll.getVerticalScrollBar().setUnitIncrement(16);
32ba91e9 85
f59eafef
NR
86 // TODO:
87 // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
88 // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
89
908bbb35 90 if (!imageDocument) {
32ba91e9
NR
91 add(scroll, BorderLayout.CENTER);
92 } else {
93 imageProgress = new JProgressBar();
94 imageProgress.setStringPainted(true);
95 add(imageProgress, BorderLayout.SOUTH);
96
97 JPanel main = new JPanel(new BorderLayout());
908bbb35 98 main.add(scroll, BorderLayout.CENTER);
32ba91e9 99
908bbb35 100 left = new JButton("<HTML>&nbsp; &nbsp; &lt; &nbsp; &nbsp;</HTML>");
32ba91e9
NR
101 left.addActionListener(new ActionListener() {
102 @Override
103 public void actionPerformed(ActionEvent e) {
104 setImage(--currentImage);
105 }
106 });
107 main.add(left, BorderLayout.WEST);
108
908bbb35 109 right = new JButton("<HTML>&nbsp; &nbsp; &gt; &nbsp; &nbsp;</HTML>");
32ba91e9
NR
110 right.addActionListener(new ActionListener() {
111 @Override
112 public void actionPerformed(ActionEvent e) {
113 setImage(++currentImage);
114 }
115 });
116 main.add(right, BorderLayout.EAST);
117
118 add(main, BorderLayout.CENTER);
908bbb35 119 main.invalidate();
32ba91e9
NR
120 }
121
d16065ec 122 setChapter(meta.getResume());
32ba91e9
NR
123 }
124
125 /**
126 * Load the given chapter.
127 * <p>
128 * Will always be text for a non-image document.
129 * <p>
130 * Will be an image and left/right controls for an image-document, except
131 * for chapter 0 which will be text (chapter 0 = resume).
132 *
133 * @param chap
134 * the chapter to load
135 */
136 public void setChapter(Chapter chap) {
137 this.chap = chap;
138
139 if (!imageDocument) {
908bbb35 140 setText(chap);
32ba91e9
NR
141 } else {
142 left.setVisible(chap.getNumber() > 0);
143 right.setVisible(chap.getNumber() > 0);
144 imageProgress.setVisible(chap.getNumber() > 0);
145
146 imageProgress.setMinimum(0);
147 imageProgress.setMaximum(chap.getParagraphs().size() - 1);
148
149 if (chap.getNumber() == 0) {
908bbb35 150 setText(chap);
32ba91e9
NR
151 } else {
152 setImage(0);
153 }
154 }
155 }
156
908bbb35
NR
157 /**
158 * Will set and display the current chapter text.
159 *
160 * @param chap
161 * the chapter to display
162 */
163 private void setText(final Chapter chap) {
164 new Thread(new Runnable() {
165 @Override
166 public void run() {
167 final String content = htmlOutput.convert(chap);
168 // Wait until size computations are correct
169 while (!scroll.isValid()) {
170 try {
171 Thread.sleep(1);
172 } catch (InterruptedException e) {
173 }
174 }
175
176 setText(content);
177 }
178 }).start();
179 }
180
181 /**
182 * Actually set the text in the UI.
183 * <p>
184 * Do <b>NOT</b> use this method from the UI thread.
185 *
186 * @param content
187 * the text
188 */
189 private void setText(final String content) {
190 EventQueue.invokeLater(new Runnable() {
191 @Override
192 public void run() {
193 text.setText(content);
194 text.setCaretPosition(0);
195 scroll.setViewportView(text);
196 }
197 });
198 }
199
32ba91e9
NR
200 /**
201 * Will set and display the current image, take care about the progression
202 * and update the left and right cursors' <tt>enabled</tt> property.
203 *
204 * @param i
205 * the image index to load
206 */
207 private void setImage(int i) {
208 left.setEnabled(i > 0);
209 right.setEnabled(i + 1 < chap.getParagraphs().size());
210
211 if (i < 0 || i >= chap.getParagraphs().size()) {
212 return;
213 }
214
215 imageProgress.setValue(i);
5bc9573b
NR
216 imageProgress.setString(GuiReader.trans(StringIdGui.IMAGE_PROGRESSION,
217 i + 1, chap.getParagraphs().size()));
32ba91e9
NR
218
219 currentImage = i;
32ba91e9 220
908bbb35
NR
221 final Image img = chap.getParagraphs().get(i).getContentImage();
222
eea1c0a8
NR
223 // prepare the viewport to get the right sizes later on
224 image.setIcon(null);
225 scroll.setViewportView(image);
226
908bbb35
NR
227 new Thread(new Runnable() {
228 @Override
229 public void run() {
230 // Wait until size computations are correct
231 while (!scroll.isValid()) {
232 try {
233 Thread.sleep(1);
234 } catch (InterruptedException e) {
235 }
236 }
237
238 if (img == null) {
239 setText("Error: cannot render image.");
240 } else {
241 setImage(img);
242 }
243 }
244 }).start();
245 }
246
247 /**
248 * Actually set the image in the UI.
249 * <p>
250 * Do <b>NOT</b> use this method from the UI thread.
251 *
252 * @param img
253 * the image to set
254 */
255 private void setImage(Image img) {
256 try {
257 int scrollWidth = scroll.getWidth()
258 - scroll.getVerticalScrollBar().getWidth();
259
260 BufferedImage buffImg = ImageUtilsAwt.fromImage(img);
261
262 int iw = buffImg.getWidth();
263 int ih = buffImg.getHeight();
264 double ratio = ((double) ih) / iw;
265
266 int w = scrollWidth;
267 int h = (int) (ratio * scrollWidth);
268
269 BufferedImage resizedImage = new BufferedImage(w, h,
270 BufferedImage.TYPE_4BYTE_ABGR);
271
272 Graphics2D g = resizedImage.createGraphics();
32ba91e9 273 try {
908bbb35
NR
274 g.drawImage(buffImg, 0, 0, w, h, null);
275 } finally {
276 g.dispose();
32ba91e9 277 }
908bbb35
NR
278
279 final Icon icon = new ImageIcon(resizedImage);
280 EventQueue.invokeLater(new Runnable() {
281 @Override
282 public void run() {
283 image.setIcon(icon);
284 scroll.setViewportView(image);
285 }
286 });
287 } catch (Exception e) {
d66deb8d 288 Instance.getInstance().getTraceHandler().error(
908bbb35
NR
289 new Exception("Failed to load image into label", e));
290 EventQueue.invokeLater(new Runnable() {
291 @Override
292 public void run() {
293 text.setText("Error: cannot load image.");
294 }
295 });
32ba91e9
NR
296 }
297 }
298}