gui: fix internal viewer editable
[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
20 import be.nikiroo.fanfix.Instance;
21 import be.nikiroo.fanfix.bundles.StringIdGui;
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;
37 private JScrollPane scroll;
38 private GuiReaderViewerTextOutput htmlOutput;
39
40 // text only:
41 private JEditorPane text;
42
43 // image only:
44 private JLabel image;
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();
60
61 this.text = new JEditorPane("text/html", "");
62 text.setEditable(false);
63 text.setAlignmentY(TOP_ALIGNMENT);
64 htmlOutput = new GuiReaderViewerTextOutput();
65
66 image = new JLabel();
67 image.setHorizontalAlignment(SwingConstants.CENTER);
68
69 scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
70 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
71 scroll.getVerticalScrollBar().setUnitIncrement(16);
72
73 if (!imageDocument) {
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());
81 main.add(scroll, BorderLayout.CENTER);
82
83 left = new JButton("<HTML>&nbsp; &nbsp; &lt; &nbsp; &nbsp;</HTML>");
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
92 right = new JButton("<HTML>&nbsp; &nbsp; &gt; &nbsp; &nbsp;</HTML>");
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);
102 main.invalidate();
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) {
123 setText(chap);
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) {
133 setText(chap);
134 } else {
135 setImage(0);
136 }
137 }
138 }
139
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
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);
199 imageProgress.setString(GuiReader.trans(StringIdGui.IMAGE_PROGRESSION,
200 i + 1, chap.getParagraphs().size()));
201
202 currentImage = i;
203
204 final Image img = chap.getParagraphs().get(i).getContentImage();
205
206 // prepare the viewport to get the right sizes later on
207 image.setIcon(null);
208 scroll.setViewportView(image);
209
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();
256 try {
257 g.drawImage(buffImg, 0, 0, w, h, null);
258 } finally {
259 g.dispose();
260 }
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 });
279 }
280 }
281 }