Commit | Line | Data |
---|---|---|
16a81ef7 | 1 | package be.nikiroo.fanfix.reader.tui; |
c1873e56 | 2 | |
e2d017a3 NR |
3 | import java.awt.Toolkit; |
4 | import java.awt.datatransfer.DataFlavor; | |
c1873e56 | 5 | import java.io.IOException; |
6322ab64 | 6 | import java.net.URL; |
4f9478dc | 7 | import java.net.UnknownHostException; |
c1873e56 NR |
8 | |
9 | import jexer.TApplication; | |
e2d017a3 NR |
10 | import jexer.TCommand; |
11 | import jexer.TKeypress; | |
c1873e56 | 12 | import jexer.TMessageBox; |
e2d017a3 | 13 | import jexer.TStatusBar; |
cf9c5ed1 | 14 | import jexer.TWidget; |
6322ab64 | 15 | import jexer.TWindow; |
e2d017a3 NR |
16 | import jexer.event.TMenuEvent; |
17 | import jexer.menu.TMenu; | |
a8209dd0 | 18 | import be.nikiroo.fanfix.Instance; |
c1873e56 | 19 | import be.nikiroo.fanfix.data.MetaData; |
6322ab64 NR |
20 | import be.nikiroo.fanfix.data.Story; |
21 | import be.nikiroo.fanfix.library.BasicLibrary; | |
16a81ef7 NR |
22 | import be.nikiroo.fanfix.reader.BasicReader; |
23 | import be.nikiroo.fanfix.reader.Reader; | |
6322ab64 | 24 | import be.nikiroo.utils.Progress; |
c1873e56 | 25 | |
6322ab64 | 26 | /** |
bc2ea776 NR |
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). | |
6322ab64 NR |
31 | * |
32 | * @author niki | |
33 | */ | |
34 | class TuiReaderApplication extends TApplication implements Reader { | |
e2d017a3 NR |
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; | |
4f66bfa8 NR |
39 | public static final int MENU_LIBRARY = 1029; |
40 | public static final int MENU_EXIT = 1030; | |
e2d017a3 | 41 | |
cf9c5ed1 NR |
42 | public static final TCommand CMD_EXIT = new TCommand(MENU_EXIT) { |
43 | }; | |
44 | ||
6322ab64 | 45 | private Reader reader; |
e2d017a3 | 46 | private TuiReaderMainWindow main; |
c1873e56 | 47 | |
4f66bfa8 NR |
48 | private MetaData meta; |
49 | private String source; | |
50 | private boolean useMeta; | |
51 | ||
bc2ea776 NR |
52 | // start reading if meta present |
53 | public TuiReaderApplication(Reader reader, BackendType backend) | |
54 | throws Exception { | |
55 | super(backend); | |
56 | init(reader); | |
c1873e56 | 57 | |
5b00c122 | 58 | showMain(getMeta(), null, true); |
6322ab64 NR |
59 | } |
60 | ||
e2d017a3 | 61 | public TuiReaderApplication(Reader reader, String source, |
bc2ea776 | 62 | TApplication.BackendType backend) throws Exception { |
c1873e56 | 63 | super(backend); |
bc2ea776 | 64 | init(reader); |
c1873e56 | 65 | |
4f66bfa8 NR |
66 | showMain(null, source, false); |
67 | } | |
68 | ||
211f7ddb | 69 | @Override |
6322ab64 | 70 | public void read() throws IOException { |
bc2ea776 | 71 | MetaData meta = getMeta(); |
c1873e56 | 72 | |
bc2ea776 NR |
73 | if (meta == null) { |
74 | throw new IOException("No story to read"); | |
75 | } | |
6322ab64 | 76 | |
c1873e56 | 77 | // TODO: open in editor + external option |
b0e88ebd | 78 | if (!meta.isImageDocument()) { |
cf9c5ed1 | 79 | TWindow window = new TuiReaderStoryWindow(this, getLibrary(), meta, |
e2d017a3 | 80 | getChapter()); |
cf9c5ed1 | 81 | window.maximize(); |
b0e88ebd NR |
82 | } else { |
83 | try { | |
16a81ef7 | 84 | openExternal(getLibrary(), meta.getLuid()); |
b0e88ebd | 85 | } catch (IOException e) { |
c1873e56 | 86 | messageBox("Error when trying to open the story", |
b0e88ebd | 87 | e.getMessage(), TMessageBox.Type.OK); |
c1873e56 | 88 | } |
c1873e56 NR |
89 | } |
90 | } | |
6322ab64 | 91 | |
211f7ddb | 92 | @Override |
bc2ea776 NR |
93 | public MetaData getMeta() { |
94 | return reader.getMeta(); | |
95 | } | |
96 | ||
211f7ddb | 97 | @Override |
bc2ea776 NR |
98 | public Story getStory(Progress pg) { |
99 | return reader.getStory(pg); | |
6322ab64 NR |
100 | } |
101 | ||
211f7ddb | 102 | @Override |
6322ab64 NR |
103 | public BasicLibrary getLibrary() { |
104 | return reader.getLibrary(); | |
105 | } | |
106 | ||
211f7ddb | 107 | @Override |
bc2ea776 | 108 | public void setLibrary(BasicLibrary lib) { |
6322ab64 NR |
109 | reader.setLibrary(lib); |
110 | } | |
111 | ||
211f7ddb | 112 | @Override |
bc2ea776 NR |
113 | public void setMeta(MetaData meta) throws IOException { |
114 | reader.setMeta(meta); | |
115 | } | |
116 | ||
211f7ddb | 117 | @Override |
bc2ea776 NR |
118 | public void setMeta(String luid) throws IOException { |
119 | reader.setMeta(luid); | |
6322ab64 NR |
120 | } |
121 | ||
211f7ddb | 122 | @Override |
bc2ea776 NR |
123 | public void setMeta(URL source, Progress pg) throws IOException { |
124 | reader.setMeta(source, pg); | |
6322ab64 NR |
125 | } |
126 | ||
211f7ddb | 127 | @Override |
6322ab64 NR |
128 | public void browse(String source) { |
129 | reader.browse(source); | |
130 | } | |
bc2ea776 | 131 | |
211f7ddb | 132 | @Override |
bc2ea776 NR |
133 | public int getChapter() { |
134 | return reader.getChapter(); | |
135 | } | |
136 | ||
211f7ddb | 137 | @Override |
bc2ea776 NR |
138 | public void setChapter(int chapter) { |
139 | reader.setChapter(chapter); | |
140 | } | |
141 | ||
5b00c122 NR |
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); | |
cf9c5ed1 | 157 | statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit"); |
5b00c122 NR |
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 | } | |
11758a0f | 184 | main.maximize(); |
5b00c122 NR |
185 | } |
186 | } | |
187 | ||
bc2ea776 NR |
188 | private void init(Reader reader) { |
189 | this.reader = reader; | |
190 | ||
581d42c0 NR |
191 | // TODO: traces/errors? |
192 | Instance.setTraceHandler(null); | |
a8209dd0 | 193 | |
e2d017a3 NR |
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(); | |
4f66bfa8 NR |
203 | fileMenu.addItem(MENU_LIBRARY, "Lib&rary"); |
204 | fileMenu.addSeparator(); | |
e2d017a3 NR |
205 | fileMenu.addItem(MENU_EXIT, "E&xit"); |
206 | ||
5b00c122 | 207 | setStatusBar(fileMenu, "File-management " |
e2d017a3 | 208 | + "commands (Open, Save, Print, etc.)"); |
e2d017a3 NR |
209 | |
210 | // TODO: Edit: re-download, delete | |
211 | ||
212 | // | |
213 | ||
bc2ea776 | 214 | addWindowMenu(); |
bc2ea776 NR |
215 | |
216 | getBackend().setTitle("Fanfix"); | |
217 | } | |
e2d017a3 NR |
218 | |
219 | @Override | |
220 | protected boolean onMenu(TMenuEvent menu) { | |
221 | // TODO: i18n | |
222 | switch (menu.getId()) { | |
223 | case MENU_EXIT: | |
cf9c5ed1 | 224 | close(this); |
e2d017a3 NR |
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 | ||
4f9478dc NR |
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); | |
e2d017a3 NR |
251 | } |
252 | ||
253 | return true; | |
254 | case MENU_IMPORT_FILE: | |
4f9478dc | 255 | String filename = null; |
e2d017a3 | 256 | try { |
4f9478dc | 257 | filename = fileOpenBox("."); |
e2d017a3 | 258 | if (!imprt(filename)) { |
4f9478dc NR |
259 | // TODO: i18n + error |
260 | error("File not supported: " + filename, "Import error"); | |
e2d017a3 NR |
261 | } |
262 | } catch (IOException e) { | |
4f9478dc NR |
263 | // TODO: i18n + error |
264 | error("Fail to import file" | |
265 | + (filename == null ? "" : ": " + filename), | |
266 | "Import error", e); | |
e2d017a3 | 267 | } |
4f66bfa8 NR |
268 | return true; |
269 | case MENU_LIBRARY: | |
270 | try { | |
271 | showMain(meta, source, useMeta); | |
272 | } catch (IOException e) { | |
4f9478dc NR |
273 | // i18n |
274 | error("Cannot show the library", "ERROR", e); | |
4f66bfa8 NR |
275 | } |
276 | ||
e2d017a3 NR |
277 | return true; |
278 | } | |
279 | ||
280 | return super.onMenu(menu); | |
281 | } | |
282 | ||
4f9478dc NR |
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 { | |
e2d017a3 NR |
296 | try { |
297 | reader.getLibrary().imprt(BasicReader.getUrl(url), null); | |
298 | main.refreshStories(); | |
299 | return true; | |
4f9478dc | 300 | } catch (UnknownHostException e) { |
e2d017a3 NR |
301 | return false; |
302 | } | |
303 | } | |
16a81ef7 NR |
304 | |
305 | @Override | |
306 | public void openExternal(BasicLibrary lib, String luid) throws IOException { | |
307 | reader.openExternal(lib, luid); | |
308 | } | |
cf9c5ed1 | 309 | |
4f9478dc NR |
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 | ||
cf9c5ed1 NR |
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 | } | |
c1873e56 | 375 | } |