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