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