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