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