TUI: code cleanup + show story info
[fanfix.git] / src / be / nikiroo / fanfix / reader / tui / TuiReaderStoryWindow.java
1 package be.nikiroo.fanfix.reader.tui;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import jexer.TAction;
7 import jexer.TButton;
8 import jexer.TLabel;
9 import jexer.TText;
10 import jexer.TWindow;
11 import jexer.event.TResizeEvent;
12 import jexer.event.TResizeEvent.Type;
13 import be.nikiroo.fanfix.data.Chapter;
14 import be.nikiroo.fanfix.data.MetaData;
15 import be.nikiroo.fanfix.data.Paragraph;
16 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
17 import be.nikiroo.fanfix.data.Story;
18 import be.nikiroo.fanfix.library.BasicLibrary;
19
20 class TuiReaderStoryWindow extends TWindow {
21 private BasicLibrary lib;
22 private MetaData meta;
23 private Story story;
24 private TText textField;
25 private int chapter = -1;
26 private List<TButton> navigationButtons;
27 private TLabel currentChapter;
28
29 // chapter: -1 for "none" (0 is desc)
30 public TuiReaderStoryWindow(TuiReaderApplication app, BasicLibrary lib,
31 MetaData meta, int chapter) {
32 super(app, desc(meta), 0, 0, 60, 18, CENTERED | RESIZABLE);
33
34 this.lib = lib;
35 this.meta = meta;
36
37 app.setStatusBar(this, desc(meta));
38
39 textField = new TText(this, "", 0, 0, getWidth() - 2, getHeight() - 2);
40
41 navigationButtons = new ArrayList<TButton>(5);
42
43 // -3 because 0-based and 2 for borders
44 int row = getHeight() - 3;
45
46 navigationButtons.add(addButton(" ", 0, row, null)); // for bg colour
47 // when <<
48 // button is
49 // pressed
50 navigationButtons.add(addButton("<< ", 0, row, new TAction() {
51 @Override
52 public void DO() {
53 setChapter(0);
54 }
55 }));
56 navigationButtons.add(addButton("< ", 4, row, new TAction() {
57 @Override
58 public void DO() {
59 setChapter(TuiReaderStoryWindow.this.chapter - 1);
60 }
61 }));
62 navigationButtons.add(addButton("> ", 7, row, new TAction() {
63 @Override
64 public void DO() {
65 setChapter(TuiReaderStoryWindow.this.chapter + 1);
66 }
67 }));
68 navigationButtons.add(addButton(">> ", 10, row, new TAction() {
69 @Override
70 public void DO() {
71 setChapter(getStory().getChapters().size());
72 }
73 }));
74
75 navigationButtons.get(0).setEnabled(false);
76 navigationButtons.get(1).setEnabled(false);
77 navigationButtons.get(2).setEnabled(false);
78
79 currentChapter = addLabel("", 14, row);
80 currentChapter.setWidth(getWidth() - 10);
81 setChapter(chapter);
82 }
83
84 @Override
85 public void onResize(TResizeEvent resize) {
86 super.onResize(resize);
87
88 // Resize the text field TODO: why setW/setH/reflow not enough for the
89 // scrollbars?
90 textField.onResize(new TResizeEvent(Type.WIDGET, resize.getWidth() - 2,
91 resize.getHeight() - 2));
92
93 // -3 because 0-based and 2 for borders
94 int row = getHeight() - 3;
95
96 String name = currentChapter.getLabel();
97 while (name.length() < resize.getWidth() - currentChapter.getX()) {
98 name += " ";
99 }
100 currentChapter.setLabel(name);
101 currentChapter.setWidth(resize.getWidth() - 10);
102 currentChapter.setY(row);
103
104 for (TButton button : navigationButtons) {
105 button.setY(row);
106 }
107 }
108
109 /**
110 * Display the current chapter in the window, or the {@link Story} info
111 * page.
112 *
113 * @param chapter
114 * the chapter (including "0" which is the description) or "-1"
115 * to display the info page instead
116 */
117 private void setChapter(int chapter) {
118 if (chapter > getStory().getChapters().size()) {
119 chapter = getStory().getChapters().size();
120 }
121
122 if (chapter != this.chapter) {
123 this.chapter = chapter;
124
125 int max = getStory().getChapters().size();
126 navigationButtons.get(0).setEnabled(chapter > -1);
127 navigationButtons.get(1).setEnabled(chapter > -1);
128 navigationButtons.get(2).setEnabled(chapter > -1);
129 navigationButtons.get(3).setEnabled(chapter < max);
130 navigationButtons.get(4).setEnabled(chapter < max);
131
132 StringBuilder builder = new StringBuilder();
133
134 if (chapter < 0) {
135 appendInfoPage(builder);
136 } else {
137 appendChapterPage(builder);
138 }
139
140 setText(builder.toString());
141 }
142
143 setCurrentChapterText();
144 }
145
146 /**
147 * Append the info page about the current {@link Story}.
148 *
149 * @param builder
150 * the builder to append to
151 */
152 private StringBuilder appendInfoPage(StringBuilder builder) {
153 MetaData meta = getStory().getMeta();
154
155 // TODO: use a ttable?
156
157 appendTitle(builder, meta.getTitle(), 1).append("\n");
158
159 // i18n
160 builder.append("Author: ").append(meta.getAuthor()).append("\n");
161 builder.append("Publication date: ").append(meta.getDate())
162 .append("\n");
163 builder.append("Word count: ").append(meta.getWords()).append("\n");
164
165 return builder;
166 }
167
168 /**
169 * Append the current chapter.
170 *
171 * @param builder
172 * the builder to append to
173 */
174 private void appendChapterPage(StringBuilder builder) {
175 Chapter chap = null;
176 if (chapter == 0) {
177 chap = getStory().getMeta().getResume();
178 } else if (chapter > 0) {
179 chap = getStory().getChapters().get(chapter - 1);
180 }
181
182 // TODO: i18n
183 String chapName = chap == null ? "[No RESUME]" : chap.getName();
184
185 appendTitle(builder,
186 String.format("Chapter %d: %s", chapter, chapName), 1);
187 builder.append("\n");
188
189 if (chap != null) {
190 for (Paragraph para : chap) {
191 if (para.getType() == ParagraphType.BREAK) {
192 builder.append("\n");
193 }
194 builder.append(para.getContent()).append("\n");
195 if (para.getType() == ParagraphType.BREAK) {
196 builder.append("\n");
197 }
198 }
199 }
200 }
201
202 private Story getStory() {
203 if (story == null) {
204 // TODO: progress bar?
205 story = lib.getStory(meta.getLuid(), null);
206 }
207 return story;
208 }
209
210 /**
211 * Display the given text on the window.
212 *
213 * @param text
214 * the text to display
215 */
216 private void setText(String text) {
217 textField.setText(text);
218 textField.reflowData();
219 textField.toTop();
220 }
221
222 /**
223 * Set the current chapter area to the correct value.
224 */
225 private void setCurrentChapterText() {
226 String name;
227 if (chapter < 0) {
228 name = " " + getStory().getMeta().getTitle();
229 } else if (chapter == 0) {
230 Chapter resume = getStory().getMeta().getResume();
231 if (resume != null) {
232 name = String.format(" %s", resume.getName());
233 } else {
234 // TODO: i18n
235 name = "[No RESUME]";
236 }
237 } else {
238 int max = getStory().getChapters().size();
239 Chapter chap = getStory().getChapters().get(chapter - 1);
240 name = String.format(" %d/%d: %s", chapter, max, chap.getName());
241 }
242
243 int width = getWidth() - currentChapter.getX();
244 while (name.length() < width) {
245 name += " ";
246 }
247
248 if (name.length() > width) {
249 name = name.substring(0, width);
250 }
251
252 currentChapter.setLabel(name);
253
254 }
255
256 private static String desc(MetaData meta) {
257 return String.format("%s: %s", meta.getLuid(), meta.getTitle());
258 }
259
260 /**
261 * Append a title (on its own 2 lines).
262 *
263 * @param builder
264 * the {@link StringBuilder} to append to
265 * @param title
266 * the title itself
267 * @param level
268 * the title level, 1 being the highest level, 2 second level and
269 * so on
270 */
271 private static StringBuilder appendTitle(StringBuilder builder,
272 String title, int level) {
273 String hr;
274 switch (level) {
275 case 1:
276 hr = "======";
277 break;
278 case 2:
279 hr = "=-=-=-";
280 break;
281 case 3:
282 hr = "______";
283 break;
284 case 4:
285 hr = "------";
286 break;
287 default:
288 hr = "";
289 break;
290 }
291
292 int fullPad = title.length() / hr.length();
293 int rest = title.length() - (fullPad * hr.length());
294
295 builder.append(title).append("\n");
296 for (int i = 0; i < title.length() / hr.length(); i++) {
297 builder.append(hr);
298 }
299
300 if (rest > 0) {
301 builder.append(hr.substring(0, rest));
302 }
303
304 builder.append("\n");
305
306 return builder;
307 }
308 }