Commit | Line | Data |
---|---|---|
16a81ef7 | 1 | package be.nikiroo.fanfix.reader.tui; |
c1873e56 | 2 | |
e8796963 NR |
3 | import java.text.ParseException; |
4 | import java.text.SimpleDateFormat; | |
edfd3577 | 5 | import java.util.ArrayList; |
6a26aa76 | 6 | import java.util.Arrays; |
e8796963 | 7 | import java.util.Date; |
edfd3577 NR |
8 | import java.util.List; |
9 | ||
0861d62a | 10 | import jexer.TAction; |
0861d62a | 11 | import jexer.TButton; |
0861d62a | 12 | import jexer.TLabel; |
6a26aa76 | 13 | import jexer.TTable; |
f63c4267 | 14 | import jexer.TText; |
0861d62a | 15 | import jexer.TWindow; |
cf9c5ed1 | 16 | import jexer.event.TCommandEvent; |
c1873e56 | 17 | import jexer.event.TResizeEvent; |
4f66bfa8 | 18 | import jexer.event.TResizeEvent.Type; |
c1873e56 NR |
19 | import be.nikiroo.fanfix.data.Chapter; |
20 | import be.nikiroo.fanfix.data.MetaData; | |
21 | import be.nikiroo.fanfix.data.Paragraph; | |
9293188f | 22 | import be.nikiroo.fanfix.data.Paragraph.ParagraphType; |
c1873e56 | 23 | import be.nikiroo.fanfix.data.Story; |
e42573a0 | 24 | import be.nikiroo.fanfix.library.BasicLibrary; |
e8796963 | 25 | import be.nikiroo.utils.StringUtils; |
c1873e56 | 26 | |
6a26aa76 NR |
27 | /** |
28 | * This window will contain the {@link Story} in a readable format, with a | |
29 | * chapter browser. | |
30 | * | |
31 | * @author niki | |
32 | */ | |
5dd985cf | 33 | class TuiReaderStoryWindow extends TWindow { |
68e2c6d2 | 34 | private BasicLibrary lib; |
c1873e56 NR |
35 | private MetaData meta; |
36 | private Story story; | |
6a26aa76 | 37 | private TLabel titleField; |
f63c4267 | 38 | private TText textField; |
6a26aa76 NR |
39 | private TTable table; |
40 | private int chapter = -99; // invalid value | |
edfd3577 | 41 | private List<TButton> navigationButtons; |
5b00c122 | 42 | private TLabel currentChapter; |
c1873e56 | 43 | |
6322ab64 | 44 | // chapter: -1 for "none" (0 is desc) |
5b00c122 | 45 | public TuiReaderStoryWindow(TuiReaderApplication app, BasicLibrary lib, |
68e2c6d2 | 46 | MetaData meta, int chapter) { |
c1873e56 | 47 | super(app, desc(meta), 0, 0, 60, 18, CENTERED | RESIZABLE); |
68e2c6d2 | 48 | |
b0e88ebd | 49 | this.lib = lib; |
c1873e56 NR |
50 | this.meta = meta; |
51 | ||
5b00c122 | 52 | app.setStatusBar(this, desc(meta)); |
c1873e56 | 53 | |
6a26aa76 NR |
54 | // last = use window background |
55 | titleField = new TLabel(this, " Title", 0, 1, "tlabel", false); | |
e8796963 | 56 | textField = new TText(this, "", 1, 3, getWidth() - 4, getHeight() - 5); |
6a26aa76 NR |
57 | table = new TTable(this, 0, 3, getWidth(), getHeight() - 4, null, null, |
58 | Arrays.asList("Key", "Value"), true); | |
edfd3577 | 59 | |
e8796963 NR |
60 | titleField.setEnabled(false); |
61 | textField.getVerticalScroller().setX( | |
62 | textField.getVerticalScroller().getX() + 1); | |
63 | ||
0b7e3d78 | 64 | navigationButtons = new ArrayList<TButton>(5); |
edfd3577 | 65 | |
396e924c | 66 | // -3 because 0-based and 2 for borders |
edfd3577 NR |
67 | int row = getHeight() - 3; |
68 | ||
6a26aa76 NR |
69 | // for bg colour when << button is pressed |
70 | navigationButtons.add(addButton(" ", 0, row, null)); | |
0861d62a | 71 | navigationButtons.add(addButton("<< ", 0, row, new TAction() { |
211f7ddb | 72 | @Override |
396e924c | 73 | public void DO() { |
6a26aa76 | 74 | setChapter(-1); |
396e924c | 75 | } |
edfd3577 | 76 | })); |
0861d62a | 77 | navigationButtons.add(addButton("< ", 4, row, new TAction() { |
211f7ddb | 78 | @Override |
edfd3577 NR |
79 | public void DO() { |
80 | setChapter(TuiReaderStoryWindow.this.chapter - 1); | |
81 | } | |
82 | })); | |
0861d62a | 83 | navigationButtons.add(addButton("> ", 7, row, new TAction() { |
211f7ddb | 84 | @Override |
edfd3577 NR |
85 | public void DO() { |
86 | setChapter(TuiReaderStoryWindow.this.chapter + 1); | |
87 | } | |
88 | })); | |
0861d62a | 89 | navigationButtons.add(addButton(">> ", 10, row, new TAction() { |
211f7ddb | 90 | @Override |
edfd3577 NR |
91 | public void DO() { |
92 | setChapter(getStory().getChapters().size()); | |
93 | } | |
94 | })); | |
b0e88ebd | 95 | |
8670a283 NR |
96 | navigationButtons.get(0).setEnabled(false); |
97 | navigationButtons.get(1).setEnabled(false); | |
98 | navigationButtons.get(2).setEnabled(false); | |
edfd3577 | 99 | |
5b00c122 NR |
100 | currentChapter = addLabel("", 14, row); |
101 | currentChapter.setWidth(getWidth() - 10); | |
6a26aa76 | 102 | |
edfd3577 | 103 | setChapter(chapter); |
c1873e56 NR |
104 | } |
105 | ||
106 | @Override | |
107 | public void onResize(TResizeEvent resize) { | |
108 | super.onResize(resize); | |
109 | ||
e8796963 NR |
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, | |
6a26aa76 | 113 | resize.getHeight() - 5)); |
e8796963 NR |
114 | textField.getVerticalScroller().setX( |
115 | textField.getVerticalScroller().getX() + 1); | |
6a26aa76 NR |
116 | |
117 | table.setWidth(getWidth()); | |
118 | table.setHeight(getHeight() - 4); | |
119 | table.reflowData(); | |
edfd3577 NR |
120 | |
121 | // -3 because 0-based and 2 for borders | |
122 | int row = getHeight() - 3; | |
123 | ||
5b00c122 NR |
124 | String name = currentChapter.getLabel(); |
125 | while (name.length() < resize.getWidth() - currentChapter.getX()) { | |
edfd3577 NR |
126 | name += " "; |
127 | } | |
5b00c122 NR |
128 | currentChapter.setLabel(name); |
129 | currentChapter.setWidth(resize.getWidth() - 10); | |
130 | currentChapter.setY(row); | |
edfd3577 NR |
131 | |
132 | for (TButton button : navigationButtons) { | |
133 | button.setY(row); | |
134 | } | |
135 | } | |
136 | ||
5b00c122 NR |
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 | */ | |
edfd3577 | 145 | private void setChapter(int chapter) { |
edfd3577 NR |
146 | if (chapter > getStory().getChapters().size()) { |
147 | chapter = getStory().getChapters().size(); | |
148 | } | |
149 | ||
150 | if (chapter != this.chapter) { | |
151 | this.chapter = chapter; | |
b0e88ebd | 152 | |
8670a283 | 153 | int max = getStory().getChapters().size(); |
5b00c122 NR |
154 | navigationButtons.get(0).setEnabled(chapter > -1); |
155 | navigationButtons.get(1).setEnabled(chapter > -1); | |
156 | navigationButtons.get(2).setEnabled(chapter > -1); | |
8670a283 NR |
157 | navigationButtons.get(3).setEnabled(chapter < max); |
158 | navigationButtons.get(4).setEnabled(chapter < max); | |
b0e88ebd | 159 | |
5b00c122 | 160 | if (chapter < 0) { |
6a26aa76 | 161 | displayInfoPage(); |
edfd3577 | 162 | } else { |
6a26aa76 | 163 | displayChapterPage(); |
edfd3577 | 164 | } |
5b00c122 NR |
165 | } |
166 | ||
167 | setCurrentChapterText(); | |
168 | } | |
edfd3577 | 169 | |
5b00c122 NR |
170 | /** |
171 | * Append the info page about the current {@link Story}. | |
172 | * | |
173 | * @param builder | |
174 | * the builder to append to | |
175 | */ | |
6a26aa76 NR |
176 | private void displayInfoPage() { |
177 | textField.setVisible(false); | |
178 | table.setVisible(true); | |
e8796963 NR |
179 | textField.setEnabled(false); |
180 | table.setEnabled(true); | |
5b00c122 | 181 | |
6a26aa76 | 182 | MetaData meta = getStory().getMeta(); |
5b00c122 | 183 | |
6a26aa76 | 184 | setCurrentTitle(meta.getTitle()); |
5b00c122 | 185 | |
6a26aa76 | 186 | table.setRowData(new String[][] { // |
e8796963 NR |
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() } // | |
6a26aa76 NR |
191 | }); |
192 | table.setHeaders(Arrays.asList("key", "value"), false); | |
193 | table.toTop(); | |
5b00c122 NR |
194 | } |
195 | ||
e8796963 NR |
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 | ||
5b00c122 NR |
236 | /** |
237 | * Append the current chapter. | |
238 | * | |
239 | * @param builder | |
240 | * the builder to append to | |
241 | */ | |
6a26aa76 NR |
242 | private void displayChapterPage() { |
243 | table.setVisible(false); | |
244 | textField.setVisible(true); | |
e8796963 NR |
245 | table.setEnabled(false); |
246 | textField.setEnabled(true); | |
6a26aa76 NR |
247 | |
248 | StringBuilder builder = new StringBuilder(); | |
249 | ||
5b00c122 NR |
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(); | |
6a26aa76 | 259 | setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName)); |
5b00c122 NR |
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"); | |
fdc55375 | 269 | } |
edfd3577 | 270 | } |
edfd3577 | 271 | } |
6a26aa76 NR |
272 | |
273 | setText(builder.toString()); | |
c1873e56 NR |
274 | } |
275 | ||
276 | private Story getStory() { | |
277 | if (story == null) { | |
edfd3577 | 278 | // TODO: progress bar? |
b0e88ebd | 279 | story = lib.getStory(meta.getLuid(), null); |
c1873e56 NR |
280 | } |
281 | return story; | |
282 | } | |
283 | ||
5b00c122 NR |
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); | |
c1873e56 | 327 | } |
5b00c122 NR |
328 | |
329 | /** | |
6a26aa76 | 330 | * Set the current title in-window. |
5b00c122 | 331 | * |
5b00c122 | 332 | * @param title |
6a26aa76 | 333 | * the new title |
5b00c122 | 334 | */ |
6a26aa76 | 335 | private void setCurrentTitle(String title) { |
e8796963 NR |
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; | |
6a26aa76 NR |
343 | titleField.setWidth(title.length()); |
344 | titleField.setLabel(title); | |
345 | } | |
5b00c122 | 346 | |
6a26aa76 NR |
347 | private static String desc(MetaData meta) { |
348 | return String.format("%s: %s", meta.getLuid(), meta.getTitle()); | |
5b00c122 | 349 | } |
cf9c5ed1 NR |
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 | } | |
c1873e56 | 360 | } |