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