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