79840b9d20071b418c16b8022558d708de3c5699
[fanfix.git] / src / be / nikiroo / fanfix / reader / tui / TuiReaderStoryWindow.java
1 package be.nikiroo.fanfix.reader.tui;
2
3 import java.text.ParseException;
4 import java.text.SimpleDateFormat;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Date;
8 import java.util.List;
9
10 import jexer.TAction;
11 import jexer.TButton;
12 import jexer.TLabel;
13 import jexer.TText;
14 import jexer.TWindow;
15 import jexer.event.TCommandEvent;
16 import jexer.event.TResizeEvent;
17 import be.nikiroo.fanfix.data.Chapter;
18 import be.nikiroo.fanfix.data.MetaData;
19 import be.nikiroo.fanfix.data.Paragraph;
20 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
21 import be.nikiroo.fanfix.data.Story;
22 import be.nikiroo.jexer.TTable;
23 import be.nikiroo.utils.StringUtils;
24
25 /**
26 * This window will contain the {@link Story} in a readable format, with a
27 * chapter browser.
28 *
29 * @author niki
30 */
31 class TuiReaderStoryWindow extends TWindow {
32 private Story story;
33 private TLabel titleField;
34 private TText textField;
35 private TTable table;
36 private int chapter = -99; // invalid value
37 private List<TButton> navigationButtons;
38 private TLabel currentChapter;
39 private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
40
41 // chapter: -1 for "none" (0 is desc)
42 public TuiReaderStoryWindow(TuiReaderApplication app, Story story,
43 int chapter) {
44 super(app, desc(story.getMeta()), 0, 0, 60, 18, CENTERED | RESIZABLE);
45
46 this.story = story;
47
48 app.setStatusBar(this, desc(story.getMeta()));
49
50 // last = use window background
51 titleField = new TLabel(this, " Title", 0, 1, "tlabel", false);
52 textField = new TText(this, "", 0, 0, 1, 1);
53 table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key",
54 "Value"), true);
55
56 titleField.setEnabled(false);
57
58 navigationButtons = new ArrayList<TButton>(5);
59
60 navigationButtons.add(addButton("<<", 0, 0, new TAction() {
61 @Override
62 public void DO() {
63 setChapter(-1);
64 }
65 }));
66 navigationButtons.add(addButton("< ", 4, 0, new TAction() {
67 @Override
68 public void DO() {
69 setChapter(TuiReaderStoryWindow.this.chapter - 1);
70 }
71 }));
72 navigationButtons.add(addButton("> ", 7, 0, new TAction() {
73 @Override
74 public void DO() {
75 setChapter(TuiReaderStoryWindow.this.chapter + 1);
76 }
77 }));
78 navigationButtons.add(addButton(">>", 10, 0, new TAction() {
79 @Override
80 public void DO() {
81 setChapter(getStory().getChapters().size());
82 }
83 }));
84
85 navigationButtons.get(0).setEnabled(false);
86 navigationButtons.get(1).setEnabled(false);
87
88 currentChapter = addLabel("", 0, 0);
89
90 TSizeConstraint.setSize(sizeConstraints, textField, 1, 3, -1, -1);
91 TSizeConstraint.setSize(sizeConstraints, table, 0, 3, 0, -1);
92 TSizeConstraint.setSize(sizeConstraints, currentChapter, 14, -3, -1,
93 null);
94
95 for (TButton navigationButton : navigationButtons) {
96 navigationButton.setShadowColor(null);
97 // navigationButton.setEmptyBorders(false);
98 TSizeConstraint.setSize(sizeConstraints, navigationButton, null,
99 -3, null, null);
100 }
101
102 onResize(null);
103
104 setChapter(chapter);
105 }
106
107 @Override
108 public void onResize(TResizeEvent resize) {
109 if (resize != null) {
110 super.onResize(resize);
111 }
112
113 // TODO: find out why TText and TTable does not behave the same way
114 // (offset of 2 for height and width)
115
116 TSizeConstraint.resize(sizeConstraints);
117
118 // Improve the disposition of the scrollbars
119 textField.getVerticalScroller().setX(textField.getWidth());
120 textField.getVerticalScroller().setHeight(textField.getHeight());
121 textField.getHorizontalScroller().setX(-1);
122 textField.getHorizontalScroller().setWidth(textField.getWidth() + 1);
123
124 setCurrentChapterText();
125 }
126
127 /**
128 * Display the current chapter in the window, or the {@link Story} info
129 * page.
130 *
131 * @param chapter
132 * the chapter (including "0" which is the description) or "-1"
133 * to display the info page instead
134 */
135 private void setChapter(int chapter) {
136 if (chapter > getStory().getChapters().size()) {
137 chapter = getStory().getChapters().size();
138 }
139
140 if (chapter != this.chapter) {
141 this.chapter = chapter;
142
143 int max = getStory().getChapters().size();
144 navigationButtons.get(0).setEnabled(chapter > -1);
145 navigationButtons.get(1).setEnabled(chapter > -1);
146 navigationButtons.get(2).setEnabled(chapter < max);
147 navigationButtons.get(3).setEnabled(chapter < max);
148
149 if (chapter < 0) {
150 displayInfoPage();
151 } else {
152 displayChapterPage();
153 }
154 }
155
156 setCurrentChapterText();
157 }
158
159 /**
160 * Append the info page about the current {@link Story}.
161 *
162 * @param builder
163 * the builder to append to
164 */
165 private void displayInfoPage() {
166 textField.setVisible(false);
167 table.setVisible(true);
168 textField.setEnabled(false);
169 table.setEnabled(true);
170
171 MetaData meta = getStory().getMeta();
172
173 setCurrentTitle(meta.getTitle());
174
175 StringBuilder tags = new StringBuilder();
176 for (String tag : meta.getTags()) {
177 if (tags.length() > 0) {
178 tags.append(", ");
179 }
180 tags.append(tag);
181 }
182
183 table.setRowData(new String[][] { //
184 new String[] { " Author", meta.getAuthor() }, //
185 new String[] { " Publication date", formatDate(meta.getDate()) },
186 new String[] { " Published on", meta.getPublisher() },
187 new String[] { " URL", meta.getUrl() },
188 new String[] { " Word count", format(meta.getWords()) },
189 new String[] { " Source", meta.getSource() },
190 new String[] { " Subject", meta.getSubject() },
191 new String[] { " Language", meta.getLang() },
192 new String[] { " Tags", tags.toString() } //
193 });
194 table.setHeaders(Arrays.asList("key", "value"), false);
195 table.toTop();
196 }
197
198 private String format(long value) {
199 String display = "";
200
201 while (value > 0) {
202 if (!display.isEmpty()) {
203 display = "." + display;
204 }
205 display = (value % 1000) + display;
206 value = value / 1000;
207 }
208
209 return display;
210 }
211
212 private String formatDate(String date) {
213 long ms = 0;
214
215 try {
216 ms = StringUtils.toTime(date);
217 } catch (ParseException e) {
218 }
219
220 if (ms <= 0) {
221 SimpleDateFormat sdf = new SimpleDateFormat(
222 "yyyy-MM-dd'T'HH:mm:ssXXX");
223 try {
224 ms = sdf.parse(date).getTime();
225 } catch (ParseException e) {
226 }
227 }
228
229 if (ms > 0) {
230 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
231 return sdf.format(new Date(ms));
232 }
233
234 // :(
235 return date;
236 }
237
238 /**
239 * Append the current chapter.
240 *
241 * @param builder
242 * the builder to append to
243 */
244 private void displayChapterPage() {
245 table.setVisible(false);
246 textField.setVisible(true);
247 table.setEnabled(false);
248 textField.setEnabled(true);
249
250 StringBuilder builder = new StringBuilder();
251
252 Chapter chap = null;
253 if (chapter == 0) {
254 chap = getStory().getMeta().getResume();
255 } else if (chapter > 0) {
256 chap = getStory().getChapters().get(chapter - 1);
257 }
258
259 // TODO: i18n
260 String chapName = chap == null ? "[No RESUME]" : chap.getName();
261 setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName));
262
263 if (chap != null) {
264 for (Paragraph para : chap) {
265 if (para.getType() == ParagraphType.BREAK) {
266 builder.append("\n");
267 }
268 builder.append(para.getContent()).append("\n");
269 if (para.getType() == ParagraphType.BREAK) {
270 builder.append("\n");
271 }
272 }
273 }
274
275 setText(builder.toString());
276 }
277
278 private Story getStory() {
279 return story;
280 }
281
282 /**
283 * Display the given text on the window.
284 *
285 * @param text
286 * the text to display
287 */
288 private void setText(String text) {
289 textField.setText(text);
290 textField.reflowData();
291 textField.toTop();
292 }
293
294 /**
295 * Set the current chapter area to the correct value.
296 */
297 private void setCurrentChapterText() {
298 String name;
299 if (chapter < 0) {
300 name = " " + getStory().getMeta().getTitle();
301 } else if (chapter == 0) {
302 Chapter resume = getStory().getMeta().getResume();
303 if (resume != null) {
304 name = String.format(" %s", resume.getName());
305 } else {
306 // TODO: i18n
307 name = "[No RESUME]";
308 }
309 } else {
310 int max = getStory().getChapters().size();
311 Chapter chap = getStory().getChapters().get(chapter - 1);
312 name = String.format(" %d/%d: %s", chapter, max, chap.getName());
313 }
314
315 int width = getWidth() - currentChapter.getX();
316 name = String.format("%-" + width + "s", name);
317 if (name.length() > width) {
318 name = name.substring(0, width);
319 }
320
321 currentChapter.setLabel(name);
322 }
323
324 /**
325 * Set the current title in-window.
326 *
327 * @param title
328 * the new title
329 */
330 private void setCurrentTitle(String title) {
331 String pad = "";
332 if (title.length() < getWidth()) {
333 int padSize = (getWidth() - title.length()) / 2;
334 pad = String.format("%" + padSize + "s", "");
335 }
336
337 title = pad + title + pad;
338 titleField.setWidth(title.length());
339 titleField.setLabel(title);
340 }
341
342 private static String desc(MetaData meta) {
343 return String.format("%s: %s", meta.getLuid(), meta.getTitle());
344 }
345
346 @Override
347 public void onCommand(TCommandEvent command) {
348 if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
349 TuiReaderApplication.close(this);
350 } else {
351 // Handle our own event if needed here
352 super.onCommand(command);
353 }
354 }
355 }