Fanfiction step2 + SearchableTags
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderViewerPanel.java
CommitLineData
32ba91e9
NR
1package be.nikiroo.fanfix.reader.ui;
2
3import java.awt.BorderLayout;
908bbb35
NR
4import java.awt.EventQueue;
5import java.awt.Graphics2D;
32ba91e9
NR
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
8import java.awt.image.BufferedImage;
9
10import javax.swing.Icon;
11import javax.swing.ImageIcon;
12import javax.swing.JButton;
908bbb35 13import javax.swing.JEditorPane;
32ba91e9
NR
14import javax.swing.JLabel;
15import javax.swing.JPanel;
16import javax.swing.JProgressBar;
17import javax.swing.JScrollPane;
908bbb35 18import javax.swing.SwingConstants;
32ba91e9
NR
19
20import be.nikiroo.fanfix.Instance;
5bc9573b 21import be.nikiroo.fanfix.bundles.StringIdGui;
32ba91e9
NR
22import be.nikiroo.fanfix.data.Chapter;
23import be.nikiroo.fanfix.data.Story;
24import be.nikiroo.utils.Image;
25import 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 */
32public 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>&nbsp; &nbsp; &lt; &nbsp; &nbsp;</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>&nbsp; &nbsp; &gt; &nbsp; &nbsp;</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}