Commit | Line | Data |
---|---|---|
32ba91e9 NR |
1 | package be.nikiroo.fanfix.reader.ui; |
2 | ||
3 | import java.awt.BorderLayout; | |
908bbb35 NR |
4 | import java.awt.EventQueue; |
5 | import java.awt.Graphics2D; | |
32ba91e9 NR |
6 | import java.awt.event.ActionEvent; |
7 | import java.awt.event.ActionListener; | |
8 | import java.awt.image.BufferedImage; | |
9 | ||
10 | import javax.swing.Icon; | |
11 | import javax.swing.ImageIcon; | |
12 | import javax.swing.JButton; | |
908bbb35 | 13 | import javax.swing.JEditorPane; |
32ba91e9 NR |
14 | import javax.swing.JLabel; |
15 | import javax.swing.JPanel; | |
16 | import javax.swing.JProgressBar; | |
17 | import javax.swing.JScrollPane; | |
908bbb35 | 18 | import javax.swing.SwingConstants; |
32ba91e9 NR |
19 | |
20 | import be.nikiroo.fanfix.Instance; | |
5bc9573b | 21 | import be.nikiroo.fanfix.bundles.StringIdGui; |
32ba91e9 | 22 | import be.nikiroo.fanfix.data.Chapter; |
d16065ec | 23 | import be.nikiroo.fanfix.data.MetaData; |
32ba91e9 NR |
24 | import be.nikiroo.fanfix.data.Story; |
25 | import be.nikiroo.utils.Image; | |
26 | import 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 | */ | |
33 | public 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 | |
908bbb35 | 86 | if (!imageDocument) { |
32ba91e9 NR |
87 | add(scroll, BorderLayout.CENTER); |
88 | } else { | |
89 | imageProgress = new JProgressBar(); | |
90 | imageProgress.setStringPainted(true); | |
91 | add(imageProgress, BorderLayout.SOUTH); | |
92 | ||
93 | JPanel main = new JPanel(new BorderLayout()); | |
908bbb35 | 94 | main.add(scroll, BorderLayout.CENTER); |
32ba91e9 | 95 | |
908bbb35 | 96 | left = new JButton("<HTML> < </HTML>"); |
32ba91e9 NR |
97 | left.addActionListener(new ActionListener() { |
98 | @Override | |
99 | public void actionPerformed(ActionEvent e) { | |
100 | setImage(--currentImage); | |
101 | } | |
102 | }); | |
103 | main.add(left, BorderLayout.WEST); | |
104 | ||
908bbb35 | 105 | right = new JButton("<HTML> > </HTML>"); |
32ba91e9 NR |
106 | right.addActionListener(new ActionListener() { |
107 | @Override | |
108 | public void actionPerformed(ActionEvent e) { | |
109 | setImage(++currentImage); | |
110 | } | |
111 | }); | |
112 | main.add(right, BorderLayout.EAST); | |
113 | ||
114 | add(main, BorderLayout.CENTER); | |
908bbb35 | 115 | main.invalidate(); |
32ba91e9 NR |
116 | } |
117 | ||
d16065ec | 118 | setChapter(meta.getResume()); |
32ba91e9 NR |
119 | } |
120 | ||
121 | /** | |
122 | * Load the given chapter. | |
123 | * <p> | |
124 | * Will always be text for a non-image document. | |
125 | * <p> | |
126 | * Will be an image and left/right controls for an image-document, except | |
127 | * for chapter 0 which will be text (chapter 0 = resume). | |
128 | * | |
129 | * @param chap | |
130 | * the chapter to load | |
131 | */ | |
132 | public void setChapter(Chapter chap) { | |
133 | this.chap = chap; | |
134 | ||
135 | if (!imageDocument) { | |
908bbb35 | 136 | setText(chap); |
32ba91e9 NR |
137 | } else { |
138 | left.setVisible(chap.getNumber() > 0); | |
139 | right.setVisible(chap.getNumber() > 0); | |
140 | imageProgress.setVisible(chap.getNumber() > 0); | |
141 | ||
142 | imageProgress.setMinimum(0); | |
143 | imageProgress.setMaximum(chap.getParagraphs().size() - 1); | |
144 | ||
145 | if (chap.getNumber() == 0) { | |
908bbb35 | 146 | setText(chap); |
32ba91e9 NR |
147 | } else { |
148 | setImage(0); | |
149 | } | |
150 | } | |
151 | } | |
152 | ||
908bbb35 NR |
153 | /** |
154 | * Will set and display the current chapter text. | |
155 | * | |
156 | * @param chap | |
157 | * the chapter to display | |
158 | */ | |
159 | private void setText(final Chapter chap) { | |
160 | new Thread(new Runnable() { | |
161 | @Override | |
162 | public void run() { | |
163 | final String content = htmlOutput.convert(chap); | |
164 | // Wait until size computations are correct | |
165 | while (!scroll.isValid()) { | |
166 | try { | |
167 | Thread.sleep(1); | |
168 | } catch (InterruptedException e) { | |
169 | } | |
170 | } | |
171 | ||
172 | setText(content); | |
173 | } | |
174 | }).start(); | |
175 | } | |
176 | ||
177 | /** | |
178 | * Actually set the text in the UI. | |
179 | * <p> | |
180 | * Do <b>NOT</b> use this method from the UI thread. | |
181 | * | |
182 | * @param content | |
183 | * the text | |
184 | */ | |
185 | private void setText(final String content) { | |
186 | EventQueue.invokeLater(new Runnable() { | |
187 | @Override | |
188 | public void run() { | |
189 | text.setText(content); | |
190 | text.setCaretPosition(0); | |
191 | scroll.setViewportView(text); | |
192 | } | |
193 | }); | |
194 | } | |
195 | ||
32ba91e9 NR |
196 | /** |
197 | * Will set and display the current image, take care about the progression | |
198 | * and update the left and right cursors' <tt>enabled</tt> property. | |
199 | * | |
200 | * @param i | |
201 | * the image index to load | |
202 | */ | |
203 | private void setImage(int i) { | |
204 | left.setEnabled(i > 0); | |
205 | right.setEnabled(i + 1 < chap.getParagraphs().size()); | |
206 | ||
207 | if (i < 0 || i >= chap.getParagraphs().size()) { | |
208 | return; | |
209 | } | |
210 | ||
211 | imageProgress.setValue(i); | |
5bc9573b NR |
212 | imageProgress.setString(GuiReader.trans(StringIdGui.IMAGE_PROGRESSION, |
213 | i + 1, chap.getParagraphs().size())); | |
32ba91e9 NR |
214 | |
215 | currentImage = i; | |
32ba91e9 | 216 | |
908bbb35 NR |
217 | final Image img = chap.getParagraphs().get(i).getContentImage(); |
218 | ||
eea1c0a8 NR |
219 | // prepare the viewport to get the right sizes later on |
220 | image.setIcon(null); | |
221 | scroll.setViewportView(image); | |
222 | ||
908bbb35 NR |
223 | new Thread(new Runnable() { |
224 | @Override | |
225 | public void run() { | |
226 | // Wait until size computations are correct | |
227 | while (!scroll.isValid()) { | |
228 | try { | |
229 | Thread.sleep(1); | |
230 | } catch (InterruptedException e) { | |
231 | } | |
232 | } | |
233 | ||
234 | if (img == null) { | |
235 | setText("Error: cannot render image."); | |
236 | } else { | |
237 | setImage(img); | |
238 | } | |
239 | } | |
240 | }).start(); | |
241 | } | |
242 | ||
243 | /** | |
244 | * Actually set the image in the UI. | |
245 | * <p> | |
246 | * Do <b>NOT</b> use this method from the UI thread. | |
247 | * | |
248 | * @param img | |
249 | * the image to set | |
250 | */ | |
251 | private void setImage(Image img) { | |
252 | try { | |
253 | int scrollWidth = scroll.getWidth() | |
254 | - scroll.getVerticalScrollBar().getWidth(); | |
255 | ||
256 | BufferedImage buffImg = ImageUtilsAwt.fromImage(img); | |
257 | ||
258 | int iw = buffImg.getWidth(); | |
259 | int ih = buffImg.getHeight(); | |
260 | double ratio = ((double) ih) / iw; | |
261 | ||
262 | int w = scrollWidth; | |
263 | int h = (int) (ratio * scrollWidth); | |
264 | ||
265 | BufferedImage resizedImage = new BufferedImage(w, h, | |
266 | BufferedImage.TYPE_4BYTE_ABGR); | |
267 | ||
268 | Graphics2D g = resizedImage.createGraphics(); | |
32ba91e9 | 269 | try { |
908bbb35 NR |
270 | g.drawImage(buffImg, 0, 0, w, h, null); |
271 | } finally { | |
272 | g.dispose(); | |
32ba91e9 | 273 | } |
908bbb35 NR |
274 | |
275 | final Icon icon = new ImageIcon(resizedImage); | |
276 | EventQueue.invokeLater(new Runnable() { | |
277 | @Override | |
278 | public void run() { | |
279 | image.setIcon(icon); | |
280 | scroll.setViewportView(image); | |
281 | } | |
282 | }); | |
283 | } catch (Exception e) { | |
284 | Instance.getTraceHandler().error( | |
285 | new Exception("Failed to load image into label", e)); | |
286 | EventQueue.invokeLater(new Runnable() { | |
287 | @Override | |
288 | public void run() { | |
289 | text.setText("Error: cannot load image."); | |
290 | } | |
291 | }); | |
32ba91e9 NR |
292 | } |
293 | } | |
294 | } |