tui: use tsizeconstraint
[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.TCommandEvent;
17 import jexer.event.TResizeEvent;
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.utils.StringUtils;
24
25 /**
26 * This window will contain the {@link Story} in a readable format, with a
27 * chapter browser.
28 *
29 * @author niki
30 */
31 class TuiReaderStoryWindow extends TWindow {
32 private Story story;
33 private TLabel titleField;
34 private TText textField;
35 private TTable table;
36 private int chapter = -99; // invalid value
37 private List<TButton> navigationButtons;
38 private TLabel currentChapter;
39 private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
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, "", 0, 0, 1, 1);
53 table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key",
54 "Value"), true);
55
56 titleField.setEnabled(false);
57
58 navigationButtons = new ArrayList<TButton>(5);
59
60 // for bg colour when << button is pressed
61 navigationButtons.add(addButton(" ", 0, 0, null));
62 navigationButtons.add(addButton("<< ", 0, 0, new TAction() {
63 @Override
64 public void DO() {
65 setChapter(-1);
66 }
67 }));
68 navigationButtons.add(addButton("< ", 4, 0, new TAction() {
69 @Override
70 public void DO() {
71 setChapter(TuiReaderStoryWindow.this.chapter - 1);
72 }
73 }));
74 navigationButtons.add(addButton("> ", 7, 0, new TAction() {
75 @Override
76 public void DO() {
77 setChapter(TuiReaderStoryWindow.this.chapter + 1);
78 }
79 }));
80 navigationButtons.add(addButton(">> ", 10, 0, new TAction() {
81 @Override
82 public void DO() {
83 setChapter(getStory().getChapters().size());
84 }
85 }));
86
87 navigationButtons.get(0).setEnabled(false);
88 navigationButtons.get(1).setEnabled(false);
89 navigationButtons.get(2).setEnabled(false);
90
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);
104
105 setChapter(chapter);
106 }
107
108 @Override
109 public void onResize(TResizeEvent resize) {
110 if (resize != null) {
111 super.onResize(resize);
112 }
113
114 // TODO: find out why TText and TTable does not behave the same way
115 // (offset of 2 for height and width)
116
117 TSizeConstraint.resize(sizeConstraints);
118
119 textField.getVerticalScroller().setX(
120 textField.getVerticalScroller().getX() + 1);
121
122 String name = currentChapter.getLabel();
123 int size = Math.max(name.length(), currentChapter.getWidth());
124 name = String.format("%" + size + "s", name);
125
126 currentChapter.setLabel(name);
127 }
128
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 */
137 private void setChapter(int chapter) {
138 if (chapter > getStory().getChapters().size()) {
139 chapter = getStory().getChapters().size();
140 }
141
142 if (chapter != this.chapter) {
143 this.chapter = chapter;
144
145 int max = getStory().getChapters().size();
146 navigationButtons.get(0).setEnabled(chapter > -1);
147 navigationButtons.get(1).setEnabled(chapter > -1);
148 navigationButtons.get(2).setEnabled(chapter > -1);
149 navigationButtons.get(3).setEnabled(chapter < max);
150 navigationButtons.get(4).setEnabled(chapter < max);
151
152 if (chapter < 0) {
153 displayInfoPage();
154 } else {
155 displayChapterPage();
156 }
157 }
158
159 setCurrentChapterText();
160 }
161
162 /**
163 * Append the info page about the current {@link Story}.
164 *
165 * @param builder
166 * the builder to append to
167 */
168 private void displayInfoPage() {
169 textField.setVisible(false);
170 table.setVisible(true);
171 textField.setEnabled(false);
172 table.setEnabled(true);
173
174 MetaData meta = getStory().getMeta();
175
176 setCurrentTitle(meta.getTitle());
177
178 table.setRowData(new String[][] { //
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() } //
183 });
184 table.setHeaders(Arrays.asList("key", "value"), false);
185 table.toTop();
186 }
187
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
228 /**
229 * Append the current chapter.
230 *
231 * @param builder
232 * the builder to append to
233 */
234 private void displayChapterPage() {
235 table.setVisible(false);
236 textField.setVisible(true);
237 table.setEnabled(false);
238 textField.setEnabled(true);
239
240 StringBuilder builder = new StringBuilder();
241
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();
251 setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName));
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");
261 }
262 }
263 }
264
265 setText(builder.toString());
266 }
267
268 private Story getStory() {
269 return story;
270 }
271
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);
315 }
316
317 /**
318 * Set the current title in-window.
319 *
320 * @param title
321 * the new title
322 */
323 private void setCurrentTitle(String title) {
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;
331 titleField.setWidth(title.length());
332 titleField.setLabel(title);
333 }
334
335 private static String desc(MetaData meta) {
336 return String.format("%s: %s", meta.getLuid(), meta.getTitle());
337 }
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 }
348 }