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