1 package be
.nikiroo
.fanfix
.reader
.tui
;
3 import java
.awt
.Toolkit
;
4 import java
.awt
.datatransfer
.DataFlavor
;
5 import java
.io
.IOException
;
7 import java
.net
.UnknownHostException
;
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
;
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
;
32 * Manages the TUI general mode and links and manages the {@link TWindow}s.
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).
39 class TuiReaderApplication
extends TApplication
implements Reader
{
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;
48 public static final int MENU_OPT_FANFIX
= 1032;
49 public static final int MENU_OPT_TUI
= 1033;
52 public static final TCommand CMD_EXIT
= new TCommand(MENU_FILE_EXIT
) {
55 private Reader reader
;
56 private TuiReaderMainWindow main
;
58 // start reading if meta present
59 public TuiReaderApplication(Reader reader
, BackendType backend
)
64 if (getMeta() != null) {
69 public TuiReaderApplication(Reader reader
, String source
,
70 TApplication
.BackendType backend
) throws Exception
{
75 main
.setMode(Mode
.SOURCE
, source
);
79 public void read(boolean sync
) throws IOException
{
80 read(getStory(null), sync
);
84 public MetaData
getMeta() {
85 return reader
.getMeta();
89 public Story
getStory(Progress pg
) throws IOException
{
90 return reader
.getStory(pg
);
94 public BasicLibrary
getLibrary() {
95 return reader
.getLibrary();
99 public void setLibrary(BasicLibrary lib
) {
100 reader
.setLibrary(lib
);
104 public void setMeta(MetaData meta
) throws IOException
{
105 reader
.setMeta(meta
);
109 public void setMeta(String luid
) throws IOException
{
110 reader
.setMeta(luid
);
114 public void setMeta(URL source
, Progress pg
) throws IOException
{
115 reader
.setMeta(source
, pg
);
119 public void browse(String source
) {
121 reader
.browse(source
);
122 } catch (IOException e
) {
123 Instance
.getInstance().getTraceHandler().error(e
);
128 public int getChapter() {
129 return reader
.getChapter();
133 public void setChapter(int chapter
) {
134 reader
.setChapter(chapter
);
138 public void search(boolean sync
) throws IOException
{
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
);
149 public void searchTag(SupportType searchOn
, int page
, int item
,
150 boolean sync
, Integer
... tags
) throws IOException
{
151 reader
.searchTag(searchOn
, page
, item
, sync
, tags
);
155 * Open the given {@link Story} for reading. This may or may not start an
156 * external program to read said {@link Story}.
159 * the {@link Story} to read
161 * execute the process synchronously (wait until it is terminated
164 * @throws IOException
165 * in case of I/O errors
167 public void read(Story story
, boolean sync
) throws IOException
{
169 throw new IOException("No story to read");
172 // TODO: open in editor + external option
173 if (!story
.getMeta().isImageDocument()) {
174 TWindow window
= new TuiReaderStoryWindow(this, story
, getChapter());
178 openExternal(getLibrary(), story
.getMeta().getLuid(), sync
);
179 } catch (IOException e
) {
180 messageBox("Error when trying to open the story",
181 e
.getMessage(), TMessageBox
.Type
.OK
);
187 * Set the default status bar when this window appear.
189 * Some shortcuts are always visible, and will be put here.
191 * Note that shortcuts placed this way on menu won't work unless the menu
192 * also implement them.
195 * the new window or menu on screen
197 * the description to show on the status ba
199 public TStatusBar
setStatusBar(TWindow window
, String description
) {
200 TStatusBar statusBar
= window
.newStatusBar(description
);
201 statusBar
.addShortcutKeypress(TKeypress
.kbF10
, CMD_EXIT
, "Exit");
206 private void showMain() {
207 if (main
!= null && main
.isVisible()) {
213 main
= new TuiReaderMainWindow(this);
218 private void init(Reader reader
) {
219 this.reader
= reader
;
221 // TODO: traces/errors?
222 Instance
.getInstance().setTraceHandler(null);
224 // Add the menus TODO: i18n
225 TMenu fileMenu
= addMenu("&File");
226 fileMenu
.addItem(MENU_FILE_OPEN
, "&Open...");
227 fileMenu
.addItem(MENU_FILE_EXPORT
, "&Save as...");
228 fileMenu
.addItem(MENU_FILE_DELETE
, "&Delete...");
230 fileMenu
.addSeparator();
231 fileMenu
.addItem(MENU_FILE_IMPORT_URL
, "Import &URL...");
232 fileMenu
.addItem(MENU_FILE_IMPORT_FILE
, "Import &file...");
233 fileMenu
.addSeparator();
234 fileMenu
.addItem(MENU_FILE_LIBRARY
, "Lib&rary");
235 fileMenu
.addSeparator();
236 fileMenu
.addItem(MENU_FILE_EXIT
, "E&xit");
238 TMenu OptionsMenu
= addMenu("&Options");
239 OptionsMenu
.addItem(MENU_OPT_FANFIX
, "&Fanfix Configuration");
240 OptionsMenu
.addItem(MENU_OPT_TUI
, "&UI Configuration");
242 setStatusBar(fileMenu
, "File-management "
243 + "commands (Open, Save, Print, etc.)");
246 // TODO: Edit: re-download, delete
252 getBackend().setTitle("Fanfix");
256 protected boolean onCommand(TCommandEvent command
) {
257 if (command
.getCmd().equals(TuiReaderMainWindow
.CMD_SEARCH
)) {
258 messageBox("title", "caption");
261 return super.onCommand(command
);
265 protected boolean onMenu(TMenuEvent menu
) {
267 switch (menu
.getId()) {
272 String openfile
= null;
274 openfile
= fileOpenBox(".");
275 reader
.setMeta(BasicReader
.getUrl(openfile
), null);
277 } catch (IOException e
) {
279 error("Fail to open file"
280 + (openfile
== null ?
"" : ": " + openfile
),
285 case MENU_FILE_DELETE
:
288 MetaData meta
= null;
290 meta
= main
.getSelectedMeta();
293 luid
= meta
.getLuid();
294 story
= luid
+ ": " + meta
.getTitle();
298 TMessageBox mbox
= messageBox("Delete story", "Delete story \""
299 + story
+ "\"", Type
.OKCANCEL
);
300 if (mbox
.getResult() == Result
.OK
) {
302 reader
.getLibrary().delete(luid
);
304 main
.refreshStories();
306 } catch (IOException e
) {
308 error("Fail to delete the story: \"" + story
+ "\"",
314 case MENU_FILE_IMPORT_URL
:
315 String clipboard
= "";
317 clipboard
= ("" + Toolkit
.getDefaultToolkit()
318 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
320 } catch (Exception e
) {
321 // No data will be handled
324 if (clipboard
== null || !clipboard
.startsWith("http")) {
328 String url
= inputBox("Import story", "URL to import", clipboard
)
334 error("URK not supported: " + url
, "Import error");
336 } catch (IOException e
) {
338 error("Fail to import URL: " + url
, "Import error", e
);
342 case MENU_FILE_IMPORT_FILE
:
343 String filename
= null;
345 filename
= fileOpenBox(".");
346 if (!imprt(filename
)) {
348 error("File not supported: " + filename
, "Import error");
350 } catch (IOException e
) {
352 error("Fail to import file"
353 + (filename
== null ?
"" : ": " + filename
),
357 case MENU_FILE_LIBRARY
:
361 case MENU_OPT_FANFIX
:
362 new TuiReaderOptionWindow(this, false).maximize();
366 new TuiReaderOptionWindow(this, true).maximize();
371 return super.onMenu(menu
);
375 * Import the given url.
377 * Can fail if the host is not supported.
381 * @return TRUE in case of success, FALSE if the host is not supported
383 * @throws IOException
384 * in case of I/O error
386 private boolean imprt(String url
) throws IOException
{
388 reader
.getLibrary().imprt(BasicReader
.getUrl(url
), null);
389 main
.refreshStories();
391 } catch (UnknownHostException e
) {
397 public void openExternal(BasicLibrary lib
, String luid
, boolean sync
)
399 reader
.openExternal(lib
, luid
, sync
);
403 * Display an error message and log it.
408 * the title of the error message
410 private void error(String message
, String title
) {
411 error(message
, title
, null);
415 * Display an error message and log it, including the linked
421 * the title of the error message
423 * the exception to log if any (can be NULL)
425 private void error(String message
, String title
, Exception e
) {
426 Instance
.getInstance().getTraceHandler().error(title
+ ": " + message
);
428 Instance
.getInstance().getTraceHandler().error(e
);
432 messageBox(title
, message
//
433 + "\n" + e
.getMessage());
435 messageBox(title
, message
);
440 * Ask the user and, if confirmed, close the {@link TApplication} this
441 * {@link TWidget} is running on.
443 * This should result in the program terminating.
446 * the {@link TWidget}
448 static public void close(TWidget widget
) {
449 close(widget
.getApplication());
453 * Ask the user and, if confirmed, close the {@link TApplication}.
455 * This should result in the program terminating.
458 * the {@link TApplication}
460 static void close(TApplication app
) {
462 if (app
.messageBox("Confirmation", "(TODO: i18n) Exit application?",
463 TMessageBox
.Type
.YESNO
).getResult() == TMessageBox
.Result
.YES
) {