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