85b1abcfb44f7af7419b757f21865b3f4ed8c3d2
[fanfix.git] / src / be / nikiroo / fanfix / reader / tui / TuiReaderApplication.java
1 package be.nikiroo.fanfix.reader.tui;
2
3 import java.awt.Toolkit;
4 import java.awt.datatransfer.DataFlavor;
5 import java.io.IOException;
6 import java.net.URL;
7 import java.net.UnknownHostException;
8
9 import jexer.TApplication;
10 import jexer.TCommand;
11 import jexer.TKeypress;
12 import jexer.TMessageBox;
13 import jexer.TMessageBox.Result;
14 import jexer.TMessageBox.Type;
15 import jexer.TStatusBar;
16 import jexer.TWidget;
17 import jexer.TWindow;
18 import jexer.event.TCommandEvent;
19 import jexer.event.TMenuEvent;
20 import jexer.menu.TMenu;
21 import be.nikiroo.fanfix.Instance;
22 import be.nikiroo.fanfix.data.MetaData;
23 import be.nikiroo.fanfix.data.Story;
24 import be.nikiroo.fanfix.library.BasicLibrary;
25 import be.nikiroo.fanfix.reader.BasicReader;
26 import be.nikiroo.fanfix.reader.Reader;
27 import be.nikiroo.fanfix.reader.tui.TuiReaderMainWindow.Mode;
28 import be.nikiroo.fanfix.supported.SupportType;
29 import be.nikiroo.utils.Progress;
30
31 /**
32 * Manages the TUI general mode and links and manages the {@link TWindow}s.
33 * <p>
34 * It will also enclose a {@link Reader} and simply handle the reading part
35 * differently (it will create the required sub-windows and display them).
36 *
37 * @author niki
38 */
39 class TuiReaderApplication extends TApplication implements Reader {
40 public static final int MENU_OPEN = 1025;
41 public static final int MENU_IMPORT_URL = 1026;
42 public static final int MENU_IMPORT_FILE = 1027;
43 public static final int MENU_EXPORT = 1028;
44 public static final int MENU_DELETE = 1029;
45 public static final int MENU_LIBRARY = 1030;
46 public static final int MENU_EXIT = 1031;
47
48 public static final TCommand CMD_EXIT = new TCommand(MENU_EXIT) {
49 };
50
51 private Reader reader;
52 private TuiReaderMainWindow main;
53
54 // start reading if meta present
55 public TuiReaderApplication(Reader reader, BackendType backend)
56 throws Exception {
57 super(backend);
58 init(reader);
59
60 if (getMeta() != null) {
61 read(false);
62 }
63 }
64
65 public TuiReaderApplication(Reader reader, String source,
66 TApplication.BackendType backend) throws Exception {
67 super(backend);
68 init(reader);
69
70 showMain();
71 main.setMode(Mode.SOURCE, source);
72 }
73
74 @Override
75 public void read(boolean sync) throws IOException {
76 read(getStory(null), sync);
77 }
78
79 @Override
80 public MetaData getMeta() {
81 return reader.getMeta();
82 }
83
84 @Override
85 public Story getStory(Progress pg) {
86 return reader.getStory(pg);
87 }
88
89 @Override
90 public BasicLibrary getLibrary() {
91 return reader.getLibrary();
92 }
93
94 @Override
95 public void setLibrary(BasicLibrary lib) {
96 reader.setLibrary(lib);
97 }
98
99 @Override
100 public void setMeta(MetaData meta) throws IOException {
101 reader.setMeta(meta);
102 }
103
104 @Override
105 public void setMeta(String luid) throws IOException {
106 reader.setMeta(luid);
107 }
108
109 @Override
110 public void setMeta(URL source, Progress pg) throws IOException {
111 reader.setMeta(source, pg);
112 }
113
114 @Override
115 public void browse(String source) {
116 reader.browse(source);
117 }
118
119 @Override
120 public int getChapter() {
121 return reader.getChapter();
122 }
123
124 @Override
125 public void setChapter(int chapter) {
126 reader.setChapter(chapter);
127 }
128
129 @Override
130 public void search(SupportType searchOn, String keywords, int page, int item) throws IOException {
131 reader.search(searchOn, keywords, page, item);
132 }
133
134 @Override
135 public void searchTag(SupportType searchOn, int page, int item,
136 Integer... tags) throws IOException {
137 reader.searchTag(searchOn, page, item, tags);
138 }
139
140 /**
141 * Open the given {@link Story} for reading. This may or may not start an
142 * external program to read said {@link Story}.
143 *
144 * @param story
145 * the {@link Story} to read
146 * @param sync
147 * execute the process synchronously (wait until it is terminated
148 * before returning)
149 *
150 * @throws IOException
151 * in case of I/O errors
152 */
153 public void read(Story story, boolean sync) throws IOException {
154 if (story == null) {
155 throw new IOException("No story to read");
156 }
157
158 // TODO: open in editor + external option
159 if (!story.getMeta().isImageDocument()) {
160 TWindow window = new TuiReaderStoryWindow(this, story, getChapter());
161 window.maximize();
162 } else {
163 try {
164 openExternal(getLibrary(), story.getMeta().getLuid(), sync);
165 } catch (IOException e) {
166 messageBox("Error when trying to open the story",
167 e.getMessage(), TMessageBox.Type.OK);
168 }
169 }
170 }
171
172 /**
173 * Set the default status bar when this window appear.
174 * <p>
175 * Some shortcuts are always visible, and will be put here.
176 * <p>
177 * Note that shortcuts placed this way on menu won't work unless the menu
178 * also implement them.
179 *
180 * @param window
181 * the new window or menu on screen
182 * @param description
183 * the description to show on the status ba
184 */
185 public TStatusBar setStatusBar(TWindow window, String description) {
186 TStatusBar statusBar = window.newStatusBar(description);
187 statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit");
188 return statusBar;
189
190 }
191
192 private void showMain() {
193 if (main != null && main.isVisible()) {
194 main.activate();
195 } else {
196 if (main != null) {
197 main.close();
198 }
199 main = new TuiReaderMainWindow(this);
200 main.maximize();
201 }
202 }
203
204 private void init(Reader reader) {
205 this.reader = reader;
206
207 // TODO: traces/errors?
208 Instance.setTraceHandler(null);
209
210 // Add the menus TODO: i18n
211 TMenu fileMenu = addMenu("&File");
212 fileMenu.addItem(MENU_OPEN, "&Open...");
213 fileMenu.addItem(MENU_EXPORT, "&Save as...");
214 fileMenu.addItem(MENU_DELETE, "&Delete...");
215 // TODO: Move to...
216 fileMenu.addSeparator();
217 fileMenu.addItem(MENU_IMPORT_URL, "Import &URL...");
218 fileMenu.addItem(MENU_IMPORT_FILE, "Import &file...");
219 fileMenu.addSeparator();
220 fileMenu.addItem(MENU_LIBRARY, "Lib&rary");
221 fileMenu.addSeparator();
222 fileMenu.addItem(MENU_EXIT, "E&xit");
223
224 setStatusBar(fileMenu, "File-management "
225 + "commands (Open, Save, Print, etc.)");
226
227 // TODO: Edit: re-download, delete
228
229 //
230
231 addWindowMenu();
232
233 getBackend().setTitle("Fanfix");
234 }
235
236 @Override
237 protected boolean onCommand(TCommandEvent command) {
238 if (command.getCmd().equals(TuiReaderMainWindow.CMD_SEARCH)) {
239 messageBox("title", "caption");
240 return true;
241 }
242 return super.onCommand(command);
243 }
244
245 @Override
246 protected boolean onMenu(TMenuEvent menu) {
247 // TODO: i18n
248 switch (menu.getId()) {
249 case MENU_EXIT:
250 close(this);
251 return true;
252 case MENU_OPEN:
253 String openfile = null;
254 try {
255 openfile = fileOpenBox(".");
256 reader.setMeta(BasicReader.getUrl(openfile), null);
257 read(false);
258 } catch (IOException e) {
259 // TODO: i18n
260 error("Fail to open file"
261 + (openfile == null ? "" : ": " + openfile),
262 "Import error", e);
263 }
264
265 return true;
266 case MENU_DELETE:
267 String luid = null;
268 String story = null;
269 MetaData meta = null;
270 if (main != null) {
271 meta = main.getSelectedMeta();
272 }
273 if (meta != null) {
274 luid = meta.getLuid();
275 story = luid + ": " + meta.getTitle();
276 }
277
278 // TODO: i18n
279 TMessageBox mbox = messageBox("Delete story", "Delete story \""
280 + story + "\"", Type.OKCANCEL);
281 if (mbox.getResult() == Result.OK) {
282 try {
283 reader.getLibrary().delete(luid);
284 if (main != null) {
285 main.refreshStories();
286 }
287 } catch (IOException e) {
288 // TODO: i18n
289 error("Fail to delete the story: \"" + story + "\"",
290 "Error", e);
291 }
292 }
293
294 return true;
295 case MENU_IMPORT_URL:
296 String clipboard = "";
297 try {
298 clipboard = ("" + Toolkit.getDefaultToolkit()
299 .getSystemClipboard().getData(DataFlavor.stringFlavor))
300 .trim();
301 } catch (Exception e) {
302 // No data will be handled
303 }
304
305 if (clipboard == null || !clipboard.startsWith("http")) {
306 clipboard = "";
307 }
308
309 String url = inputBox("Import story", "URL to import", clipboard)
310 .getText();
311
312 try {
313 if (!imprt(url)) {
314 // TODO: i18n
315 error("URK not supported: " + url, "Import error");
316 }
317 } catch (IOException e) {
318 // TODO: i18n
319 error("Fail to import URL: " + url, "Import error", e);
320 }
321
322 return true;
323 case MENU_IMPORT_FILE:
324 String filename = null;
325 try {
326 filename = fileOpenBox(".");
327 if (!imprt(filename)) {
328 // TODO: i18n
329 error("File not supported: " + filename, "Import error");
330 }
331 } catch (IOException e) {
332 // TODO: i18n
333 error("Fail to import file"
334 + (filename == null ? "" : ": " + filename),
335 "Import error", e);
336 }
337 return true;
338 case MENU_LIBRARY:
339 showMain();
340 return true;
341 }
342
343 return super.onMenu(menu);
344 }
345
346 /**
347 * Import the given url.
348 * <p>
349 * Can fail if the host is not supported.
350 *
351 * @param url
352 *
353 * @return TRUE in case of success, FALSE if the host is not supported
354 *
355 * @throws IOException
356 * in case of I/O error
357 */
358 private boolean imprt(String url) throws IOException {
359 try {
360 reader.getLibrary().imprt(BasicReader.getUrl(url), null);
361 main.refreshStories();
362 return true;
363 } catch (UnknownHostException e) {
364 return false;
365 }
366 }
367
368 @Override
369 public void openExternal(BasicLibrary lib, String luid, boolean sync)
370 throws IOException {
371 reader.openExternal(lib, luid, sync);
372 }
373
374 /**
375 * Display an error message and log it.
376 *
377 * @param message
378 * the message
379 * @param title
380 * the title of the error message
381 */
382 private void error(String message, String title) {
383 error(message, title, null);
384 }
385
386 /**
387 * Display an error message and log it, including the linked
388 * {@link Exception}.
389 *
390 * @param message
391 * the message
392 * @param title
393 * the title of the error message
394 * @param e
395 * the exception to log if any (can be NULL)
396 */
397 private void error(String message, String title, Exception e) {
398 Instance.getTraceHandler().error(title + ": " + message);
399 if (e != null) {
400 Instance.getTraceHandler().error(e);
401 }
402
403 if (e != null) {
404 messageBox(title, message //
405 + "\n" + e.getMessage());
406 } else {
407 messageBox(title, message);
408 }
409 }
410
411 /**
412 * Ask the user and, if confirmed, close the {@link TApplication} this
413 * {@link TWidget} is running on.
414 * <p>
415 * This should result in the program terminating.
416 *
417 * @param widget
418 * the {@link TWidget}
419 */
420 static public void close(TWidget widget) {
421 close(widget.getApplication());
422 }
423
424 /**
425 * Ask the user and, if confirmed, close the {@link TApplication}.
426 * <p>
427 * This should result in the program terminating.
428 *
429 * @param app
430 * the {@link TApplication}
431 */
432 static void close(TApplication app) {
433 // TODO: i18n
434 if (app.messageBox("Confirmation", "(TODO: i18n) Exit application?",
435 TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
436 app.exit();
437 }
438 }
439 }