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