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