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