Move TSizeConstraint into new package
[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.TText;
14 import jexer.TWindow;
15 import jexer.event.TCommandEvent;
16 import jexer.event.TResizeEvent;
17 import be.nikiroo.fanfix.data.Chapter;
18 import be.nikiroo.fanfix.data.MetaData;
19 import be.nikiroo.fanfix.data.Paragraph;
20 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
21 import be.nikiroo.fanfix.data.Story;
22 import be.nikiroo.jexer.TSizeConstraint;
23 import be.nikiroo.jexer.TTable;
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 Story story;
34 private TLabel titleField;
35 private TText textField;
36 private TTable table;
37 private int chapter = -99; // invalid value
38 private List<TButton> navigationButtons;
39 private TLabel currentChapter;
40 private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
41
42 // chapter: -1 for "none" (0 is desc)
43 public TuiReaderStoryWindow(TuiReaderApplication app, Story story,
44 int chapter) {
45 super(app, desc(story.getMeta()), 0, 0, 60, 18, CENTERED | RESIZABLE);
46
47 this.story = story;
48
49 app.setStatusBar(this, desc(story.getMeta()));
50
51 // last = use window background
52 titleField = new TLabel(this, " Title", 0, 1, "tlabel", false);
53 textField = new TText(this, "", 0, 0, 1, 1);
54 table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key",
55 "Value"), true);
56
57 titleField.setEnabled(false);
58
59 navigationButtons = new ArrayList<TButton>(5);
60
61 navigationButtons.add(addButton("<<", 0, 0, new TAction() {
62 @Override
63 public void DO() {
64 setChapter(-1);
65 }
66 }));
67 navigationButtons.add(addButton("< ", 4, 0, new TAction() {
68 @Override
69 public void DO() {
70 setChapter(TuiReaderStoryWindow.this.chapter - 1);
71 }
72 }));
73 navigationButtons.add(addButton("> ", 7, 0, new TAction() {
74 @Override
75 public void DO() {
76 setChapter(TuiReaderStoryWindow.this.chapter + 1);
77 }
78 }));
79 navigationButtons.add(addButton(">>", 10, 0, new TAction() {
80 @Override
81 public void DO() {
82 setChapter(getStory().getChapters().size());
83 }
84 }));
85
86 navigationButtons.get(0).setEnabled(false);
87 navigationButtons.get(1).setEnabled(false);
88
89 currentChapter = addLabel("", 0, 0);
90
91 TSizeConstraint.setSize(sizeConstraints, textField, 1, 3, -1, -1);
92 TSizeConstraint.setSize(sizeConstraints, table, 0, 3, 0, -1);
93 TSizeConstraint.setSize(sizeConstraints, currentChapter, 14, -3, -1,
94 null);
95
96 for (TButton navigationButton : navigationButtons) {
97 navigationButton.setShadowColor(null);
98 // navigationButton.setEmptyBorders(false);
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 // Improve the disposition of the scrollbars
120 textField.getVerticalScroller().setX(textField.getWidth());
121 textField.getVerticalScroller().setHeight(textField.getHeight());
122 textField.getHorizontalScroller().setX(-1);
123 textField.getHorizontalScroller().setWidth(textField.getWidth() + 1);
124
125 setCurrentChapterText();
126 }
127
128 /**
129 * Display the current chapter in the window, or the {@link Story} info
130 * page.
131 *
132 * @param chapter
133 * the chapter (including "0" which is the description) or "-1"
134 * to display the info page instead
135 */
136 private void setChapter(int chapter) {
137 if (chapter > getStory().getChapters().size()) {
138 chapter = getStory().getChapters().size();
139 }
140
141 if (chapter != this.chapter) {
142 this.chapter = chapter;
143
144 int max = getStory().getChapters().size();
145 navigationButtons.get(0).setEnabled(chapter > -1);
146 navigationButtons.get(1).setEnabled(chapter > -1);
147 navigationButtons.get(2).setEnabled(chapter < max);
148 navigationButtons.get(3).setEnabled(chapter < max);
149
150 if (chapter < 0) {
151 displayInfoPage();
152 } else {
153 displayChapterPage();
154 }
155 }
156
157 setCurrentChapterText();
158 }
159
160 /**
161 * Append the info page about the current {@link Story}.
162 *
163 * @param builder
164 * the builder to append to
165 */
166 private void displayInfoPage() {
167 textField.setVisible(false);
168 table.setVisible(true);
169 textField.setEnabled(false);
170 table.setEnabled(true);
171
172 MetaData meta = getStory().getMeta();
173
174 setCurrentTitle(meta.getTitle());
175
176 StringBuilder tags = new StringBuilder();
177 for (String tag : meta.getTags()) {
178 if (tags.length() > 0) {
179 tags.append(", ");
180 }
181 tags.append(tag);
182 }
183
184 table.setRowData(new String[][] { //
185 new String[] { " Author", meta.getAuthor() }, //
186 new String[] { " Publication date", formatDate(meta.getDate()) },
187 new String[] { " Published on", meta.getPublisher() },
188 new String[] { " URL", meta.getUrl() },
189 new String[] { " Word count", format(meta.getWords()) },
190 new String[] { " Source", meta.getSource() },
191 new String[] { " Subject", meta.getSubject() },
192 new String[] { " Language", meta.getLang() },
193 new String[] { " Tags", tags.toString() } //
194 });
195 table.setHeaders(Arrays.asList("key", "value"), false);
196 table.toTop();
197 }
198
199 private String format(long value) {
200 String display = "";
201
202 while (value > 0) {
203 if (!display.isEmpty()) {
204 display = "." + display;
205 }
206 display = (value % 1000) + display;
207 value = value / 1000;
208 }
209
210 return display;
211 }
212
213 private String formatDate(String date) {
214 long ms = 0;
215
216 try {
217 ms = StringUtils.toTime(date);
218 } catch (ParseException e) {
219 }
220
221 if (ms <= 0) {
222 SimpleDateFormat sdf = new SimpleDateFormat(
223 "yyyy-MM-dd'T'HH:mm:ssXXX");
224 try {
225 ms = sdf.parse(date).getTime();
226 } catch (ParseException e) {
227 }
228 }
229
230 if (ms > 0) {
231 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
232 return sdf.format(new Date(ms));
233 }
234
235 // :(
236 return date;
237 }
238
239 /**
240 * Append the current chapter.
241 *
242 * @param builder
243 * the builder to append to
244 */
245 private void displayChapterPage() {
246 table.setVisible(false);
247 textField.setVisible(true);
248 table.setEnabled(false);
249 textField.setEnabled(true);
250
251 StringBuilder builder = new StringBuilder();
252
253 Chapter chap = null;
254 if (chapter == 0) {
255 chap = getStory().getMeta().getResume();
256 } else if (chapter > 0) {
257 chap = getStory().getChapters().get(chapter - 1);
258 }
259
260 // TODO: i18n
261 String chapName = chap == null ? "[No RESUME]" : chap.getName();
262 setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName));
263
264 if (chap != null) {
265 for (Paragraph para : chap) {
266 if (para.getType() == ParagraphType.BREAK) {
267 builder.append("\n");
268 }
269 builder.append(para.getContent()).append("\n");
270 if (para.getType() == ParagraphType.BREAK) {
271 builder.append("\n");
272 }
273 }
274 }
275
276 setText(builder.toString());
277 }
278
279 private Story getStory() {
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 name = String.format("%-" + width + "s", name);
318 if (name.length() > width) {
319 name = name.substring(0, width);
320 }
321
322 currentChapter.setLabel(name);
323 }
324
325 /**
326 * Set the current title in-window.
327 *
328 * @param title
329 * the new title
330 */
331 private void setCurrentTitle(String title) {
332 String pad = "";
333 if (title.length() < getWidth()) {
334 int padSize = (getWidth() - title.length()) / 2;
335 pad = String.format("%" + padSize + "s", "");
336 }
337
338 title = pad + title + pad;
339 titleField.setWidth(title.length());
340 titleField.setLabel(title);
341 }
342
343 private static String desc(MetaData meta) {
344 return String.format("%s: %s", meta.getLuid(), meta.getTitle());
345 }
346
347 @Override
348 public void onCommand(TCommandEvent command) {
349 if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
350 TuiReaderApplication.close(this);
351 } else {
352 // Handle our own event if needed here
353 super.onCommand(command);
354 }
355 }
356 }