6f7ed3cbddc61f003d286ce231c0b4d0daeb41d0
[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(boolean sync) throws IOException {
131 reader.search(sync);
132 }
133
134 @Override
135 public void search(SupportType searchOn, String keywords, int page,
136 int item, boolean sync) throws IOException {
137 reader.search(searchOn, keywords, page, item, sync);
138 }
139
140 @Override
141 public void searchTag(SupportType searchOn, int page, int item,
142 boolean sync, Integer... tags) throws IOException {
143 reader.searchTag(searchOn, page, item, sync, tags);
144 }
145
146 /**
147 * Open the given {@link Story} for reading. This may or may not start an
148 * external program to read said {@link Story}.
149 *
150 * @param story
151 * the {@link Story} to read
152 * @param sync
153 * execute the process synchronously (wait until it is terminated
154 * before returning)
155 *
156 * @throws IOException
157 * in case of I/O errors
158 */
159 public void read(Story story, boolean sync) throws IOException {
160 if (story == null) {
161 throw new IOException("No story to read");
162 }
163
164 // TODO: open in editor + external option
165 if (!story.getMeta().isImageDocument()) {
166 TWindow window = new TuiReaderStoryWindow(this, story, getChapter());
167 window.maximize();
168 } else {
169 try {
170 openExternal(getLibrary(), story.getMeta().getLuid(), sync);
171 } catch (IOException e) {
172 messageBox("Error when trying to open the story",
173 e.getMessage(), TMessageBox.Type.OK);
174 }
175 }
176 }
177
178 /**
179 * Set the default status bar when this window appear.
180 * <p>
181 * Some shortcuts are always visible, and will be put here.
182 * <p>
183 * Note that shortcuts placed this way on menu won't work unless the menu
184 * also implement them.
185 *
186 * @param window
187 * the new window or menu on screen
188 * @param description
189 * the description to show on the status ba
190 */
191 public TStatusBar setStatusBar(TWindow window, String description) {
192 TStatusBar statusBar = window.newStatusBar(description);
193 statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit");
194 return statusBar;
195
196 }
197
198 private void showMain() {
199 if (main != null && main.isVisible()) {
200 main.activate();
201 } else {
202 if (main != null) {
203 main.close();
204 }
205 main = new TuiReaderMainWindow(this);
206 main.maximize();
207 }
208 }
209
210 private void init(Reader reader) {
211 this.reader = reader;
212
213 // TODO: traces/errors?
214 Instance.setTraceHandler(null);
215
216 // Add the menus TODO: i18n
217 TMenu fileMenu = addMenu("&File");
218 fileMenu.addItem(MENU_OPEN, "&Open...");
219 fileMenu.addItem(MENU_EXPORT, "&Save as...");
220 fileMenu.addItem(MENU_DELETE, "&Delete...");
221 // TODO: Move to...
222 fileMenu.addSeparator();
223 fileMenu.addItem(MENU_IMPORT_URL, "Import &URL...");
224 fileMenu.addItem(MENU_IMPORT_FILE, "Import &file...");
225 fileMenu.addSeparator();
226 fileMenu.addItem(MENU_LIBRARY, "Lib&rary");
227 fileMenu.addSeparator();
228 fileMenu.addItem(MENU_EXIT, "E&xit");
229
230 setStatusBar(fileMenu, "File-management "
231 + "commands (Open, Save, Print, etc.)");
232
233 // TODO: Edit: re-download, delete
234
235 //
236
237 addWindowMenu();
238
239 getBackend().setTitle("Fanfix");
240 }
241
242 @Override
243 protected boolean onCommand(TCommandEvent command) {
244 if (command.getCmd().equals(TuiReaderMainWindow.CMD_SEARCH)) {
245 messageBox("title", "caption");
246 return true;
247 }
248 return super.onCommand(command);
249 }
250
251 @Override
252 protected boolean onMenu(TMenuEvent menu) {
253 // TODO: i18n
254 switch (menu.getId()) {
255 case MENU_EXIT:
256 close(this);
257 return true;
258 case MENU_OPEN:
259 String openfile = null;
260 try {
261 openfile = fileOpenBox(".");
262 reader.setMeta(BasicReader.getUrl(openfile), null);
263 read(false);
264 } catch (IOException e) {
265 // TODO: i18n
266 error("Fail to open file"
267 + (openfile == null ? "" : ": " + openfile),
268 "Import error", e);
269 }
270
271 return true;
272 case MENU_DELETE:
273 String luid = null;
274 String story = null;
275 MetaData meta = null;
276 if (main != null) {
277 meta = main.getSelectedMeta();
278 }
279 if (meta != null) {
280 luid = meta.getLuid();
281 story = luid + ": " + meta.getTitle();
282 }
283
284 // TODO: i18n
285 TMessageBox mbox = messageBox("Delete story", "Delete story \""
286 + story + "\"", Type.OKCANCEL);
287 if (mbox.getResult() == Result.OK) {
288 try {
289 reader.getLibrary().delete(luid);
290 if (main != null) {
291 main.refreshStories();
292 }
293 } catch (IOException e) {
294 // TODO: i18n
295 error("Fail to delete the story: \"" + story + "\"",
296 "Error", e);
297 }
298 }
299
300 return true;
301 case MENU_IMPORT_URL:
302 String clipboard = "";
303 try {
304 clipboard = ("" + Toolkit.getDefaultToolkit()
305 .getSystemClipboard().getData(DataFlavor.stringFlavor))
306 .trim();
307 } catch (Exception e) {
308 // No data will be handled
309 }
310
311 if (clipboard == null || !clipboard.startsWith("http")) {
312 clipboard = "";
313 }
314
315 String url = inputBox("Import story", "URL to import", clipboard)
316 .getText();
317
318 try {
319 if (!imprt(url)) {
320 // TODO: i18n
321 error("URK not supported: " + url, "Import error");
322 }
323 } catch (IOException e) {
324 // TODO: i18n
325 error("Fail to import URL: " + url, "Import error", e);
326 }
327
328 return true;
329 case MENU_IMPORT_FILE:
330 String filename = null;
331 try {
332 filename = fileOpenBox(".");
333 if (!imprt(filename)) {
334 // TODO: i18n
335 error("File not supported: " + filename, "Import error");
336 }
337 } catch (IOException e) {
338 // TODO: i18n
339 error("Fail to import file"
340 + (filename == null ? "" : ": " + filename),
341 "Import error", e);
342 }
343 return true;
344 case MENU_LIBRARY:
345 showMain();
346 return true;
347 }
348
349 return super.onMenu(menu);
350 }
351
352 /**
353 * Import the given url.
354 * <p>
355 * Can fail if the host is not supported.
356 *
357 * @param url
358 *
359 * @return TRUE in case of success, FALSE if the host is not supported
360 *
361 * @throws IOException
362 * in case of I/O error
363 */
364 private boolean imprt(String url) throws IOException {
365 try {
366 reader.getLibrary().imprt(BasicReader.getUrl(url), null);
367 main.refreshStories();
368 return true;
369 } catch (UnknownHostException e) {
370 return false;
371 }
372 }
373
374 @Override
375 public void openExternal(BasicLibrary lib, String luid, boolean sync)
376 throws IOException {
377 reader.openExternal(lib, luid, sync);
378 }
379
380 /**
381 * Display an error message and log it.
382 *
383 * @param message
384 * the message
385 * @param title
386 * the title of the error message
387 */
388 private void error(String message, String title) {
389 error(message, title, null);
390 }
391
392 /**
393 * Display an error message and log it, including the linked
394 * {@link Exception}.
395 *
396 * @param message
397 * the message
398 * @param title
399 * the title of the error message
400 * @param e
401 * the exception to log if any (can be NULL)
402 */
403 private void error(String message, String title, Exception e) {
404 Instance.getTraceHandler().error(title + ": " + message);
405 if (e != null) {
406 Instance.getTraceHandler().error(e);
407 }
408
409 if (e != null) {
410 messageBox(title, message //
411 + "\n" + e.getMessage());
412 } else {
413 messageBox(title, message);
414 }
415 }
416
417 /**
418 * Ask the user and, if confirmed, close the {@link TApplication} this
419 * {@link TWidget} is running on.
420 * <p>
421 * This should result in the program terminating.
422 *
423 * @param widget
424 * the {@link TWidget}
425 */
426 static public void close(TWidget widget) {
427 close(widget.getApplication());
428 }
429
430 /**
431 * Ask the user and, if confirmed, close the {@link TApplication}.
432 * <p>
433 * This should result in the program terminating.
434 *
435 * @param app
436 * the {@link TApplication}
437 */
438 static void close(TApplication app) {
439 // TODO: i18n
440 if (app.messageBox("Confirmation", "(TODO: i18n) Exit application?",
441 TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
442 app.exit();
443 }
444 }
445 }