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