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