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