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