From d7367179b645781b4e0e4fc24893cb1e6725c14c Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 5 May 2020 18:21:24 +0200 Subject: [PATCH] remove reader ui/tui --- configure.sh | 21 +- src/be/nikiroo/fanfix/Instance.java | 16 +- src/be/nikiroo/fanfix/Main.java | 168 ++- src/be/nikiroo/fanfix/bundles/Config.java | 3 - src/be/nikiroo/fanfix/reader/BasicReader.java | 156 +-- .../fanfix/reader/{cli => }/CliReader.java | 103 +- src/be/nikiroo/fanfix/reader/Reader.java | 267 ----- .../nikiroo/fanfix/reader/tui/ConfigItem.java | 362 ------ .../fanfix/reader/tui/ConfigItemString.java | 50 - .../fanfix/reader/tui/TOptionWindow.java | 120 -- .../reader/tui/TSimpleScrollableWindow.java | 150 --- .../nikiroo/fanfix/reader/tui/TuiReader.java | 107 -- .../reader/tui/TuiReaderApplication.java | 467 -------- .../reader/tui/TuiReaderMainWindow.java | 374 ------ .../reader/tui/TuiReaderOptionWindow.java | 15 - .../reader/tui/TuiReaderStoryWindow.java | 305 ----- .../nikiroo/fanfix/reader/ui/GuiReader.java | 499 -------- .../fanfix/reader/ui/GuiReaderBook.java | 339 ------ .../fanfix/reader/ui/GuiReaderBookInfo.java | 258 ----- .../reader/ui/GuiReaderCoverImager.java | 244 ---- .../fanfix/reader/ui/GuiReaderFrame.java | 1015 ----------------- .../fanfix/reader/ui/GuiReaderGroup.java | 476 -------- .../fanfix/reader/ui/GuiReaderMainPanel.java | 790 ------------- .../fanfix/reader/ui/GuiReaderNavBar.java | 343 ------ .../reader/ui/GuiReaderPropertiesFrame.java | 40 - .../reader/ui/GuiReaderPropertiesPane.java | 99 -- .../reader/ui/GuiReaderSearchAction.java | 91 -- .../reader/ui/GuiReaderSearchByNamePanel.java | 246 ---- .../reader/ui/GuiReaderSearchByPanel.java | 281 ----- .../reader/ui/GuiReaderSearchByTagPanel.java | 458 -------- .../reader/ui/GuiReaderSearchFrame.java | 380 ------ .../fanfix/reader/ui/GuiReaderViewer.java | 158 --- .../reader/ui/GuiReaderViewerPanel.java | 298 ----- .../reader/ui/GuiReaderViewerTextOutput.java | 128 --- 34 files changed, 143 insertions(+), 8684 deletions(-) rename src/be/nikiroo/fanfix/reader/{cli => }/CliReader.java (72%) delete mode 100644 src/be/nikiroo/fanfix/reader/Reader.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/ConfigItem.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TuiReader.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java delete mode 100644 src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReader.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java delete mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java diff --git a/configure.sh b/configure.sh index 15ae180..1c1a529 100755 --- a/configure.sh +++ b/configure.sh @@ -5,13 +5,6 @@ PREFIX=/usr/local PROGS="java javac jar make sed" IMG=be/nikiroo/utils/ui/ImageUtilsAwt -CLI=be/nikiroo/fanfix/reader/cli/CliReader -TUI=be/nikiroo/fanfix/reader/tui/TuiReader -GUI=be/nikiroo/fanfix/reader/ui/GuiReader -JIMG= -JCLI= -JTUI="-C bin/ jexer" -JGUI= valid=true while [ "$*" != "" ]; do @@ -31,15 +24,6 @@ while [ "$*" != "" ]; do --prefix) #=PATH Change the prefix to the given path PREFIX="$val" ;; - --cli) #=no Disable CLI support (System.out) - [ "$val" = no -o "$val" = false ] && CLI= && JCLI= - ;; - --tui) #=no Enable TUI support (Jexer) - [ "$val" = no -o "$val" = false ] && TUI= && JTUI= - ;; - --gui) #=no Disable GUI support (Swing) - [ "$val" = no -o "$val" = false ] && GUI= && JGUI= - ;; *) echo "Unsupported parameter: '$1'" >&2 echo >&2 @@ -76,13 +60,12 @@ else fi; echo "MAIN = be/nikiroo/fanfix/Main" > Makefile -echo "MORE = $CLI $TUI $GUI $IMG" >> Makefile +echo "MORE = $IMG" >> Makefile echo "TEST = be/nikiroo/fanfix/test/Test" >> Makefile echo "TEST_PARAMS = $cols $ok $ko" >> Makefile echo "NAME = fanfix" >> Makefile echo "PREFIX = $PREFIX" >> Makefile -echo "JAR_FLAGS += -C bin/ org $JCLI $JTUI $JGUI -C bin/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile -#echo "SJAR_FLAGS += -C src/ org -C src/ jexer -C src/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile +echo "JAR_FLAGS += -C bin/ org -C bin/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile cat Makefile.base >> Makefile diff --git a/src/be/nikiroo/fanfix/Instance.java b/src/be/nikiroo/fanfix/Instance.java index 5c00a07..c3c086f 100644 --- a/src/be/nikiroo/fanfix/Instance.java +++ b/src/be/nikiroo/fanfix/Instance.java @@ -284,9 +284,9 @@ public class Instance { } /** - * Get the (unique) {@link LocalLibrary} for the program. + * Get the (unique) {@link BasicLibrary} for the program. * - * @return the {@link LocalLibrary} + * @return the {@link BasicLibrary} */ public BasicLibrary getLibrary() { if (lib == null) { @@ -296,6 +296,18 @@ public class Instance { return lib; } + /** + * Change the default {@link BasicLibrary} for this program. + *

+ * Be careful. + * + * @param lib + * the new {@link BasicLibrary} + */ + public void setLibrary(BasicLibrary lib) { + this.lib = lib; + } + /** * Return the directory where to look for default cover pages. * diff --git a/src/be/nikiroo/fanfix/Main.java b/src/be/nikiroo/fanfix/Main.java index 961816a..8d72803 100644 --- a/src/be/nikiroo/fanfix/Main.java +++ b/src/be/nikiroo/fanfix/Main.java @@ -22,8 +22,7 @@ import be.nikiroo.fanfix.library.RemoteLibraryServer; import be.nikiroo.fanfix.output.BasicOutput; import be.nikiroo.fanfix.output.BasicOutput.OutputType; import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.fanfix.reader.Reader.ReaderType; +import be.nikiroo.fanfix.reader.CliReader; import be.nikiroo.fanfix.searchable.BasicSearchable; import be.nikiroo.fanfix.supported.BasicSupport; import be.nikiroo.fanfix.supported.SupportType; @@ -38,7 +37,7 @@ import be.nikiroo.utils.serial.server.ServerObject; */ public class Main { private enum MainAction { - IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG + IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG } /** @@ -76,8 +75,6 @@ public class Main { *

  • --set-source [id] [new source]: change the source of the given story
  • *
  • --set-title [id] [new title]: change the title of the given story
  • *
  • --set-author [id] [new author]: change the author of the given story
  • - *
  • --set-reader [reader type]: set the reader type to CLI, TUI or LOCAL - * for this command
  • *
  • --version: get the version of the program
  • *
  • --server: start the server mode (see config file for parameters)
  • *
  • --stop-server: stop the running server on this port if any
  • @@ -314,10 +311,6 @@ public class Main { case HELP: exitCode = 255; break; - case SET_READER: - exitCode = setReaderType(args[i]); - action = MainAction.START; - break; case START: exitCode = 255; // not supposed to be selected by user break; @@ -339,10 +332,11 @@ public class Main { port = Integer.parseInt(args[i]); BasicLibrary lib = new RemoteLibrary(key, host, port); - lib = new CacheLibrary(Instance.getInstance().getRemoteDir(host), lib, + lib = new CacheLibrary( + Instance.getInstance().getRemoteDir(host), lib, Instance.getInstance().getUiConfig()); - BasicReader.setDefaultLibrary(lib); + Instance.getInstance().setLibrary(lib); action = MainAction.START; } else { @@ -413,11 +407,6 @@ public class Main { updates.ok(); // we consider it read break; case LIST: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } exitCode = list(sourceString); break; case SET_SOURCE: @@ -445,20 +434,46 @@ public class Main { } break; case READ: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; + if (luid == null || luid.isEmpty()) { + syntax(false); + exitCode = 255; break; } - exitCode = read(luid, chapString, true); + + try { + BasicLibrary lib = Instance.getInstance().getLibrary(); + exitCode = read(lib.getStory(luid, null), chapString); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + exitCode = 2; + } + break; case READ_URL: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; + if (urlString == null || urlString.isEmpty()) { + syntax(false); + exitCode = 255; break; } - exitCode = read(urlString, chapString, false); + + try { + BasicSupport support = BasicSupport + .getSupport(BasicReader.getUrl(urlString)); + if (support == null) { + Instance.getInstance().getTraceHandler() + .error("URL not supported: " + urlString); + exitCode = 2; + break; + } + + exitCode = read(support.process(null), chapString); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + exitCode = 2; + } + break; case SEARCH: page = page == null ? 1 : page; @@ -475,19 +490,13 @@ public class Main { break; } - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - try { if (searchOn == null) { - BasicReader.getReader().search(true); + new CliReader().listSearchables(); } else if (search != null) { - BasicReader.getReader().search(searchOn, search, page, - item, true); + new CliReader().searchBooksByKeyword(searchOn, search, page, + item); } else { exitCode = 255; } @@ -517,15 +526,9 @@ public class Main { break; } - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } - try { - BasicReader.getReader().searchTag(searchOn, page, item, - true, tags.toArray(new Integer[] {})); + new CliReader().searchBooksByTag(searchOn, page, item, + tags.toArray(new Integer[] {})); } catch (IOException e1) { Instance.getInstance().getTraceHandler().error(e1); } @@ -535,9 +538,6 @@ public class Main { syntax(true); exitCode = 0; break; - case SET_READER: - exitCode = 255; - break; case VERSION: System.out .println(String.format("Fanfix version %s" @@ -547,13 +547,8 @@ public class Main { updates.ok(); // we consider it read break; case START: - if (BasicReader.getReader() == null) { - Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured")); - exitCode = 10; - break; - } try { - BasicReader.getReader().browse(null); + new CliReader().listBooks(null); } catch (IOException e) { Instance.getInstance().getTraceHandler().error(e); exitCode = 66; @@ -684,9 +679,8 @@ public class Main { * @return the exit return code (0 = success) */ private static int list(String source) { - BasicReader.setDefaultReaderType(ReaderType.CLI); try { - BasicReader.getReader().browse(source); + new CliReader().listBooks(source); } catch (IOException e) { Instance.getInstance().getTraceHandler().error(e); return 66; @@ -699,41 +693,41 @@ public class Main { * Start the current reader for this {@link Story}. * * @param story - * the LUID of the {@link Story} in the {@link LocalLibrary} - * or the {@link Story} {@link URL} + * the story to read * @param chapString * which {@link Chapter} to read (starting at 1), or NULL to get * the {@link Story} description - * @param library - * TRUE if the source is the {@link Story} LUID, FALSE if it is a - * {@link URL} * * @return the exit return code (0 = success) */ - private static int read(String story, String chapString, boolean library) { - try { - Reader reader = BasicReader.getReader(); - if (library) { - reader.setMeta(story); - } else { - reader.setMeta(BasicReader.getUrl(story), null); + private static int read(Story story, String chapString) { + Integer chap = null; + if (chapString != null) { + try { + chap = Integer.parseInt(chapString); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Chapter number cannot be parsed: " + chapString, e)); + return 2; } + } - if (chapString != null) { - try { - reader.setChapter(Integer.parseInt(chapString)); - reader.read(true); - } catch (NumberFormatException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Chapter number cannot be parsed: " + chapString, e)); - return 2; + if (story != null) { + try { + if (chap == null) { + new CliReader().listChapters(story); + } else { + new CliReader().printChapter(story, chap); } - } else { - reader.read(true); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + return 2; } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - return 1; + } else { + Instance.getInstance().getTraceHandler() + .error("Cannot find book: " + chapString); + return 2; } return 0; @@ -862,24 +856,4 @@ public class Main { System.err.println(trans(StringId.ERR_SYNTAX)); } } - - /** - * Set the default reader type for this session only (it can be changed in - * the configuration file, too, but this value will override it). - * - * @param readerTypeString - * the type - */ - private static int setReaderType(String readerTypeString) { - try { - ReaderType readerType = ReaderType.valueOf(readerTypeString - .toUpperCase()); - BasicReader.setDefaultReaderType(readerType); - return 0; - } catch (IllegalArgumentException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Unknown reader type: " + readerTypeString, e)); - return 1; - } - } } diff --git a/src/be/nikiroo/fanfix/bundles/Config.java b/src/be/nikiroo/fanfix/bundles/Config.java index fd27a83..587851a 100644 --- a/src/be/nikiroo/fanfix/bundles/Config.java +++ b/src/be/nikiroo/fanfix/bundles/Config.java @@ -16,9 +16,6 @@ public enum Config { @Meta(description = "The language to use for in the program (example: en-GB, fr-BE...) or nothing for default system language (can be overwritten with the variable $LANG)",// format = Format.LOCALE, list = { "en-GB", "fr-BE" }) LANG, // - @Meta(description = "The default reader type to use to read stories:\nCLI = simple output to console\nTUI = a Text User Interface with menus and windows, based upon Jexer\nGUI = a GUI with locally stored files, based upon Swing", // - hidden = true, format = Format.FIXED_LIST, list = { "CLI", "GUI", "TUI" }, def = "GUI") - READER_TYPE, // @Meta(description = "File format options",// group = true) diff --git a/src/be/nikiroo/fanfix/reader/BasicReader.java b/src/be/nikiroo/fanfix/reader/BasicReader.java index 7f79da3..9ec0879 100644 --- a/src/be/nikiroo/fanfix/reader/BasicReader.java +++ b/src/be/nikiroo/fanfix/reader/BasicReader.java @@ -11,171 +11,18 @@ import java.util.Map; import java.util.TreeMap; import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; import be.nikiroo.fanfix.bundles.UiConfig; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.utils.Progress; import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.serial.SerialUtils; /** * The class that handles the different {@link Story} readers you can use. - *

    - * All the readers should be accessed via {@link BasicReader#getReader()}. * * @author niki */ -public abstract class BasicReader implements Reader { - private static BasicLibrary defaultLibrary = Instance.getInstance().getLibrary(); - private static ReaderType defaultType = ReaderType.GUI; - - private BasicLibrary lib; - private MetaData meta; - private Story story; - private int chapter; - - /** - * Take the default reader type configuration from the config file. - */ - static { - String typeString = Instance.getInstance().getConfig().getString(Config.READER_TYPE); - if (typeString != null && !typeString.isEmpty()) { - try { - ReaderType type = ReaderType.valueOf(typeString.toUpperCase()); - defaultType = type; - } catch (IllegalArgumentException e) { - // Do nothing - } - } - } - - @Override - public synchronized Story getStory(Progress pg) throws IOException { - if (story == null) { - story = getLibrary().getStory(meta.getLuid(), pg); - } - - return story; - } - - @Override - public BasicLibrary getLibrary() { - if (lib == null) { - lib = defaultLibrary; - } - - return lib; - } - - @Override - public void setLibrary(BasicLibrary lib) { - this.lib = lib; - } - - @Override - public synchronized MetaData getMeta() { - return meta; - } - - @Override - public synchronized void setMeta(MetaData meta) throws IOException { - setMeta(meta == null ? null : meta.getLuid()); // must check the library - } - - @Override - public synchronized void setMeta(String luid) throws IOException { - story = null; - meta = getLibrary().getInfo(luid); - - if (meta == null) { - throw new IOException("Cannot retrieve story from library: " + luid); - } - } - - @Override - public synchronized void setMeta(URL url, Progress pg) throws IOException { - BasicSupport support = BasicSupport.getSupport(url); - if (support == null) { - throw new IOException("URL not supported: " + url.toString()); - } - - story = support.process(pg); - if (story == null) { - throw new IOException( - "Cannot retrieve story from external source: " - + url.toString()); - } - - meta = story.getMeta(); - } - - @Override - public int getChapter() { - return chapter; - } - - @Override - public void setChapter(int chapter) { - this.chapter = chapter; - } - - /** - * Return a new {@link BasicReader} ready for use if one is configured. - *

    - * Can return NULL if none are configured. - * - * @return a {@link BasicReader}, or NULL if none configured - */ - public static Reader getReader() { - try { - if (defaultType != null) { - return (Reader) SerialUtils.createObject(defaultType - .getTypeName()); - } - } catch (Exception e) { - Instance.getInstance().getTraceHandler() - .error(new Exception("Cannot create a reader of type: " + defaultType + " (Not compiled in?)", e)); - } - - return null; - } - - /** - * The default {@link Reader.ReaderType} used when calling - * {@link BasicReader#getReader()}. - * - * @return the default type - */ - public static ReaderType getDefaultReaderType() { - return defaultType; - } - - /** - * The default {@link Reader.ReaderType} used when calling - * {@link BasicReader#getReader()}. - * - * @param defaultType - * the new default type - */ - public static void setDefaultReaderType(ReaderType defaultType) { - BasicReader.defaultType = defaultType; - } - - /** - * Change the default {@link LocalLibrary} to open with the - * {@link BasicReader}s. - * - * @param lib - * the new {@link LocalLibrary} - */ - public static void setDefaultLibrary(BasicLibrary lib) { - BasicReader.defaultLibrary = lib; - } - +public abstract class BasicReader { /** * Return an {@link URL} from this {@link String}, be it a file path or an * actual {@link URL}. @@ -263,7 +110,6 @@ public abstract class BasicReader implements Reader { * @throws IOException * in case of I/O error */ - @Override public void openExternal(BasicLibrary lib, String luid, boolean sync) throws IOException { MetaData meta = lib.getInfo(luid); diff --git a/src/be/nikiroo/fanfix/reader/cli/CliReader.java b/src/be/nikiroo/fanfix/reader/CliReader.java similarity index 72% rename from src/be/nikiroo/fanfix/reader/cli/CliReader.java rename to src/be/nikiroo/fanfix/reader/CliReader.java index 235276c..96ca644 100644 --- a/src/be/nikiroo/fanfix/reader/cli/CliReader.java +++ b/src/be/nikiroo/fanfix/reader/CliReader.java @@ -1,4 +1,4 @@ -package be.nikiroo.fanfix.reader.cli; +package be.nikiroo.fanfix.reader; import java.io.IOException; import java.util.List; @@ -9,10 +9,10 @@ import be.nikiroo.fanfix.data.Chapter; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Paragraph; import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.reader.BasicReader; import be.nikiroo.fanfix.searchable.BasicSearchable; import be.nikiroo.fanfix.searchable.SearchableTag; import be.nikiroo.fanfix.supported.SupportType; +import be.nikiroo.utils.Image; import be.nikiroo.utils.StringUtils; /** @@ -22,11 +22,27 @@ import be.nikiroo.utils.StringUtils; * * @author niki */ -class CliReader extends BasicReader { - @Override - public void read(boolean sync) throws IOException { - MetaData meta = getMeta(); +public class CliReader extends BasicReader { + public void listBooks(String source) throws IOException { + List stories = Instance.getInstance().getLibrary().getList() + .filter(source, null, null); + for (MetaData story : stories) { + String author = ""; + if (story.getAuthor() != null && !story.getAuthor().isEmpty()) { + author = " (" + story.getAuthor() + ")"; + } + + System.out.println( + story.getLuid() + ": " + story.getTitle() + author); + } + } + + public void listChapters(Story story) throws IOException { + if (story == null || story.getMeta() == null) { + throw new IOException("No story to read"); + } + MetaData meta = story.getMeta(); if (meta == null) { throw new IOException("No story to read"); } @@ -49,57 +65,50 @@ class CliReader extends BasicReader { System.out.println(author); System.out.println(""); - // TODO: progress? - for (Chapter chap : getStory(null)) { + for (Chapter chap : story) { if (chap.getName() != null && !chap.getName().isEmpty()) { - System.out.println(Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), + System.out.println(Instance.getInstance().getTrans().getString( + StringId.CHAPTER_NAMED, chap.getNumber(), chap.getName())); } else { - System.out.println( - Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber())); + System.out.println(Instance.getInstance().getTrans() + .getString(StringId.CHAPTER_UNNAMED, chap.getNumber())); } } } - public void read(int chapter) throws IOException { - MetaData meta = getMeta(); - + public void printChapter(Story story, int chapter) throws IOException { + if (story == null || story.getMeta() == null) { + throw new IOException("No story to read"); + } + MetaData meta = story.getMeta(); if (meta == null) { throw new IOException("No story to read"); } - // TODO: progress? - if (chapter > getStory(null).getChapters().size()) { + if (chapter <= 0 || chapter > story.getChapters().size()) { System.err.println("Chapter " + chapter + ": no such chapter"); } else { - Chapter chap = getStory(null).getChapters().get(chapter - 1); - System.out.println("Chapter " + chap.getNumber() + ": " - + chap.getName()); - + Chapter chap = story.getChapters().get(chapter - 1); + System.out.println( + "Chapter " + chap.getNumber() + ": " + chap.getName()); + System.out.println(); + for (Paragraph para : chap) { - System.out.println(para.getContent()); + Image img = para.getContentImage(); + if (img != null) { + String sz = StringUtils.formatNumber(img.getSize(), 1); + System.out.println("[Image: " + sz + "]"); + } else { + System.out.println( + para.getContent() == null ? "" : para.getContent()); + } System.out.println(""); } } } - @Override - public void browse(String source) throws IOException { - List stories = getLibrary().getList().filter(source, null, null); - - for (MetaData story : stories) { - String author = ""; - if (story.getAuthor() != null && !story.getAuthor().isEmpty()) { - author = " (" + story.getAuthor() + ")"; - } - - System.out.println(story.getLuid() + ": " + story.getTitle() - + author); - } - } - - @Override - public void search(boolean sync) throws IOException { + public void listSearchables() throws IOException { for (SupportType type : SupportType.values()) { if (BasicSearchable.getSearchable(type) != null) { System.out.println(type); @@ -107,9 +116,8 @@ class CliReader extends BasicReader { } } - @Override - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) throws IOException { + public void searchBooksByKeyword(SupportType searchOn, String keywords, + int page, int item) throws IOException { BasicSearchable search = BasicSearchable.getSearchable(searchOn); if (page == 0) { @@ -118,8 +126,8 @@ class CliReader extends BasicReader { List metas = search.search(keywords, page); if (item == 0) { - System.out.println("Page " + page + " of stories for: " - + keywords); + System.out.println( + "Page " + page + " of stories for: " + keywords); displayStories(metas); } else { // ! 1-based index ! @@ -133,9 +141,8 @@ class CliReader extends BasicReader { } } - @Override - public void searchTag(SupportType searchOn, int page, int item, - boolean sync, Integer... tags) throws IOException { + public void searchBooksByTag(SupportType searchOn, int page, int item, + Integer... tags) throws IOException { BasicSearchable search = BasicSearchable.getSearchable(searchOn); SearchableTag stag = search.getTag(tags); @@ -178,8 +185,8 @@ class CliReader extends BasicReader { displayTag(subtag); } } else { - System.out.println("Invalid item: only " + count - + " items found"); + System.out.println( + "Invalid item: only " + count + " items found"); } } else { if (metas != null) { diff --git a/src/be/nikiroo/fanfix/reader/Reader.java b/src/be/nikiroo/fanfix/reader/Reader.java deleted file mode 100644 index 3ecf247..0000000 --- a/src/be/nikiroo/fanfix/reader/Reader.java +++ /dev/null @@ -1,267 +0,0 @@ -package be.nikiroo.fanfix.reader; - -import java.io.IOException; -import java.net.URL; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; - -/** - * A {@link Reader} is a class that will handle {@link Story} reading and - * browsing. - * - * @author niki - */ -public interface Reader { - /** - * A type of {@link BasicReader}. - * - * @author niki - */ - public enum ReaderType { - /** Simple reader that outputs everything on the console */ - CLI, - /** Reader that starts local programs to handle the stories */ - GUI, - /** A text (UTF-8) reader with menu and text windows */ - TUI, - /** A GUI reader implemented with the Android framework */ - ANDROID, - - ; - - /** - * Return the full class name of a type that implements said - * {@link ReaderType}. - * - * @return the class name - */ - public String getTypeName() { - String pkg = "be.nikiroo.fanfix.reader."; - switch (this) { - case CLI: - return pkg + "cli.CliReader"; - case TUI: - return pkg + "tui.TuiReader"; - case GUI: - return pkg + "ui.GuiReader"; - case ANDROID: - return pkg + "android.AndroidReader"; - } - - return null; - } - } - - /** - * Return the current target {@link MetaData}. - * - * @return the meta - */ - public MetaData getMeta(); - - /** - * Return the current {@link Story} as described by the current - * {@link MetaData}. - * - * @param pg - * the optional progress - * - * @return the {@link Story} - * - * @throws IOException - * in case of I/O error - * - */ - public Story getStory(Progress pg) throws IOException; - - /** - * The {@link BasicLibrary} to load the stories from (by default, takes the - * default {@link BasicLibrary}). - * - * @return the {@link BasicLibrary} - */ - public BasicLibrary getLibrary(); - - /** - * Change the {@link BasicLibrary} that will be managed by this - * {@link BasicReader}. - * - * @param lib - * the new {@link BasicLibrary} - */ - public void setLibrary(BasicLibrary lib); - - /** - * Set a {@link Story} from the current {@link BasicLibrary} into the - * {@link Reader}. - * - * @param luid - * the {@link Story} ID - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(String luid) throws IOException; - - /** - * Set a {@link Story} from the current {@link BasicLibrary} into the - * {@link Reader}. - * - * @param meta - * the meta - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(MetaData meta) throws IOException; - - /** - * Set an external {@link Story} into this {@link Reader}. - * - * @param source - * the {@link Story} {@link URL} - * @param pg - * the optional progress reporter - * - * @throws IOException - * in case of I/O error - */ - public void setMeta(URL source, Progress pg) throws IOException; - - /** - * Start the {@link Story} Reading. - * - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error or if the {@link Story} was not - * previously set - */ - public void read(boolean sync) throws IOException; - - /** - * The selected chapter to start reading at (starting at 1, 0 = description, - * -1 = none). - * - * @return the chapter, or -1 for "no chapter" - */ - public int getChapter(); - - /** - * The selected chapter to start reading at (starting at 1, 0 = description, - * -1 = none). - * - * @param chapter - * the chapter, or -1 for "no chapter" - */ - public void setChapter(int chapter); - - /** - * Start the reader in browse mode for the given source (or pass NULL for - * all sources). - *

    - * Note that this must be a synchronous action. - * - * @param source - * the type of {@link Story} to take into account, or NULL for - * all - * - * @throws IOException - * in case of I/O error - */ - public void browse(String source) throws IOException; - - /** - * Display all supports that allow search operations. - * - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void search(boolean sync) throws IOException; - - /** - * Search for the given terms and find stories that correspond if possible. - * - * @param searchOn - * the website to search on - * @param keywords - * the words to search for (cannot be NULL) - * @param page - * the page of results to show (0 = request the maximum number of - * pages, pages start at 1) - * @param item - * the item to select (0 = do not select a specific item but show - * all the page, items start at 1) - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) throws IOException; - - /** - * Search based upon a hierarchy of tags, or search for (sub)tags. - *

    - * We use the tags DisplayName. - *

    - * If no tag is given, the main tags will be shown. - *

    - * If a non-leaf tag is given, the subtags will be shown. - *

    - * If a leaf tag is given (or a full hierarchy ending with a leaf tag), - * stories will be shown. - *

    - * You can select the story you want with the item number. - * - * @param searchOn - * the website to search on - * @param page - * the page of results to show (0 = request the maximum number of - * pages, pages start at 1) - * @param item - * the item to select (0 = do not select a specific item but show - * all the page, items start at 1) - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * @param tags - * the tags indices to search for (this is a tag - * hierarchy, NOT a multiple tags choice) - * - * @throws IOException - * in case of I/O error - */ - public void searchTag(SupportType searchOn, int page, int item, - boolean sync, Integer... tags) throws IOException; - - /** - * Open the {@link Story} with an external reader (the program should be - * passed the main file associated with this {@link Story}). - * - * @param lib - * the {@link BasicLibrary} to select the {@link Story} from - * @param luid - * the {@link Story} LUID - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O error - */ - public void openExternal(BasicLibrary lib, String luid, boolean sync) - throws IOException; -} diff --git a/src/be/nikiroo/fanfix/reader/tui/ConfigItem.java b/src/be/nikiroo/fanfix/reader/tui/ConfigItem.java deleted file mode 100644 index 43e9fe8..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/ConfigItem.java +++ /dev/null @@ -1,362 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.util.List; - -import jexer.TAction; -import jexer.TButton; -import jexer.TLabel; -import jexer.TPanel; -import jexer.TWidget; -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.MetaInfo; -import be.nikiroo.utils.ui.ConfigItemBase; - -/** - * A graphical item that reflect a configuration option from the given - * {@link Bundle}. - *

    - * This graphical item can be edited, and the result will be saved back into the - * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should - * you wish to, of course. - * - * @author niki - * - * @param - * the type of {@link Bundle} to edit - */ -public abstract class ConfigItem> extends TWidget { - /** The code base */ - private final ConfigItemBase base; - - /** - * Prepare a new {@link ConfigItem} instance, linked to the given - * {@link MetaInfo}. - * - * @param parent - * the parent widget - * @param info - * the info - * @param autoDirtyHandling - * TRUE to automatically manage the setDirty/Save operations, - * FALSE if you want to do it yourself via - * {@link ConfigItem#setDirtyItem(int)} - */ - protected ConfigItem(TWidget parent, MetaInfo info, - boolean autoDirtyHandling) { - super(parent); - - base = new ConfigItemBase(info, autoDirtyHandling) { - @Override - protected TWidget createEmptyField(int item) { - return ConfigItem.this.createEmptyField(item); - } - - @Override - protected Object getFromInfo(int item) { - return ConfigItem.this.getFromInfo(item); - } - - @Override - protected void setToInfo(Object value, int item) { - ConfigItem.this.setToInfo(value, item); - } - - @Override - protected Object getFromField(int item) { - return ConfigItem.this.getFromField(item); - } - - @Override - protected void setToField(Object value, int item) { - ConfigItem.this.setToField(value, item); - } - - @Override - public TWidget createField(int item) { - TWidget field = super.createField(item); - - // TODO: size? - - return field; - } - - @Override - public List reload() { - List removed = base.reload(); - if (!removed.isEmpty()) { - for (TWidget c : removed) { - removeChild(c); - } - } - - return removed; - } - }; - } - - /** - * Create a new {@link ConfigItem} for the given {@link MetaInfo}. - * - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - */ - public void init(int nhgap) { - if (getInfo().isArray()) { - // TODO: width - int size = getInfo().getListSize(false); - final TPanel pane = new TPanel(this, 0, 0, 20, size + 2); - final TWidget label = label(0, 0, nhgap); - label.setParent(pane, false); - setHeight(pane.getHeight()); - - for (int i = 0; i < size; i++) { - // TODO: minusPanel - TWidget field = base.addItem(i, null); - field.setParent(pane, false); - field.setX(label.getWidth() + 1); - field.setY(i); - } - - // x, y - final TButton add = new TButton(pane, "+", label.getWidth() + 1, - size + 1, null); - TAction action = new TAction() { - @Override - public void DO() { - TWidget field = base.addItem(base.getFieldsSize(), null); - field.setParent(pane, false); - field.setX(label.getWidth() + 1); - field.setY(add.getY()); - add.setY(add.getY() + 1); - } - }; - add.setAction(action); - } else { - final TWidget label = label(0, 0, nhgap); - - TWidget field = base.createField(-1); - field.setX(label.getWidth() + 1); - field.setWidth(10); // TODO - - // TODO - setWidth(30); - setHeight(1); - } - } - - /** The {@link MetaInfo} linked to the field. */ - public MetaInfo getInfo() { - return base.getInfo(); - } - - /** - * Retrieve the associated graphical component that was created with - * {@link ConfigItemBase#createEmptyField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - protected TWidget getField(int item) { - return base.getField(item); - } - - /** - * Manually specify that the given item is "dirty" and thus should be saved - * when asked. - *

    - * Has no effect if the class is using automatic dirty handling (see - * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}). - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - protected void setDirtyItem(int item) { - base.setDirtyItem(item); - } - - /** - * Check if the value changed since the last load/save into the linked - * {@link MetaInfo}. - *

    - * Note that we consider NULL and an Empty {@link String} to be equals. - * - * @param value - * the value to test - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return TRUE if it has - */ - protected boolean hasValueChanged(Object value, int item) { - return base.hasValueChanged(value, item); - } - - /** - * Create an empty graphical component to be used later by - * {@link ConfigItem#createField(int)}. - *

    - * Note that {@link ConfigItem#reload(int)} will be called after it was - * created by {@link ConfigItem#createField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - abstract protected TWidget createEmptyField(int item); - - /** - * Get the information from the {@link MetaInfo} in the subclass preferred - * format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the information in the subclass preferred format - */ - abstract protected Object getFromInfo(int item); - - /** - * Set the value to the {@link MetaInfo}. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToInfo(Object value, int item); - - /** - * The value present in the given item's related field in the subclass - * preferred format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the value present in the given item's related field in the - * subclass preferred format - */ - abstract protected Object getFromField(int item); - - /** - * Set the value (in the subclass preferred format) into the field. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToField(Object value, int item); - - /** - * Create a label which width is constrained in lock steps. - * - * @param x - * the X position of the label - * @param y - * the Y position of the label - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - * - * @return the label - */ - protected TWidget label(int x, int y, int nhgap) { - // TODO: see Swing version for lock-step sizes - // TODO: see Swing version for help info-buttons - - String lbl = getInfo().getName(); - return new TLabel(this, lbl, x, y); - } - - /** - * Create a new {@link ConfigItem} for the given {@link MetaInfo}. - * - * @param - * the type of {@link Bundle} to edit - * - * @param x - * the X position of the item - * @param y - * the Y position of the item - * @param parent - * the parent widget to use for this one - * @param info - * the {@link MetaInfo} - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - * - * @return the new {@link ConfigItem} - */ - static public > ConfigItem createItem(TWidget parent, - int x, int y, MetaInfo info, int nhgap) { - - ConfigItem configItem; - switch (info.getFormat()) { - // TODO - // case BOOLEAN: - // configItem = new ConfigItemBoolean(info); - // break; - // case COLOR: - // configItem = new ConfigItemColor(info); - // break; - // case FILE: - // configItem = new ConfigItemBrowse(info, false); - // break; - // case DIRECTORY: - // configItem = new ConfigItemBrowse(info, true); - // break; - // case COMBO_LIST: - // configItem = new ConfigItemCombobox(info, true); - // break; - // case FIXED_LIST: - // configItem = new ConfigItemCombobox(info, false); - // break; - // case INT: - // configItem = new ConfigItemInteger(info); - // break; - // case PASSWORD: - // configItem = new ConfigItemPassword(info); - // break; - // case LOCALE: - // configItem = new ConfigItemLocale(info); - // break; - // case STRING: - default: - configItem = new ConfigItemString(parent, info); - break; - } - - configItem.init(nhgap); - configItem.setX(x); - configItem.setY(y); - - return configItem; - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java b/src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java deleted file mode 100644 index b1057e9..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java +++ /dev/null @@ -1,50 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import jexer.TField; -import jexer.TWidget; -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemString> extends ConfigItem { - /** - * Create a new {@link ConfigItemString} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemString(TWidget parent, MetaInfo info) { - super(parent, info, true); - } - - @Override - protected Object getFromField(int item) { - TField field = (TField) getField(item); - if (field != null) { - return field.getText(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getString(item, false); - } - - @Override - protected void setToField(Object value, int item) { - TField field = (TField) getField(item); - if (field != null) { - field.setText(value == null ? "" : value.toString()); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString((String) value, item); - } - - @Override - protected TWidget createEmptyField(int item) { - return new TField(this, 0, 0, 1, false); - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java b/src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java deleted file mode 100644 index 4bb67de..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java +++ /dev/null @@ -1,120 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import jexer.TAction; -import jexer.TApplication; -import jexer.TPanel; -import jexer.TWidget; -import jexer.event.TCommandEvent; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.MetaInfo; - -public class TOptionWindow> extends TSimpleScrollableWindow { - private List> items; - - public TOptionWindow(TApplication parent, Class type, - final Bundle bundle, String title) { - super(parent, title, 0, 0, CENTERED | RESIZABLE); - - getMainPane().addLabel(title, 0, 0); - - items = new ArrayList>(); - List> groupedItems = MetaInfo.getItems(type, bundle); - int y = 2; - for (MetaInfo item : groupedItems) { - // will populate this.items - y += addItem(getMainPane(), 5, y, item, 0).getHeight(); - } - - y++; - - setRealHeight(y + 1); - - getMainPane().addButton("Reset", 25, y, new TAction() { - @Override - public void DO() { - for (MetaInfo item : items) { - item.reload(); - } - } - }); - - getMainPane().addButton("Default", 15, y, new TAction() { - @Override - public void DO() { - Object snap = bundle.takeSnapshot(); - bundle.reload(true); - for (MetaInfo item : items) { - item.reload(); - } - bundle.reload(false); - bundle.restoreSnapshot(snap); - } - }); - - getMainPane().addButton("Save", 1, y, new TAction() { - @Override - public void DO() { - for (MetaInfo item : items) { - item.save(true); - } - - try { - bundle.updateFile(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - }); - } - - private TWidget addItem(TWidget parent, int x, int y, MetaInfo item, - int nhgap) { - if (item.isGroup()) { - // TODO: width - int w = 80 - x; - - String name = item.getName(); - String info = item.getDescription(); - info = StringUtils.justifyTexts(info, w - 3); // -3 for borders - - final TPanel pane = new TPanel(parent, x, y, w, 1); - pane.addLabel(name, 0, 0); - - int h = 0; - if (!info.isEmpty()) { - h += info.split("\n").length + 1; // +1 for scroll - pane.addText(info + "\n", 0, 1, w, h); - } - - // +1 for the title - h++; - - int paneY = h; // for the info desc - for (MetaInfo subitem : item) { - paneY += addItem(pane, 4, paneY, subitem, nhgap + 11) - .getHeight(); - } - - pane.setHeight(paneY); - return pane; - } - - items.add(item); - return ConfigItem.createItem(parent, x, y, item, nhgap); - } - - @Override - public void onCommand(TCommandEvent command) { - if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) { - TuiReaderApplication.close(this); - } else { - // Handle our own event if needed here - super.onCommand(command); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java b/src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java deleted file mode 100644 index 7b8f439..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java +++ /dev/null @@ -1,150 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import jexer.TApplication; -import jexer.THScroller; -import jexer.TPanel; -import jexer.TScrollableWindow; -import jexer.TVScroller; -import jexer.TWidget; -import jexer.event.TMouseEvent; -import jexer.event.TResizeEvent; - -public class TSimpleScrollableWindow extends TScrollableWindow { - protected TPanel mainPane; - private int prevHorizontal = -1; - private int prevVertical = -1; - - public TSimpleScrollableWindow(TApplication application, String title, - int width, int height) { - this(application, title, width, height, 0, 0, 0); - } - - public TSimpleScrollableWindow(TApplication application, String title, - int width, int height, int flags) { - this(application, title, width, height, flags, 0, 0); - } - - // 0 = none (so, no scrollbar) - public TSimpleScrollableWindow(TApplication application, String title, - int width, int height, int flags, int realWidth, int realHeight) { - super(application, title, width, height, flags); - - mainPane = new TPanel(this, 0, 0, 1, 1) { - @Override - public void draw() { - for (TWidget children : mainPane.getChildren()) { - int y = children.getY() + children.getHeight(); - int x = children.getX() + children.getWidth(); - boolean visible = (y > getVerticalValue()) - && (x > getHorizontalValue()); - children.setVisible(visible); - } - super.draw(); - } - }; - - mainPane.setWidth(getWidth()); - mainPane.setHeight(getHeight()); - - setRealWidth(realWidth); - setRealHeight(realHeight); - placeScrollbars(); - } - - /** - * The main pane on which you can add other widgets for this scrollable - * window. - * - * @return the main pane - */ - public TPanel getMainPane() { - return mainPane; - } - - public void setRealWidth(int realWidth) { - if (realWidth <= 0) { - if (hScroller != null) { - hScroller.remove(); - } - } else { - if (hScroller == null) { - // size/position will be fixed by placeScrollbars() - hScroller = new THScroller(this, 0, 0, 10); - } - setRightValue(realWidth); - } - - reflowData(); - } - - public void setRealHeight(int realHeight) { - if (realHeight <= 0) { - if (vScroller != null) { - vScroller.remove(); - } - } else { - if (vScroller == null) { - // size/position will be fixed by placeScrollbars() - vScroller = new TVScroller(this, 0, 0, 10); - } - setBottomValue(realHeight); - } - - reflowData(); - } - - @Override - public void onResize(TResizeEvent event) { - super.onResize(event); - mainPane.setWidth(getWidth()); - mainPane.setHeight(getHeight()); - mainPane.onResize(event); - } - - @Override - public void reflowData() { - super.reflowData(); - reflowData(getHorizontalValue(), getVerticalValue()); - } - - protected void reflowData(int totalX, int totalY) { - super.reflowData(); - mainPane.setX(-totalX); - mainPane.setY(-totalY); - } - - @Override - public void onMouseUp(TMouseEvent mouse) { - super.onMouseUp(mouse); - - // TODO: why? this should already be done by the scrollers - // it could also mean we do it twice if, somehow, it sometime works... - int mrx = mouse.getX(); - int mry = mouse.getY(); - - int mx = mouse.getAbsoluteX(); - int my = mouse.getAbsoluteY(); - - if (vScroller != null) { - mouse.setX(mx - vScroller.getAbsoluteX()); - mouse.setY(my - vScroller.getAbsoluteY()); - vScroller.onMouseUp(mouse); - } - if (hScroller != null) { - mouse.setX(mx - hScroller.getAbsoluteX()); - mouse.setY(my - hScroller.getAbsoluteY()); - hScroller.onMouseUp(mouse); - } - - mouse.setX(mrx); - mouse.setY(mry); - // - - if (prevHorizontal != getHorizontalValue() - || prevVertical != getVerticalValue()) { - prevHorizontal = getHorizontalValue(); - prevVertical = getVerticalValue(); - reflowData(); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReader.java b/src/be/nikiroo/fanfix/reader/tui/TuiReader.java deleted file mode 100644 index ef5e71b..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReader.java +++ /dev/null @@ -1,107 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.io.IOException; - -import jexer.TApplication; -import jexer.TApplication.BackendType; -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.fanfix.supported.SupportType; - -/** - * This {@link Reader}is based upon the TUI widget library 'jexer' - * (https://github.com/klamonte/jexer/) and offer, as its name suggest, a Text - * User Interface. - *

    - * It is expected to be on par with the GUI version. - * - * @author niki - */ -class TuiReader extends BasicReader { - /** - * Will detect the backend to use. - *

    - * Swing is the default backend on Windows and MacOS while evreything else - * will use XTERM unless explicitly overridden by jexer.Swing = - * true or false. - * - * @return the backend to use - */ - private static BackendType guessBackendType() { - // TODO: allow a config option to force one or the other? - TApplication.BackendType backendType = TApplication.BackendType.XTERM; - if (System.getProperty("os.name").startsWith("Windows")) { - backendType = TApplication.BackendType.SWING; - } - - if (System.getProperty("os.name").startsWith("Mac")) { - backendType = TApplication.BackendType.SWING; - } - - if (System.getProperty("jexer.Swing") != null) { - if (System.getProperty("jexer.Swing", "false").equals("true")) { - backendType = TApplication.BackendType.SWING; - } else { - backendType = TApplication.BackendType.XTERM; - } - } - - return backendType; - } - - @Override - public void read(boolean sync) throws IOException { - // TODO - if (!sync) { - // How could you do a not-sync in TUI mode? - throw new java.lang.IllegalStateException( - "Async mode not implemented yet."); - } - - try { - TuiReaderApplication app = new TuiReaderApplication(this, - guessBackendType()); - app.run(); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - @Override - public void browse(String source) { - try { - TuiReaderApplication app = new TuiReaderApplication(this, source, - guessBackendType()); - app.run(); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - @Override - public void search(boolean sync) throws IOException { - // TODO - if (sync) { - throw new java.lang.IllegalStateException("Not implemented yet."); - } - } - - @Override - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) { - // TODO - if (sync) { - throw new java.lang.IllegalStateException("Not implemented yet."); - } - } - - @Override - public void searchTag(SupportType searchOn, int page, int item, - boolean sync, Integer... tags) { - // TODO - if (sync) { - throw new java.lang.IllegalStateException("Not implemented yet."); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java deleted file mode 100644 index f85f12f..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java +++ /dev/null @@ -1,467 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.awt.Toolkit; -import java.awt.datatransfer.DataFlavor; -import java.io.IOException; -import java.net.URL; -import java.net.UnknownHostException; - -import jexer.TApplication; -import jexer.TCommand; -import jexer.TKeypress; -import jexer.TMessageBox; -import jexer.TMessageBox.Result; -import jexer.TMessageBox.Type; -import jexer.TStatusBar; -import jexer.TWidget; -import jexer.TWindow; -import jexer.event.TCommandEvent; -import jexer.event.TMenuEvent; -import jexer.menu.TMenu; -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.fanfix.reader.tui.TuiReaderMainWindow.Mode; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; - -/** - * Manages the TUI general mode and links and manages the {@link TWindow}s. - *

    - * It will also enclose a {@link Reader} and simply handle the reading part - * differently (it will create the required sub-windows and display them). - * - * @author niki - */ -class TuiReaderApplication extends TApplication implements Reader { - public static final int MENU_FILE_OPEN = 1025; - public static final int MENU_FILE_IMPORT_URL = 1026; - public static final int MENU_FILE_IMPORT_FILE = 1027; - public static final int MENU_FILE_EXPORT = 1028; - public static final int MENU_FILE_DELETE = 1029; - public static final int MENU_FILE_LIBRARY = 1030; - public static final int MENU_FILE_EXIT = 1031; - // - public static final int MENU_OPT_FANFIX = 1032; - public static final int MENU_OPT_TUI = 1033; - - - public static final TCommand CMD_EXIT = new TCommand(MENU_FILE_EXIT) { - }; - - private Reader reader; - private TuiReaderMainWindow main; - - // start reading if meta present - public TuiReaderApplication(Reader reader, BackendType backend) - throws Exception { - super(backend); - init(reader); - - if (getMeta() != null) { - read(false); - } - } - - public TuiReaderApplication(Reader reader, String source, - TApplication.BackendType backend) throws Exception { - super(backend); - init(reader); - - showMain(); - main.setMode(Mode.SOURCE, source); - } - - @Override - public void read(boolean sync) throws IOException { - read(getStory(null), sync); - } - - @Override - public MetaData getMeta() { - return reader.getMeta(); - } - - @Override - public Story getStory(Progress pg) throws IOException { - return reader.getStory(pg); - } - - @Override - public BasicLibrary getLibrary() { - return reader.getLibrary(); - } - - @Override - public void setLibrary(BasicLibrary lib) { - reader.setLibrary(lib); - } - - @Override - public void setMeta(MetaData meta) throws IOException { - reader.setMeta(meta); - } - - @Override - public void setMeta(String luid) throws IOException { - reader.setMeta(luid); - } - - @Override - public void setMeta(URL source, Progress pg) throws IOException { - reader.setMeta(source, pg); - } - - @Override - public void browse(String source) { - try { - reader.browse(source); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - @Override - public int getChapter() { - return reader.getChapter(); - } - - @Override - public void setChapter(int chapter) { - reader.setChapter(chapter); - } - - @Override - public void search(boolean sync) throws IOException { - reader.search(sync); - } - - @Override - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) throws IOException { - reader.search(searchOn, keywords, page, item, sync); - } - - @Override - public void searchTag(SupportType searchOn, int page, int item, - boolean sync, Integer... tags) throws IOException { - reader.searchTag(searchOn, page, item, sync, tags); - } - - /** - * Open the given {@link Story} for reading. This may or may not start an - * external program to read said {@link Story}. - * - * @param story - * the {@link Story} to read - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * - * @throws IOException - * in case of I/O errors - */ - public void read(Story story, boolean sync) throws IOException { - if (story == null) { - throw new IOException("No story to read"); - } - - // TODO: open in editor + external option - if (!story.getMeta().isImageDocument()) { - TWindow window = new TuiReaderStoryWindow(this, story, getChapter()); - window.maximize(); - } else { - try { - openExternal(getLibrary(), story.getMeta().getLuid(), sync); - } catch (IOException e) { - messageBox("Error when trying to open the story", - e.getMessage(), TMessageBox.Type.OK); - } - } - } - - /** - * Set the default status bar when this window appear. - *

    - * Some shortcuts are always visible, and will be put here. - *

    - * Note that shortcuts placed this way on menu won't work unless the menu - * also implement them. - * - * @param window - * the new window or menu on screen - * @param description - * the description to show on the status ba - */ - public TStatusBar setStatusBar(TWindow window, String description) { - TStatusBar statusBar = window.newStatusBar(description); - statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit"); - return statusBar; - - } - - private void showMain() { - if (main != null && main.isVisible()) { - main.activate(); - } else { - if (main != null) { - main.close(); - } - main = new TuiReaderMainWindow(this); - main.maximize(); - } - } - - private void init(Reader reader) { - this.reader = reader; - - // TODO: traces/errors? - Instance.getInstance().setTraceHandler(null); - - // Add the menus TODO: i18n - TMenu fileMenu = addMenu("&File"); - fileMenu.addItem(MENU_FILE_OPEN, "&Open..."); - fileMenu.addItem(MENU_FILE_EXPORT, "&Save as..."); - fileMenu.addItem(MENU_FILE_DELETE, "&Delete..."); - // TODO: Move to... - fileMenu.addSeparator(); - fileMenu.addItem(MENU_FILE_IMPORT_URL, "Import &URL..."); - fileMenu.addItem(MENU_FILE_IMPORT_FILE, "Import &file..."); - fileMenu.addSeparator(); - fileMenu.addItem(MENU_FILE_LIBRARY, "Lib&rary"); - fileMenu.addSeparator(); - fileMenu.addItem(MENU_FILE_EXIT, "E&xit"); - - TMenu OptionsMenu = addMenu("&Options"); - OptionsMenu.addItem(MENU_OPT_FANFIX, "&Fanfix Configuration"); - OptionsMenu.addItem(MENU_OPT_TUI, "&UI Configuration"); - - setStatusBar(fileMenu, "File-management " - + "commands (Open, Save, Print, etc.)"); - - - // TODO: Edit: re-download, delete - - // - - addWindowMenu(); - - getBackend().setTitle("Fanfix"); - } - - @Override - protected boolean onCommand(TCommandEvent command) { - if (command.getCmd().equals(TuiReaderMainWindow.CMD_SEARCH)) { - messageBox("title", "caption"); - return true; - } - return super.onCommand(command); - } - - @Override - protected boolean onMenu(TMenuEvent menu) { - // TODO: i18n - switch (menu.getId()) { - case MENU_FILE_EXIT: - close(this); - return true; - case MENU_FILE_OPEN: - String openfile = null; - try { - openfile = fileOpenBox("."); - reader.setMeta(BasicReader.getUrl(openfile), null); - read(false); - } catch (IOException e) { - // TODO: i18n - error("Fail to open file" - + (openfile == null ? "" : ": " + openfile), - "Import error", e); - } - - return true; - case MENU_FILE_DELETE: - String luid = null; - String story = null; - MetaData meta = null; - if (main != null) { - meta = main.getSelectedMeta(); - } - if (meta != null) { - luid = meta.getLuid(); - story = luid + ": " + meta.getTitle(); - } - - // TODO: i18n - TMessageBox mbox = messageBox("Delete story", "Delete story \"" - + story + "\"", Type.OKCANCEL); - if (mbox.getResult() == Result.OK) { - try { - reader.getLibrary().delete(luid); - if (main != null) { - main.refreshStories(); - } - } catch (IOException e) { - // TODO: i18n - error("Fail to delete the story: \"" + story + "\"", - "Error", e); - } - } - - return true; - case MENU_FILE_IMPORT_URL: - String clipboard = ""; - try { - clipboard = ("" + Toolkit.getDefaultToolkit() - .getSystemClipboard().getData(DataFlavor.stringFlavor)) - .trim(); - } catch (Exception e) { - // No data will be handled - } - - if (clipboard == null || !clipboard.startsWith("http")) { - clipboard = ""; - } - - String url = inputBox("Import story", "URL to import", clipboard) - .getText(); - - try { - if (!imprt(url)) { - // TODO: i18n - error("URK not supported: " + url, "Import error"); - } - } catch (IOException e) { - // TODO: i18n - error("Fail to import URL: " + url, "Import error", e); - } - - return true; - case MENU_FILE_IMPORT_FILE: - String filename = null; - try { - filename = fileOpenBox("."); - if (!imprt(filename)) { - // TODO: i18n - error("File not supported: " + filename, "Import error"); - } - } catch (IOException e) { - // TODO: i18n - error("Fail to import file" - + (filename == null ? "" : ": " + filename), - "Import error", e); - } - return true; - case MENU_FILE_LIBRARY: - showMain(); - return true; - - case MENU_OPT_FANFIX: - new TuiReaderOptionWindow(this, false).maximize(); - return true; - - case MENU_OPT_TUI: - new TuiReaderOptionWindow(this, true).maximize(); - return true; - - } - - return super.onMenu(menu); - } - - /** - * Import the given url. - *

    - * Can fail if the host is not supported. - * - * @param url - * - * @return TRUE in case of success, FALSE if the host is not supported - * - * @throws IOException - * in case of I/O error - */ - private boolean imprt(String url) throws IOException { - try { - reader.getLibrary().imprt(BasicReader.getUrl(url), null); - main.refreshStories(); - return true; - } catch (UnknownHostException e) { - return false; - } - } - - @Override - public void openExternal(BasicLibrary lib, String luid, boolean sync) - throws IOException { - reader.openExternal(lib, luid, sync); - } - - /** - * Display an error message and log it. - * - * @param message - * the message - * @param title - * the title of the error message - */ - private void error(String message, String title) { - error(message, title, null); - } - - /** - * Display an error message and log it, including the linked - * {@link Exception}. - * - * @param message - * the message - * @param title - * the title of the error message - * @param e - * the exception to log if any (can be NULL) - */ - private void error(String message, String title, Exception e) { - Instance.getInstance().getTraceHandler().error(title + ": " + message); - if (e != null) { - Instance.getInstance().getTraceHandler().error(e); - } - - if (e != null) { - messageBox(title, message // - + "\n" + e.getMessage()); - } else { - messageBox(title, message); - } - } - - /** - * Ask the user and, if confirmed, close the {@link TApplication} this - * {@link TWidget} is running on. - *

    - * This should result in the program terminating. - * - * @param widget - * the {@link TWidget} - */ - static public void close(TWidget widget) { - close(widget.getApplication()); - } - - /** - * Ask the user and, if confirmed, close the {@link TApplication}. - *

    - * This should result in the program terminating. - * - * @param app - * the {@link TApplication} - */ - static void close(TApplication app) { - // TODO: i18n - if (app.messageBox("Confirmation", "(TODO: i18n) Exit application?", - TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) { - app.exit(); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java deleted file mode 100644 index 2ee319a..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java +++ /dev/null @@ -1,374 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import jexer.TAction; -import jexer.TComboBox; -import jexer.TCommand; -import jexer.TField; -import jexer.TFileOpenBox.Type; -import jexer.TKeypress; -import jexer.TLabel; -import jexer.TList; -import jexer.TStatusBar; -import jexer.TWindow; -import jexer.event.TCommandEvent; -import jexer.event.TKeypressEvent; -import jexer.event.TMenuEvent; -import jexer.event.TResizeEvent; -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.jexer.TSizeConstraint; - -/** - * The library window, that will list all the (filtered) stories available in - * this {@link BasicLibrary}. - * - * @author niki - */ -class TuiReaderMainWindow extends TWindow { - public static final int MENU_SEARCH = 1100; - public static final TCommand CMD_SEARCH = new TCommand(MENU_SEARCH) { - }; - - public enum Mode { - SOURCE, AUTHOR, - } - - private TList list; - private List listKeys; - private List listItems; - private TuiReaderApplication reader; - - private Mode mode = Mode.SOURCE; - private String target = null; - private String filter = ""; - - private List sizeConstraints = new ArrayList(); - - // The 2 comboboxes used to select by source/author - private TComboBox selectTargetBox; - private TComboBox selectBox; - - /** - * Create a new {@link TuiReaderMainWindow} without any stories in the list. - * - * @param reader - * the reader and main application - */ - public TuiReaderMainWindow(TuiReaderApplication reader) { - // Construct a demo window. X and Y don't matter because it will be - // centred on screen. - super(reader, "Library", 0, 0, 60, 18, CENTERED | RESIZABLE); - - this.reader = reader; - - listKeys = new ArrayList(); - listItems = new ArrayList(); - - addList(); - addSearch(); - addSelect(); - - TStatusBar statusBar = reader.setStatusBar(this, "Library"); - statusBar.addShortcutKeypress(TKeypress.kbCtrlF, CMD_SEARCH, "Search"); - - TSizeConstraint.resize(sizeConstraints); - - // TODO: remove when not used anymore - - // addLabel("Label (1,1)", 1, 1); - // addButton("&Button (35,1)", 35, 1, new TAction() { - // public void DO() { - // } - // }); - // addCheckbox(1, 2, "Checky (1,2)", false); - // addProgressBar(1, 3, 30, 42); - // TRadioGroup groupy = addRadioGroup(1, 4, "Radio groupy"); - // groupy.addRadioButton("Fanfan"); - // groupy.addRadioButton("Tulipe"); - // addField(1, 10, 20, false, "text not fixed."); - // addField(1, 11, 20, true, "text fixed."); - // addText("20x4 Text in (12,20)", 1, 12, 20, 4); - // - // TTreeView tree = addTreeView(30, 5, 20, 5); - // TTreeItem root = new TTreeItem(tree, "expended root", true); - // tree.setSelected(root); // needed to allow arrow navigation without - // // mouse-clicking before - // - // root.addChild("child"); - // root.addChild("child 2").addChild("sub child"); - } - - private void addSearch() { - TLabel lblSearch = addLabel("Search: ", 0, 0); - - TField search = new TField(this, 0, 0, 1, true) { - @Override - public void onKeypress(TKeypressEvent keypress) { - super.onKeypress(keypress); - TKeypress key = keypress.getKey(); - if (key.isFnKey() && key.getKeyCode() == TKeypress.ENTER) { - TuiReaderMainWindow.this.filter = getText(); - TuiReaderMainWindow.this.refreshStories(); - } - } - }; - - TSizeConstraint.setSize(sizeConstraints, lblSearch, 5, 1, null, null); - TSizeConstraint.setSize(sizeConstraints, search, 15, 1, -5, null); - } - - private void addList() { - list = addList(listItems, 0, 0, 10, 10, new TAction() { - @Override - public void DO() { - MetaData meta = getSelectedMeta(); - if (meta != null) { - readStory(meta); - } - } - }); - - TSizeConstraint.setSize(sizeConstraints, list, 0, 7, 0, 0); - } - - private void addSelect() { - // TODO: i18n - final List selects = new ArrayList(); - selects.add("(show all)"); - selects.add("Sources"); - selects.add("Author"); - - final List selectTargets = new ArrayList(); - selectTargets.add(""); - - TLabel lblSelect = addLabel("Select: ", 0, 0); - - TAction onSelect = new TAction() { - @Override - public void DO() { - String smode = selectBox.getText(); - boolean showTarget; - if (smode == null || smode.equals("(show all)")) { - showTarget = false; - } else if (smode.equals("Sources")) { - selectTargets.clear(); - selectTargets.add("(show all)"); - try { - for (String source : reader.getLibrary().getSources()) { - selectTargets.add(source); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - showTarget = true; - } else { - selectTargets.clear(); - selectTargets.add("(show all)"); - try { - for (String author : reader.getLibrary().getAuthors()) { - selectTargets.add(author); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - showTarget = true; - } - - selectTargetBox.setVisible(showTarget); - selectTargetBox.setEnabled(showTarget); - if (showTarget) { - selectTargetBox.reflowData(); - } - - selectTargetBox.setText(selectTargets.get(0)); - if (showTarget) { - TuiReaderMainWindow.this.activate(selectTargetBox); - } else { - TuiReaderMainWindow.this.activate(list); - } - } - }; - - selectBox = addComboBox(0, 0, 10, selects, 0, -1, onSelect); - - selectTargetBox = addComboBox(0, 0, 0, selectTargets, 0, -1, - new TAction() { - @Override - public void DO() { - if (selectTargetBox.getText().equals( - selectTargets.get(0))) { - setMode(mode, null); - } else { - setMode(mode, selectTargetBox.getText()); - } - } - }); - - // Set defaults - onSelect.DO(); - - TSizeConstraint.setSize(sizeConstraints, lblSelect, 5, 3, null, null); - TSizeConstraint.setSize(sizeConstraints, selectBox, 15, 3, -5, null); - TSizeConstraint.setSize(sizeConstraints, selectTargetBox, 15, 4, -5, - null); - } - - @Override - public void onResize(TResizeEvent resize) { - super.onResize(resize); - TSizeConstraint.resize(sizeConstraints); - } - - @Override - public void onClose() { - setVisible(false); - super.onClose(); - } - - /** - * Refresh the list of stories displayed in this library. - *

    - * Will take the current settings into account (filter, source...). - */ - public void refreshStories() { - List metas; - - try { - if (mode == Mode.SOURCE) { - metas = reader.getLibrary().getList().filter(target, null, null); - } else if (mode == Mode.AUTHOR) { - metas = reader.getLibrary().getList().filter(null, target, null); - } else { - metas = reader.getLibrary().getList().getMetas(); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - metas = new ArrayList(); - } - - setMetas(metas); - } - - /** - * Change the author/source filter and display all stories matching this - * target. - * - * @param mode - * the new mode or NULL for no sorting - * @param target - * the actual target for the given mode, or NULL for all of them - */ - public void setMode(Mode mode, String target) { - this.mode = mode; - this.target = target; - refreshStories(); - } - - /** - * Update the list of stories displayed in this {@link TWindow}. - *

    - * If a filter is set, only the stories which pass the filter will be - * displayed. - * - * @param metas - * the new list of stories to display - */ - private void setMetas(List metas) { - listKeys.clear(); - listItems.clear(); - - if (metas != null) { - for (MetaData meta : metas) { - String desc = desc(meta); - if (filter.isEmpty() - || desc.toLowerCase().contains(filter.toLowerCase())) { - listKeys.add(meta); - listItems.add(desc); - } - } - } - - list.setList(listItems); - if (listItems.size() > 0) { - list.setSelectedIndex(0); - } - } - - public MetaData getSelectedMeta() { - if (list.getSelectedIndex() >= 0) { - return listKeys.get(list.getSelectedIndex()); - } - - return null; - } - - public void readStory(MetaData meta) { - try { - reader.setChapter(-1); - reader.setMeta(meta); - reader.read(false); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - private String desc(MetaData meta) { - return String.format("%5s: %s", meta.getLuid(), meta.getTitle()); - } - - @Override - public void onCommand(TCommandEvent command) { - if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) { - TuiReaderApplication.close(this); - } else { - // Handle our own event if needed here - super.onCommand(command); - } - } - - @Override - public void onMenu(TMenuEvent menu) { - MetaData meta = getSelectedMeta(); - if (meta != null) { - switch (menu.getId()) { - case TuiReaderApplication.MENU_FILE_OPEN: - readStory(meta); - - return; - case TuiReaderApplication.MENU_FILE_EXPORT: - - try { - // TODO: choose type, pg, error - OutputType outputType = OutputType.EPUB; - String path = fileOpenBox(".", Type.SAVE); - reader.getLibrary().export(meta.getLuid(), outputType, - path, null); - } catch (IOException e) { - // TODO - e.printStackTrace(); - } - - return; - - case -1: - try { - reader.getLibrary().delete(meta.getLuid()); - } catch (IOException e) { - // TODO - } - - return; - } - } - - super.onMenu(menu); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java deleted file mode 100644 index 451eef6..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java +++ /dev/null @@ -1,15 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import jexer.TStatusBar; -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.UiConfig; - -class TuiReaderOptionWindow extends TOptionWindow { - public TuiReaderOptionWindow(TuiReaderApplication reader, boolean uiOptions) { - super(reader, uiOptions ? UiConfig.class : Config.class, - uiOptions ? Instance.getInstance().getUiConfig() : Instance.getInstance().getConfig(), "Options"); - - TStatusBar statusBar = reader.setStatusBar(this, "Options"); - } -} diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java deleted file mode 100644 index 4848ef8..0000000 --- a/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java +++ /dev/null @@ -1,305 +0,0 @@ -package be.nikiroo.fanfix.reader.tui; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import jexer.TAction; -import jexer.TButton; -import jexer.TLabel; -import jexer.TText; -import jexer.TWindow; -import jexer.event.TCommandEvent; -import jexer.event.TResizeEvent; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.jexer.TSizeConstraint; -import be.nikiroo.jexer.TTable; - -/** - * This window will contain the {@link Story} in a readable format, with a - * chapter browser. - * - * @author niki - */ -class TuiReaderStoryWindow extends TWindow { - private Story story; - private TLabel titleField; - private TText textField; - private TTable table; - private int chapter = -99; // invalid value - private List navigationButtons; - private TLabel currentChapter; - private List sizeConstraints = new ArrayList(); - - // chapter: -1 for "none" (0 is desc) - public TuiReaderStoryWindow(TuiReaderApplication app, Story story, - int chapter) { - super(app, desc(story.getMeta()), 0, 0, 60, 18, CENTERED | RESIZABLE); - - this.story = story; - - app.setStatusBar(this, desc(story.getMeta())); - - // last = use window background - titleField = new TLabel(this, " Title", 0, 1, "tlabel", false); - textField = new TText(this, "", 0, 0, 1, 1); - table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key", - "Value"), true); - - titleField.setEnabled(false); - - navigationButtons = new ArrayList(5); - - navigationButtons.add(addButton("<<", 0, 0, new TAction() { - @Override - public void DO() { - setChapter(-1); - } - })); - navigationButtons.add(addButton("< ", 4, 0, new TAction() { - @Override - public void DO() { - setChapter(TuiReaderStoryWindow.this.chapter - 1); - } - })); - navigationButtons.add(addButton("> ", 7, 0, new TAction() { - @Override - public void DO() { - setChapter(TuiReaderStoryWindow.this.chapter + 1); - } - })); - navigationButtons.add(addButton(">>", 10, 0, new TAction() { - @Override - public void DO() { - setChapter(getStory().getChapters().size()); - } - })); - - navigationButtons.get(0).setEnabled(false); - navigationButtons.get(1).setEnabled(false); - - currentChapter = addLabel("", 0, 0); - - TSizeConstraint.setSize(sizeConstraints, textField, 1, 3, -1, -1); - TSizeConstraint.setSize(sizeConstraints, table, 0, 3, 0, -1); - TSizeConstraint.setSize(sizeConstraints, currentChapter, 14, -3, -1, - null); - - for (TButton navigationButton : navigationButtons) { - navigationButton.setShadowColor(null); - // navigationButton.setEmptyBorders(false); - TSizeConstraint.setSize(sizeConstraints, navigationButton, null, - -3, null, null); - } - - onResize(null); - - setChapter(chapter); - } - - @Override - public void onResize(TResizeEvent resize) { - if (resize != null) { - super.onResize(resize); - } - - // TODO: find out why TText and TTable does not behave the same way - // (offset of 2 for height and width) - - TSizeConstraint.resize(sizeConstraints); - - // Improve the disposition of the scrollbars - textField.getVerticalScroller().setX(textField.getWidth()); - textField.getVerticalScroller().setHeight(textField.getHeight()); - textField.getHorizontalScroller().setX(-1); - textField.getHorizontalScroller().setWidth(textField.getWidth() + 1); - - setCurrentChapterText(); - } - - /** - * Display the current chapter in the window, or the {@link Story} info - * page. - * - * @param chapter - * the chapter (including "0" which is the description) or "-1" - * to display the info page instead - */ - private void setChapter(int chapter) { - if (chapter > getStory().getChapters().size()) { - chapter = getStory().getChapters().size(); - } - - if (chapter != this.chapter) { - this.chapter = chapter; - - int max = getStory().getChapters().size(); - navigationButtons.get(0).setEnabled(chapter > -1); - navigationButtons.get(1).setEnabled(chapter > -1); - navigationButtons.get(2).setEnabled(chapter < max); - navigationButtons.get(3).setEnabled(chapter < max); - - if (chapter < 0) { - displayInfoPage(); - } else { - displayChapterPage(); - } - } - - setCurrentChapterText(); - } - - /** - * Append the info page about the current {@link Story}. - * - * @param builder - * the builder to append to - */ - private void displayInfoPage() { - textField.setVisible(false); - table.setVisible(true); - textField.setEnabled(false); - table.setEnabled(true); - - MetaData meta = getStory().getMeta(); - - setCurrentTitle(meta.getTitle()); - - Map metaDesc = BasicReader.getMetaDesc(meta); - String[][] metaDescObj = new String[metaDesc.size()][2]; - int i = 0; - for (String key : metaDesc.keySet()) { - metaDescObj[i][0] = " " + key; - metaDescObj[i][1] = metaDesc.get(key); - i++; - } - - table.setRowData(metaDescObj); - table.setHeaders(Arrays.asList("key", "value"), false); - table.toTop(); - } - - /** - * Append the current chapter. - * - * @param builder - * the builder to append to - */ - private void displayChapterPage() { - table.setVisible(false); - textField.setVisible(true); - table.setEnabled(false); - textField.setEnabled(true); - - StringBuilder builder = new StringBuilder(); - - Chapter chap = null; - if (chapter == 0) { - chap = getStory().getMeta().getResume(); - } else if (chapter > 0) { - chap = getStory().getChapters().get(chapter - 1); - } - - // TODO: i18n - String chapName = chap == null ? "[No RESUME]" : chap.getName(); - setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName)); - - if (chap != null) { - for (Paragraph para : chap) { - if (para.getType() == ParagraphType.BREAK) { - builder.append("\n"); - } - builder.append(para.getContent()).append("\n"); - if (para.getType() == ParagraphType.BREAK) { - builder.append("\n"); - } - } - } - - setText(builder.toString()); - } - - private Story getStory() { - return story; - } - - /** - * Display the given text on the window. - * - * @param text - * the text to display - */ - private void setText(String text) { - textField.setText(text); - textField.reflowData(); - textField.toTop(); - } - - /** - * Set the current chapter area to the correct value. - */ - private void setCurrentChapterText() { - String name; - if (chapter < 0) { - name = " " + getStory().getMeta().getTitle(); - } else if (chapter == 0) { - Chapter resume = getStory().getMeta().getResume(); - if (resume != null) { - name = String.format(" %s", resume.getName()); - } else { - // TODO: i18n - name = "[No RESUME]"; - } - } else { - int max = getStory().getChapters().size(); - Chapter chap = getStory().getChapters().get(chapter - 1); - name = String.format(" %d/%d: %s", chapter, max, chap.getName()); - } - - int width = getWidth() - currentChapter.getX(); - name = String.format("%-" + width + "s", name); - if (name.length() > width) { - name = name.substring(0, width); - } - - currentChapter.setLabel(name); - } - - /** - * Set the current title in-window. - * - * @param title - * the new title - */ - private void setCurrentTitle(String title) { - String pad = ""; - if (title.length() < getWidth()) { - int padSize = (getWidth() - title.length()) / 2; - pad = String.format("%" + padSize + "s", ""); - } - - title = pad + title + pad; - titleField.setWidth(title.length()); - titleField.setLabel(title); - } - - private static String desc(MetaData meta) { - return String.format("%s: %s", meta.getLuid(), meta.getTitle()); - } - - @Override - public void onCommand(TCommandEvent command) { - if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) { - TuiReaderApplication.close(this); - } else { - // Handle our own event if needed here - super.onCommand(command); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReader.java b/src/be/nikiroo/fanfix/reader/ui/GuiReader.java deleted file mode 100644 index a02cc84..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReader.java +++ /dev/null @@ -1,499 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.Desktop; -import java.awt.EventQueue; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; - -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.VersionCheck; -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.CacheLibrary; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.Reader; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.searchable.SearchableTag; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.ui.UIUtils; - -/** - * This class implements a graphical {@link Reader} using the Swing library from - * Java. - *

    - * It can thus be themed to look native-like, or metal-like, or use any other - * theme you may want to try. - *

    - * We actually try to enable native look-alike mode on start. - * - * @author niki - */ -class GuiReader extends BasicReader { - static private boolean nativeLookLoaded; - - private CacheLibrary cacheLib; - - private File cacheDir; - - /** - * Create a new graphical {@link Reader}. - * - * @throws IOException - * in case of I/O errors - */ - public GuiReader() throws IOException { - // TODO: allow different themes? - if (!nativeLookLoaded) { - UIUtils.setLookAndFeel(); - nativeLookLoaded = true; - } - - cacheDir = Instance.getInstance().getReaderDir(); - cacheDir.mkdirs(); - if (!cacheDir.exists()) { - throw new IOException( - "Cannote create cache directory for local reader: " - + cacheDir); - } - } - - @Override - public synchronized BasicLibrary getLibrary() { - if (cacheLib == null) { - BasicLibrary lib = super.getLibrary(); - if (lib instanceof CacheLibrary) { - cacheLib = (CacheLibrary) lib; - } else { - cacheLib = new CacheLibrary(cacheDir, lib, Instance.getInstance().getUiConfig()); - } - } - - return cacheLib; - } - - @Override - public void read(boolean sync) throws IOException { - MetaData meta = getMeta(); - - if (meta == null) { - throw new IOException("No story to read"); - } - - read(meta.getLuid(), sync, null); - } - - /** - * Check if the {@link Story} denoted by this Library UID is present in the - * {@link GuiReader} cache. - * - * @param luid - * the Library UID - * - * @return TRUE if it is - */ - public boolean isCached(String luid) { - return cacheLib.isCached(luid); - } - - @Override - public void browse(String type) { - final Boolean[] done = new Boolean[] { false }; - - // TODO: improve presentation of update message - final VersionCheck updates = VersionCheck.check(); - StringBuilder builder = new StringBuilder(); - - final JEditorPane updateMessage = new JEditorPane("text/html", ""); - if (updates.isNewVersionAvailable()) { - builder.append(trans(StringIdGui.NEW_VERSION_AVAILABLE, - "https://github.com/nikiroo/fanfix/releases")); - builder.append("
    "); - builder.append("
    "); - for (Version v : updates.getNewer()) { - builder.append("\t" - + trans(StringIdGui.NEW_VERSION_VERSION, v.toString()) - + ""); - builder.append("
    "); - builder.append("

    "); - } - - // html content - updateMessage.setText("" // - + builder// - + ""); - - // handle link events - updateMessage.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - if (e.getEventType().equals( - HyperlinkEvent.EventType.ACTIVATED)) - try { - Desktop.getDesktop().browse(e.getURL().toURI()); - } catch (IOException ee) { - Instance.getInstance().getTraceHandler().error(ee); - } catch (URISyntaxException ee) { - Instance.getInstance().getTraceHandler().error(ee); - } - } - }); - updateMessage.setEditable(false); - updateMessage.setBackground(new JLabel().getBackground()); - } - - final String typeFinal = type; - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - if (updates.isNewVersionAvailable()) { - int rep = JOptionPane.showConfirmDialog(null, - updateMessage, - trans(StringIdGui.NEW_VERSION_TITLE), - JOptionPane.OK_CANCEL_OPTION); - if (rep == JOptionPane.OK_OPTION) { - updates.ok(); - } else { - updates.ignore(); - } - } - - new Thread(new Runnable() { - @Override - public void run() { - try { - GuiReaderFrame gui = new GuiReaderFrame( - GuiReader.this, typeFinal); - sync(gui); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } finally { - done[0] = true; - } - - } - }).start(); - } - }); - - // This action must be synchronous, so wait until the frame is closed - while (!done[0]) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - } - - @Override - public void start(File target, String program, boolean sync) - throws IOException { - - boolean handled = false; - if (program == null && !sync) { - try { - Desktop.getDesktop().browse(target.toURI()); - handled = true; - } catch (UnsupportedOperationException e) { - } - } - - if (!handled) { - super.start(target, program, sync); - } - } - - @Override - public void search(boolean sync) throws IOException { - GuiReaderSearchFrame search = new GuiReaderSearchFrame(this); - if (sync) { - sync(search); - } else { - search.setVisible(true); - } - } - - @Override - public void search(SupportType searchOn, String keywords, int page, - int item, boolean sync) { - GuiReaderSearchFrame search = new GuiReaderSearchFrame(this); - search.search(searchOn, keywords, page, item); - if (sync) { - sync(search); - } else { - search.setVisible(true); - } - } - - @Override - public void searchTag(final SupportType searchOn, final int page, - final int item, final boolean sync, final Integer... tags) { - - final GuiReaderSearchFrame search = new GuiReaderSearchFrame(this); - - final BasicSearchable searchable = BasicSearchable - .getSearchable(searchOn); - - Runnable action = new Runnable() { - @Override - public void run() { - SearchableTag tag = null; - try { - tag = searchable.getTag(tags); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - search.searchTag(searchOn, page, item, tag); - - if (sync) { - sync(search); - } else { - search.setVisible(true); - } - } - }; - - if (sync) { - action.run(); - } else { - new Thread(action).start(); - } - } - - /** - * Delete the {@link Story} from the cache if it is present, but NOT - * from the main library. - *

    - * The next time we try to retrieve the {@link Story}, it may be required to - * cache it again. - * - * @param luid - * the luid of the {@link Story} - */ - void clearLocalReaderCache(String luid) { - try { - cacheLib.clearFromCache(luid); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * Forward the delete operation to the main library. - *

    - * The {@link Story} will be deleted from the main library as well as the - * cache if present. - * - * @param luid - * the {@link Story} to delete - */ - void delete(String luid) { - try { - cacheLib.delete(luid); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * "Open" the given {@link Story}. It usually involves starting an external - * program adapted to the given file type. - * - * @param luid - * the luid of the {@link Story} to open - * @param sync - * execute the process synchronously (wait until it is terminated - * before returning) - * @param pg - * the optional progress (we may need to prepare the - * {@link Story} for reading - * - * @throws IOException - * in case of I/O errors - */ - void read(String luid, boolean sync, Progress pg) throws IOException { - MetaData meta = cacheLib.getInfo(luid); - - boolean textInternal = Instance.getInstance().getUiConfig() - .getBoolean(UiConfig.NON_IMAGES_DOCUMENT_USE_INTERNAL_READER, true); - boolean imageInternal = Instance.getInstance().getUiConfig() - .getBoolean(UiConfig.IMAGES_DOCUMENT_USE_INTERNAL_READER, true); - - boolean useInternalViewer = true; - if (meta.isImageDocument() && !imageInternal) { - useInternalViewer = false; - } - if (!meta.isImageDocument() && !textInternal) { - useInternalViewer = false; - } - - if (useInternalViewer) { - GuiReaderViewer viewer = new GuiReaderViewer(cacheLib, - cacheLib.getStory(luid, null)); - if (sync) { - sync(viewer); - } else { - viewer.setVisible(true); - } - } else { - File file = cacheLib.getFile(luid, pg); - openExternal(meta, file, sync); - } - } - - - /** - * "Prefetch" the given {@link Story}. - *

    - * Synchronous method. - * - * @param luid - * the luid of the {@link Story} to prefetch - * @param pg - * the optional progress (we may need to prepare the - * {@link Story} for reading - * - * @throws IOException - * in case of I/O errors - */ - void prefetch(String luid, Progress pg) throws IOException { - cacheLib.getFile(luid, pg); - } - /** - * Change the source of the given {@link Story} (the source is the main - * information used to group the stories together). - *

    - * In other words, move the {@link Story} into other source. - *

    - * The source can be a new one, it needs not exist before hand. - * - * @param luid - * the luid of the {@link Story} to move - * @param newSource - * the new source - */ - void changeSource(String luid, String newSource) { - try { - cacheLib.changeSource(luid, newSource, null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * Change the title of the given {@link Story}. - * - * @param luid - * the luid of the {@link Story} to change - * @param newTitle - * the new title - */ - void changeTitle(String luid, String newTitle) { - try { - cacheLib.changeTitle(luid, newTitle, null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * Change the author of the given {@link Story}. - *

    - * The author can be a new one, it needs not exist before hand. - * - * @param luid - * the luid of the {@link Story} to change - * @param newAuthor - * the new author - */ - void changeAuthor(String luid, String newAuthor) { - try { - cacheLib.changeAuthor(luid, newAuthor, null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - /** - * Simple shortcut method to call {link Instance#getTransGui()#getString()}. - * - * @param id - * the ID to translate - * - * @return the translated result - */ - static String trans(StringIdGui id, Object... params) { - return Instance.getInstance().getTransGui().getString(id, params); - } - - /** - * Start a frame and wait until it is closed before returning. - * - * @param frame - * the frame to start - */ - static private void sync(final JFrame frame) { - if (EventQueue.isDispatchThread()) { - throw new IllegalStateException( - "Cannot call a sync method in the dispatch thread"); - } - - final Boolean[] done = new Boolean[] { false }; - try { - Runnable run = new Runnable() { - @Override - public void run() { - try { - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - super.windowClosing(e); - done[0] = true; - } - }); - - frame.setVisible(true); - } catch (Exception e) { - done[0] = true; - } - } - }; - - if (EventQueue.isDispatchThread()) { - run.run(); - } else { - EventQueue.invokeLater(run); - } - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - done[0] = true; - } - - // This action must be synchronous, so wait until the frame is closed - while (!done[0]) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java deleted file mode 100644 index 73ccdaa..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java +++ /dev/null @@ -1,339 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Graphics; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.Date; -import java.util.EventListener; -import java.util.List; - -import javax.swing.JLabel; -import javax.swing.JPanel; - -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.reader.Reader; - -/** - * A book item presented in a {@link GuiReaderFrame}. - *

    - * Can be a story, or a comic or... a group. - * - * @author niki - */ -class GuiReaderBook extends JPanel { - /** - * Action on a book item. - * - * @author niki - */ - interface BookActionListener extends EventListener { - /** - * The book was selected (single click). - * - * @param book - * the {@link GuiReaderBook} itself - */ - public void select(GuiReaderBook book); - - /** - * The book was double-clicked. - * - * @param book - * the {@link GuiReaderBook} itself - */ - public void action(GuiReaderBook book); - - /** - * A popup menu was requested for this {@link GuiReaderBook}. - * - * @param book - * the {@link GuiReaderBook} itself - * @param target - * the target component for the popup - * @param x - * the X position of the click/request (in case of popup - * request from the keyboard, the center of the target is - * selected as point of reference) - * @param y - * the Y position of the click/request (in case of popup - * request from the keyboard, the center of the target is - * selected as point of reference) - */ - public void popupRequested(GuiReaderBook book, Component target, int x, - int y); - } - - private static final long serialVersionUID = 1L; - - private static final String AUTHOR_COLOR = "#888888"; - private static final long doubleClickDelay = 200; // in ms - - private JLabel icon; - private JLabel title; - private boolean selected; - private boolean hovered; - private Date lastClick; - - private List listeners; - private GuiReaderBookInfo info; - private boolean cached; - private boolean seeWordCount; - - /** - * Create a new {@link GuiReaderBook} item for the given {@link Story}. - * - * @param reader - * the associated reader - * @param info - * the information about the story to represent - * @param cached - * TRUE if it is locally cached - * @param seeWordCount - * TRUE to see word counts, FALSE to see authors - */ - public GuiReaderBook(Reader reader, GuiReaderBookInfo info, boolean cached, - boolean seeWordCount) { - this.info = info; - this.cached = cached; - this.seeWordCount = seeWordCount; - - icon = new JLabel(GuiReaderCoverImager.generateCoverIcon( - reader.getLibrary(), info)); - - title = new JLabel(); - updateTitle(); - - setLayout(new BorderLayout(10, 10)); - add(icon, BorderLayout.CENTER); - add(title, BorderLayout.SOUTH); - - setupListeners(); - } - - /** - * The book current selection state. - * - * @return the selection state - */ - public boolean isSelected() { - return selected; - } - - /** - * The book current selection state. - *

    - * Setting this value to true can cause a "select" action to occur if the - * previous state was "unselected". - * - * @param selected - * TRUE if it is selected - */ - public void setSelected(boolean selected) { - if (this.selected != selected) { - this.selected = selected; - repaint(); - - if (selected) { - select(); - } - } - } - - /** - * The item mouse-hover state. - * - * @return TRUE if it is mouse-hovered - */ - public boolean isHovered() { - return this.hovered; - } - - /** - * The item mouse-hover state. - * - * @param hovered - * TRUE if it is mouse-hovered - */ - public void setHovered(boolean hovered) { - if (this.hovered != hovered) { - this.hovered = hovered; - repaint(); - } - } - - /** - * Setup the mouse listener that will activate {@link BookActionListener} - * events. - */ - private void setupListeners() { - listeners = new ArrayList(); - addMouseListener(new MouseListener() { - @Override - public void mouseReleased(MouseEvent e) { - if (isEnabled() && e.isPopupTrigger()) { - popup(e); - } - } - - @Override - public void mousePressed(MouseEvent e) { - if (isEnabled() && e.isPopupTrigger()) { - popup(e); - } - } - - @Override - public void mouseExited(MouseEvent e) { - setHovered(false); - } - - @Override - public void mouseEntered(MouseEvent e) { - setHovered(true); - } - - @Override - public void mouseClicked(MouseEvent e) { - if (isEnabled()) { - Date now = new Date(); - if (lastClick != null - && now.getTime() - lastClick.getTime() < doubleClickDelay) { - click(true); - } else { - click(false); - } - - lastClick = now; - e.consume(); - } - } - - private void click(boolean doubleClick) { - if (doubleClick) { - action(); - } else { - select(); - } - } - - private void popup(MouseEvent e) { - GuiReaderBook.this - .popup(GuiReaderBook.this, e.getX(), e.getY()); - e.consume(); - } - }); - } - - /** - * Add a new {@link BookActionListener} on this item. - * - * @param listener - * the listener - */ - public void addActionListener(BookActionListener listener) { - listeners.add(listener); - } - - /** - * Cause an action to occur on this {@link GuiReaderBook}. - */ - public void action() { - for (BookActionListener listener : listeners) { - listener.action(GuiReaderBook.this); - } - } - - /** - * Cause a select event on this {@link GuiReaderBook}. - *

    - * Have a look at {@link GuiReaderBook#setSelected(boolean)}. - */ - private void select() { - for (BookActionListener listener : listeners) { - listener.select(GuiReaderBook.this); - } - } - - /** - * Request a popup. - * - * @param target - * the target component for the popup - * @param x - * the X position of the click/request (in case of popup request - * from the keyboard, the center of the target should be selected - * as point of reference) - * @param y - * the Y position of the click/request (in case of popup request - * from the keyboard, the center of the target should be selected - * as point of reference) - */ - public void popup(Component target, int x, int y) { - for (BookActionListener listener : listeners) { - listener.select((GuiReaderBook.this)); - listener.popupRequested(GuiReaderBook.this, target, x, y); - } - } - - /** - * The information about the book represented by this item. - * - * @return the meta - */ - public GuiReaderBookInfo getInfo() { - return info; - } - - /** - * This item {@link GuiReader} library cache state. - * - * @return TRUE if it is present in the {@link GuiReader} cache - */ - public boolean isCached() { - return cached; - } - - /** - * This item {@link GuiReader} library cache state. - * - * @param cached - * TRUE if it is present in the {@link GuiReader} cache - */ - public void setCached(boolean cached) { - if (this.cached != cached) { - this.cached = cached; - repaint(); - } - } - - /** - * Update the title, paint the item, then call - * {@link GuiReaderCoverImager#paintOverlay(Graphics, boolean, boolean, boolean, boolean)} - * . - */ - @Override - public void paint(Graphics g) { - updateTitle(); - super.paint(g); - GuiReaderCoverImager.paintOverlay(g, isEnabled(), isSelected(), - isHovered(), isCached()); - } - - /** - * Update the title with the currently registered information. - */ - private void updateTitle() { - String optSecondary = info.getSecondaryInfo(seeWordCount); - title.setText(String - .format("" - + "" - + "%s" + "
    " + "" + "%s" - + "" + "" + "", - GuiReaderCoverImager.TEXT_WIDTH, - GuiReaderCoverImager.TEXT_HEIGHT, info.getMainInfo(), - AUTHOR_COLOR, optSecondary)); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java deleted file mode 100644 index 3cef8cf..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java +++ /dev/null @@ -1,258 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.io.IOException; - -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; - -/** - * Some meta information related to a "book" (which can either be a - * {@link Story}, a fake-story grouping some authors or a fake-story grouping - * some sources/types). - * - * @author niki - */ -public class GuiReaderBookInfo { - /** - * The type of {@link GuiReaderBook} (i.e., related to a story or to something else that - * can encompass stories). - * - * @author niki - */ - public enum Type { - /** A normal story, which can be "read". */ - STORY, - /** - * A special, empty story that represents a source/type common to one or - * more normal stories. - */ - SOURCE, - /** A special, empty story that represents an author. */ - AUTHOR - } - - private Type type; - private String id; - private String value; - private String count; - - private MetaData meta; - - /** - * For private use, see the "fromXXX" constructors instead for public use. - * - * @param type - * the type of book - * @param id - * the main id, which must uniquely identify this book and will - * be used as a unique ID later on - * @param value - * the main value to show (see - * {@link GuiReaderBookInfo#getMainInfo()}) - */ - private GuiReaderBookInfo(Type type, String id, String value) { - this.type = type; - this.id = id; - this.value = value; - } - - /** - * The type of {@link GuiReaderBookInfo}. - * - * @return the type - */ - public Type getType() { - return type; - } - - /** - * Get the main info to display for this book (a title, an author, a - * source/type name...). - *

    - * Note that when {@link MetaData} about the book are present, the title - * inside is returned instead of the actual value (that way, we can update - * the {@link MetaData} and see the changes here). - * - * @return the main info, usually the title - */ - public String getMainInfo() { - if (meta != null) { - return meta.getTitle(); - } - - return value; - } - - /** - * Get the secondary info, of the given type. - * - * @param seeCount - * TRUE for word/image/story count, FALSE for author name - * - * @return the secondary info - */ - public String getSecondaryInfo(boolean seeCount) { - String author = meta == null ? null : meta.getAuthor(); - String secondaryInfo = seeCount ? count : author; - - if (secondaryInfo != null && !secondaryInfo.trim().isEmpty()) { - secondaryInfo = "(" + secondaryInfo + ")"; - } else { - secondaryInfo = ""; - } - - return secondaryInfo; - } - - /** - * A unique ID for this {@link GuiReaderBookInfo}. - * - * @return the unique ID - */ - public String getId() { - return id; - } - - /** - * The {@link MetaData} associated with this book, if this book is a - * {@link Story}. - *

    - * Can be NULL for non-story books (authors or sources/types). - * - * @return the {@link MetaData} or NULL - */ - public MetaData getMeta() { - return meta; - } - - /** - * Get the base image to use to represent this book. - *

    - * The image is NOT resized in any way, this is the original version. - *

    - * It can be NULL if no image can be found for this book. - * - * @param lib - * the {@link BasicLibrary} to use to fetch the image - * - * @return the base image - * - * @throws IOException - * in case of I/O error - */ - public Image getBaseImage(BasicLibrary lib) throws IOException { - switch (type) { - case STORY: - if (meta.getCover() != null) { - return meta.getCover(); - } - - if (meta.getLuid() != null) { - return lib.getCover(meta.getLuid()); - } - - return null; - case SOURCE: - return lib.getSourceCover(value); - case AUTHOR: - return lib.getAuthorCover(value); - } - - return null; - } - - /** - * Create a new book describing the given {@link Story}. - * - * @param meta - * the {@link MetaData} representing the {@link Story} - * - * @return the book - */ - static public GuiReaderBookInfo fromMeta(MetaData meta) { - String uid = meta.getUuid(); - if (uid == null || uid.trim().isEmpty()) { - uid = meta.getLuid(); - } - if (uid == null || uid.trim().isEmpty()) { - uid = meta.getUrl(); - } - - GuiReaderBookInfo info = new GuiReaderBookInfo(Type.STORY, uid, - meta.getTitle()); - - info.meta = meta; - info.count = StringUtils.formatNumber(meta.getWords()); - if (!info.count.isEmpty()) { - info.count = GuiReader.trans( - meta.isImageDocument() ? StringIdGui.BOOK_COUNT_IMAGES - : StringIdGui.BOOK_COUNT_WORDS, info.count); - } - - return info; - } - - /** - * Create a new book describing the given source/type. - * - * @param lib - * the {@link BasicLibrary} to use to retrieve some more - * information about the source - * @param source - * the source name - * - * @return the book - */ - static public GuiReaderBookInfo fromSource(BasicLibrary lib, String source) { - GuiReaderBookInfo info = new GuiReaderBookInfo(Type.SOURCE, "source_" - + source, source); - - int size = 0; - try { - size = lib.getList().filter(source, null, null).size(); - } catch (IOException e) { - } - - info.count = StringUtils.formatNumber(size); - if (!info.count.isEmpty()) { - info.count = GuiReader.trans(StringIdGui.BOOK_COUNT_STORIES, - info.count); - } - - return info; - } - - /** - * Create a new book describing the given author. - * - * @param lib - * the {@link BasicLibrary} to use to retrieve some more - * information about the author - * @param author - * the author name - * - * @return the book - */ - static public GuiReaderBookInfo fromAuthor(BasicLibrary lib, String author) { - GuiReaderBookInfo info = new GuiReaderBookInfo(Type.AUTHOR, "author_" - + author, author); - - int size = 0; - try { - size = lib.getList().filter(null, author, null).size(); - } catch (IOException e) { - } - - info.count = StringUtils.formatNumber(size); - if (!info.count.isEmpty()) { - info.count = GuiReader.trans(StringIdGui.BOOK_COUNT_STORIES, - info.count); - } - - return info; - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java deleted file mode 100644 index 8d5aaeb..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java +++ /dev/null @@ -1,244 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; - -import javax.imageio.ImageIO; -import javax.swing.ImageIcon; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ui.ImageUtilsAwt; -import be.nikiroo.utils.ui.UIUtils; - -/** - * This class can create a cover icon ready to use for the graphical - * application. - * - * @author niki - */ -class GuiReaderCoverImager { - - // TODO: export some of the configuration options? - private static final int COVER_WIDTH = 100; - private static final int COVER_HEIGHT = 150; - private static final int SPINE_WIDTH = 5; - private static final int SPINE_HEIGHT = 5; - private static final int HOFFSET = 20; - private static final Color SPINE_COLOR_BOTTOM = new Color(180, 180, 180); - private static final Color SPINE_COLOR_RIGHT = new Color(100, 100, 100); - private static final Color BORDER = Color.black; - - public static final int TEXT_HEIGHT = 50; - public static final int TEXT_WIDTH = COVER_WIDTH + 40; - - // - - /** - * Draw a partially transparent overlay if needed depending upon the - * selection and mouse-hover states on top of the normal component, as well - * as a possible "cached" icon if the item is cached. - * - * @param g - * the {@link Graphics} to paint onto - * @param enabled - * draw an enabled overlay - * @param selected - * draw a selected overlay - * @param hovered - * draw a hovered overlay - * @param cached - * draw a cached overlay - */ - static public void paintOverlay(Graphics g, boolean enabled, - boolean selected, boolean hovered, boolean cached) { - Rectangle clip = g.getClipBounds(); - if (clip.getWidth() <= 0 || clip.getHeight() <= 0) { - return; - } - - int h = COVER_HEIGHT; - int w = COVER_WIDTH; - int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 1; - int yOffset = HOFFSET; - - if (BORDER != null) { - if (BORDER != null) { - g.setColor(BORDER); - g.drawRect(xOffset, yOffset, COVER_WIDTH, COVER_HEIGHT); - } - - xOffset++; - yOffset++; - } - - int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH, - xOffset + w + SPINE_WIDTH, xOffset + w }; - int[] ys = new int[] { yOffset + h, yOffset + h + SPINE_HEIGHT, - yOffset + h + SPINE_HEIGHT, yOffset + h }; - g.setColor(SPINE_COLOR_BOTTOM); - g.fillPolygon(new Polygon(xs, ys, xs.length)); - xs = new int[] { xOffset + w, xOffset + w + SPINE_WIDTH, - xOffset + w + SPINE_WIDTH, xOffset + w }; - ys = new int[] { yOffset, yOffset + SPINE_HEIGHT, - yOffset + h + SPINE_HEIGHT, yOffset + h }; - g.setColor(SPINE_COLOR_RIGHT); - g.fillPolygon(new Polygon(xs, ys, xs.length)); - - Color color = new Color(255, 255, 255, 0); - if (!enabled) { - } else if (selected && !hovered) { - color = new Color(80, 80, 100, 40); - } else if (!selected && hovered) { - color = new Color(230, 230, 255, 100); - } else if (selected && hovered) { - color = new Color(200, 200, 255, 100); - } - - g.setColor(color); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - - if (cached) { - UIUtils.drawEllipse3D(g, Color.green.darker(), COVER_WIDTH - + HOFFSET + 30, 10, 20, 20); - } - } - - /** - * Generate a cover icon based upon the given {@link MetaData}. - * - * @param lib - * the library the meta comes from - * @param meta - * the {@link MetaData} - * - * @return the icon - */ - static public ImageIcon generateCoverIcon(BasicLibrary lib, MetaData meta) { - return generateCoverIcon(lib, GuiReaderBookInfo.fromMeta(meta)); - } - - /** - * The width of a cover image. - * - * @return the width - */ - static public int getCoverWidth() { - return SPINE_WIDTH + COVER_WIDTH; - } - - /** - * The height of a cover image. - * - * @return the height - */ - static public int getCoverHeight() { - return COVER_HEIGHT + HOFFSET; - } - - /** - * Generate a cover icon based upon the given {@link GuiReaderBookInfo}. - * - * @param lib - * the library the meta comes from - * @param info - * the {@link GuiReaderBookInfo} - * - * @return the icon - */ - static public ImageIcon generateCoverIcon(BasicLibrary lib, - GuiReaderBookInfo info) { - BufferedImage resizedImage = null; - String id = getIconId(info); - - InputStream in = Instance.getInstance().getCache().getFromCache(id); - if (in != null) { - try { - resizedImage = ImageUtilsAwt.fromImage(new Image(in)); - in.close(); - in = null; - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - if (resizedImage == null) { - try { - Image cover = info.getBaseImage(lib); - resizedImage = new BufferedImage(getCoverWidth(), - getCoverHeight(), BufferedImage.TYPE_4BYTE_ABGR); - - Graphics2D g = resizedImage.createGraphics(); - try { - g.setColor(Color.white); - g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT); - - if (cover != null) { - BufferedImage coverb = ImageUtilsAwt.fromImage(cover); - g.drawImage(coverb, 0, HOFFSET, COVER_WIDTH, - COVER_HEIGHT, null); - } else { - g.setColor(Color.black); - g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET - + COVER_HEIGHT); - g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET - + COVER_HEIGHT); - } - } finally { - g.dispose(); - } - - if (id != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ImageIO.write(resizedImage, "png", out); - byte[] imageBytes = out.toByteArray(); - in = new ByteArrayInputStream(imageBytes); - Instance.getInstance().getCache().addToCache(in, id); - in.close(); - in = null; - } - } catch (MalformedURLException e) { - Instance.getInstance().getTraceHandler().error(e); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - - return new ImageIcon(resizedImage); - } - - /** - * Manually clear the icon set for this item. - * - * @param info - * the info about the story or source/type or author - */ - static public void clearIcon(GuiReaderBookInfo info) { - String id = getIconId(info); - Instance.getInstance().getCache().removeFromCache(id); - } - - /** - * Get a unique ID from this {@link GuiReaderBookInfo} (note that it can be - * a story, a fake item for a source/type or a fake item for an author). - * - * @param info - * the info - * @return the unique ID - */ - static private String getIconId(GuiReaderBookInfo info) { - return info.getId() + ".thumb_" + SPINE_WIDTH + "x" + COVER_WIDTH + "+" - + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@" + HOFFSET; - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java deleted file mode 100644 index e0ef6d7..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java +++ /dev/null @@ -1,1015 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Frame; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPopupMenu; -import javax.swing.SwingUtilities; -import javax.swing.filechooser.FileFilter; -import javax.swing.filechooser.FileNameExtensionFilter; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.BasicLibrary.Status; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.output.BasicOutput.OutputType; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper; -import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.MetaDataRunnable; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.supported.SupportType; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.ui.ConfigEditor; - -/** - * A {@link Frame} that will show a {@link GuiReaderBook} item for each - * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a - * way to copy them to the {@link GuiReader} cache ( - * {@link BasicReader#getLibrary()}), read them, delete them... - * - * @author niki - */ -class GuiReaderFrame extends JFrame implements FrameHelper { - private static final long serialVersionUID = 1L; - private GuiReader reader; - private GuiReaderMainPanel mainPanel; - - /** - * The different modification actions you can use on {@link Story} items. - * - * @author niki - */ - private enum ChangeAction { - /** Change the source/type, that is, move it to another source. */ - SOURCE, - /** Change its name. */ - TITLE, - /** Change its author. */ - AUTHOR - } - - /** - * Create a new {@link GuiReaderFrame}. - * - * @param reader - * the associated {@link GuiReader} to forward some commands and - * access its {@link LocalLibrary} - * @param type - * the type of {@link Story} to load, or NULL for all types - */ - public GuiReaderFrame(GuiReader reader, String type) { - super(getAppTitle(reader.getLibrary().getLibraryName())); - - this.reader = reader; - - mainPanel = new GuiReaderMainPanel(this, type); - - setSize(800, 600); - setLayout(new BorderLayout()); - add(mainPanel, BorderLayout.CENTER); - } - - @Override - public JPopupMenu createBookPopup() { - Status status = reader.getLibrary().getStatus(); - JPopupMenu popup = new JPopupMenu(); - popup.add(createMenuItemOpenBook()); - popup.addSeparator(); - popup.add(createMenuItemExport()); - if (status.isWritable()) { - popup.add(createMenuItemMoveTo()); - popup.add(createMenuItemSetCoverForSource()); - popup.add(createMenuItemSetCoverForAuthor()); - } - popup.add(createMenuItemDownloadToCache()); - popup.add(createMenuItemClearCache()); - if (status.isWritable()) { - popup.add(createMenuItemRedownload()); - popup.addSeparator(); - popup.add(createMenuItemRename()); - popup.add(createMenuItemSetAuthor()); - popup.addSeparator(); - popup.add(createMenuItemDelete()); - } - popup.addSeparator(); - popup.add(createMenuItemProperties()); - return popup; - } - - @Override - public JPopupMenu createSourceAuthorPopup() { - JPopupMenu popup = new JPopupMenu(); - popup.add(createMenuItemOpenBook()); - return popup; - } - - @Override - public void createMenu(Status status) { - invalidate(); - - JMenuBar bar = new JMenuBar(); - - JMenu file = new JMenu(GuiReader.trans(StringIdGui.MENU_FILE)); - file.setMnemonic(KeyEvent.VK_F); - - JMenuItem imprt = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_URL), - KeyEvent.VK_U); - imprt.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mainPanel.imprt(true); - } - }); - JMenuItem imprtF = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_FILE), - KeyEvent.VK_F); - imprtF.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mainPanel.imprt(false); - } - }); - JMenuItem exit = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_EXIT), KeyEvent.VK_X); - exit.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - GuiReaderFrame.this.dispatchEvent(new WindowEvent( - GuiReaderFrame.this, WindowEvent.WINDOW_CLOSING)); - } - }); - - file.add(createMenuItemOpenBook()); - file.add(createMenuItemExport()); - if (status.isWritable()) { - file.add(createMenuItemMoveTo()); - file.addSeparator(); - file.add(imprt); - file.add(imprtF); - file.addSeparator(); - file.add(createMenuItemRename()); - file.add(createMenuItemSetAuthor()); - } - file.addSeparator(); - file.add(createMenuItemProperties()); - file.addSeparator(); - file.add(exit); - - bar.add(file); - - JMenu edit = new JMenu(GuiReader.trans(StringIdGui.MENU_EDIT)); - edit.setMnemonic(KeyEvent.VK_E); - - edit.add(createMenuItemSetCoverForSource()); - edit.add(createMenuItemSetCoverForAuthor()); - edit.add(createMenuItemDownloadToCache()); - edit.add(createMenuItemClearCache()); - edit.add(createMenuItemRedownload()); - edit.addSeparator(); - edit.add(createMenuItemDelete()); - - bar.add(edit); - - JMenu search = new JMenu(GuiReader.trans(StringIdGui.MENU_SEARCH)); - search.setMnemonic(KeyEvent.VK_H); - for (final SupportType type : SupportType.values()) { - BasicSearchable searchable = BasicSearchable.getSearchable(type); - if (searchable != null) { - JMenuItem searchItem = new JMenuItem(type.getSourceName()); - searchItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - reader.search(type, null, 1, 0, false); - } - }); - search.add(searchItem); - } - } - - bar.add(search); - - JMenu view = new JMenu(GuiReader.trans(StringIdGui.MENU_VIEW)); - view.setMnemonic(KeyEvent.VK_V); - JMenuItem vauthors = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_VIEW_AUTHOR)); - vauthors.setMnemonic(KeyEvent.VK_A); - vauthors.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mainPanel.setWords(false); - mainPanel.refreshBooks(); - } - }); - view.add(vauthors); - JMenuItem vwords = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_VIEW_WCOUNT)); - vwords.setMnemonic(KeyEvent.VK_W); - vwords.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mainPanel.setWords(true); - mainPanel.refreshBooks(); - } - }); - view.add(vwords); - bar.add(view); - - Map> groupedSources = new HashMap>(); - if (status.isReady()) { - try { - groupedSources = reader.getLibrary().getSourcesGrouped(); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - } - JMenu sources = new JMenu(GuiReader.trans(StringIdGui.MENU_SOURCES)); - sources.setMnemonic(KeyEvent.VK_S); - populateMenuSA(sources, groupedSources, true); - bar.add(sources); - - Map> goupedAuthors = new HashMap>(); - if (status.isReady()) { - try { - goupedAuthors = reader.getLibrary().getAuthorsGrouped(); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - } - JMenu authors = new JMenu(GuiReader.trans(StringIdGui.MENU_AUTHORS)); - authors.setMnemonic(KeyEvent.VK_A); - populateMenuSA(authors, goupedAuthors, false); - bar.add(authors); - - JMenu options = new JMenu(GuiReader.trans(StringIdGui.MENU_OPTIONS)); - options.setMnemonic(KeyEvent.VK_O); - options.add(createMenuItemConfig()); - options.add(createMenuItemUiConfig()); - bar.add(options); - - setJMenuBar(bar); - } - - // "" = [unknown] - private void populateMenuSA(JMenu menu, - Map> groupedValues, boolean type) { - - // "All" and "Listing" special items first - JMenuItem item = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_XXX_ALL_GROUPED)); - item.addActionListener(getActionOpenList(type, false)); - menu.add(item); - item = new JMenuItem(GuiReader.trans(StringIdGui.MENU_XXX_ALL_LISTING)); - item.addActionListener(getActionOpenList(type, true)); - menu.add(item); - - menu.addSeparator(); - - for (final String value : groupedValues.keySet()) { - List list = groupedValues.get(value); - if (type && list.size() == 1 && list.get(0).isEmpty()) { - // leaf item source/type - item = new JMenuItem( - value.isEmpty() ? GuiReader - .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) - : value); - item.addActionListener(getActionOpen(value, type)); - menu.add(item); - } else { - JMenu dir; - if (!type && groupedValues.size() == 1) { - // only one group of authors - dir = menu; - } else { - dir = new JMenu( - value.isEmpty() ? GuiReader - .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) - : value); - } - - for (String sub : list) { - // " " instead of "" for the visual height - String itemName = sub; - if (itemName.isEmpty()) { - itemName = type ? " " : GuiReader - .trans(StringIdGui.MENU_AUTHORS_UNKNOWN); - } - - String actualValue = value; - if (type) { - if (!sub.isEmpty()) { - actualValue += "/" + sub; - } - } else { - actualValue = sub; - } - - item = new JMenuItem(itemName); - item.addActionListener(getActionOpen(actualValue, type)); - dir.add(item); - } - - if (menu != dir) { - menu.add(dir); - } - } - } - } - - /** - * Return an {@link ActionListener} that will set the given source (type) as - * the selected/displayed one. - * - * @param type - * the type (source) to select, cannot be NULL - * - * @return the {@link ActionListener} - */ - private ActionListener getActionOpen(final String source, final boolean type) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mainPanel.removeBookPanes(); - mainPanel.addBookPane(source, type); - mainPanel.refreshBooks(); - } - }; - } - - private ActionListener getActionOpenList(final boolean type, - final boolean listMode) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - mainPanel.removeBookPanes(); - try { - mainPanel.addBookPane(type, listMode); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - mainPanel.refreshBooks(); - } - }; - } - - /** - * Create the Fanfix Configuration menu item. - * - * @return the item - */ - private JMenuItem createMenuItemConfig() { - final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG); - JMenuItem item = new JMenuItem(title); - item.setMnemonic(KeyEvent.VK_F); - - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConfigEditor ed = new ConfigEditor( - Config.class, Instance.getInstance().getConfig(), GuiReader.trans(StringIdGui.SUBTITLE_CONFIG)); - JFrame frame = new JFrame(title); - frame.add(ed); - frame.setSize(850, 600); - frame.setVisible(true); - } - }); - - return item; - } - - /** - * Create the UI Configuration menu item. - * - * @return the item - */ - private JMenuItem createMenuItemUiConfig() { - final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG_UI); - JMenuItem item = new JMenuItem(title); - item.setMnemonic(KeyEvent.VK_U); - - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConfigEditor ed = new ConfigEditor( - UiConfig.class, Instance.getInstance().getUiConfig(), - GuiReader.trans(StringIdGui.SUBTITLE_CONFIG_UI)); - JFrame frame = new JFrame(title); - frame.add(ed); - frame.setSize(800, 600); - frame.setVisible(true); - } - }); - - return item; - } - - /** - * Create the export menu item. - * - * @return the item - */ - private JMenuItem createMenuItemExport() { - final JFileChooser fc = new JFileChooser(); - fc.setAcceptAllFileFilterUsed(false); - - // Add the "ALL" filters first, then the others - final Map otherFilters = new HashMap(); - for (OutputType type : OutputType.values()) { - String ext = type.getDefaultExtension(false); - String desc = type.getDesc(false); - - if (ext == null || ext.isEmpty()) { - fc.addChoosableFileFilter(createAllFilter(desc)); - } else { - otherFilters.put(new FileNameExtensionFilter(desc, ext), type); - } - } - - for (Entry entry : otherFilters.entrySet()) { - fc.addChoosableFileFilter(entry.getKey()); - } - // - - JMenuItem export = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_EXPORT), KeyEvent.VK_S); - export.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - fc.showDialog(GuiReaderFrame.this, - GuiReader.trans(StringIdGui.TITLE_SAVE)); - if (fc.getSelectedFile() != null) { - final OutputType type = otherFilters.get(fc - .getFileFilter()); - final String path = fc.getSelectedFile() - .getAbsolutePath() - + type.getDefaultExtension(false); - final Progress pg = new Progress(); - mainPanel.outOfUi(pg, false, new Runnable() { - @Override - public void run() { - try { - reader.getLibrary().export( - selectedBook.getInfo().getMeta() - .getLuid(), type, path, pg); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - }); - } - } - } - }); - - return export; - } - - /** - * Create a {@link FileFilter} that accepts all files and return the given - * description. - * - * @param desc - * the description - * - * @return the filter - */ - private FileFilter createAllFilter(final String desc) { - return new FileFilter() { - @Override - public String getDescription() { - return desc; - } - - @Override - public boolean accept(File f) { - return true; - } - }; - } - - /** - * Create the refresh (delete cache) menu item. - * - * @return the item - */ - private JMenuItem createMenuItemClearCache() { - JMenuItem refresh = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_CLEAR_CACHE), - KeyEvent.VK_C); - refresh.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - mainPanel.outOfUi(null, false, new Runnable() { - @Override - public void run() { - reader.clearLocalReaderCache(selectedBook.getInfo() - .getMeta().getLuid()); - selectedBook.setCached(false); - GuiReaderCoverImager.clearIcon(selectedBook - .getInfo()); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - selectedBook.repaint(); - } - }); - } - }); - } - } - }); - - return refresh; - } - - /** - * Create the "move to" menu item. - * - * @return the item - */ - private JMenuItem createMenuItemMoveTo() { - JMenu changeTo = new JMenu( - GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO)); - changeTo.setMnemonic(KeyEvent.VK_M); - - Map> groupedSources = new HashMap>(); - try { - groupedSources = reader.getLibrary().getSourcesGrouped(); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - - JMenuItem item = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE)); - item.addActionListener(createMoveAction(ChangeAction.SOURCE, null)); - changeTo.add(item); - changeTo.addSeparator(); - - for (final String type : groupedSources.keySet()) { - List list = groupedSources.get(type); - if (list.size() == 1 && list.get(0).isEmpty()) { - item = new JMenuItem(type); - item.addActionListener(createMoveAction(ChangeAction.SOURCE, - type)); - changeTo.add(item); - } else { - JMenu dir = new JMenu(type); - for (String sub : list) { - // " " instead of "" for the visual height - String itemName = sub.isEmpty() ? " " : sub; - String actualType = type; - if (!sub.isEmpty()) { - actualType += "/" + sub; - } - - item = new JMenuItem(itemName); - item.addActionListener(createMoveAction( - ChangeAction.SOURCE, actualType)); - dir.add(item); - } - changeTo.add(dir); - } - } - - return changeTo; - } - - /** - * Create the "set author" menu item. - * - * @return the item - */ - private JMenuItem createMenuItemSetAuthor() { - JMenu changeTo = new JMenu( - GuiReader.trans(StringIdGui.MENU_FILE_SET_AUTHOR)); - changeTo.setMnemonic(KeyEvent.VK_A); - - // New author - JMenuItem newItem = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_AUTHOR)); - changeTo.add(newItem); - changeTo.addSeparator(); - newItem.addActionListener(createMoveAction(ChangeAction.AUTHOR, null)); - - // Existing authors - Map> groupedAuthors; - - try { - groupedAuthors = reader.getLibrary().getAuthorsGrouped(); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - groupedAuthors = new HashMap>(); - - } - - if (groupedAuthors.size() > 1) { - for (String key : groupedAuthors.keySet()) { - JMenu group = new JMenu(key); - for (String value : groupedAuthors.get(key)) { - JMenuItem item = new JMenuItem( - value.isEmpty() ? GuiReader - .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) - : value); - item.addActionListener(createMoveAction( - ChangeAction.AUTHOR, value)); - group.add(item); - } - changeTo.add(group); - } - } else if (groupedAuthors.size() == 1) { - for (String value : groupedAuthors.values().iterator().next()) { - JMenuItem item = new JMenuItem( - value.isEmpty() ? GuiReader - .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) - : value); - item.addActionListener(createMoveAction(ChangeAction.AUTHOR, - value)); - changeTo.add(item); - } - } - - return changeTo; - } - - /** - * Create the "rename" menu item. - * - * @return the item - */ - private JMenuItem createMenuItemRename() { - JMenuItem changeTo = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_RENAME)); - changeTo.setMnemonic(KeyEvent.VK_R); - changeTo.addActionListener(createMoveAction(ChangeAction.TITLE, null)); - return changeTo; - } - - private ActionListener createMoveAction(final ChangeAction what, - final String type) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - boolean refreshRequired = false; - - if (what == ChangeAction.SOURCE) { - refreshRequired = mainPanel.getCurrentType(); - } else if (what == ChangeAction.TITLE) { - refreshRequired = false; - } else if (what == ChangeAction.AUTHOR) { - refreshRequired = !mainPanel.getCurrentType(); - } - - String changeTo = type; - if (type == null) { - MetaData meta = selectedBook.getInfo().getMeta(); - String init = ""; - if (what == ChangeAction.SOURCE) { - init = meta.getSource(); - } else if (what == ChangeAction.TITLE) { - init = meta.getTitle(); - } else if (what == ChangeAction.AUTHOR) { - init = meta.getAuthor(); - } - - Object rep = JOptionPane.showInputDialog( - GuiReaderFrame.this, - GuiReader.trans(StringIdGui.SUBTITLE_MOVE_TO), - GuiReader.trans(StringIdGui.TITLE_MOVE_TO), - JOptionPane.QUESTION_MESSAGE, null, null, init); - - if (rep == null) { - return; - } - - changeTo = rep.toString(); - } - - final String fChangeTo = changeTo; - mainPanel.outOfUi(null, refreshRequired, new Runnable() { - @Override - public void run() { - String luid = selectedBook.getInfo().getMeta() - .getLuid(); - if (what == ChangeAction.SOURCE) { - reader.changeSource(luid, fChangeTo); - } else if (what == ChangeAction.TITLE) { - reader.changeTitle(luid, fChangeTo); - } else if (what == ChangeAction.AUTHOR) { - reader.changeAuthor(luid, fChangeTo); - } - - mainPanel.getSelectedBook().repaint(); - mainPanel.unsetSelectedBook(); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - createMenu(reader.getLibrary().getStatus()); - } - }); - } - }); - } - } - }; - } - - /** - * Create the re-download (then delete original) menu item. - * - * @return the item - */ - private JMenuItem createMenuItemRedownload() { - JMenuItem refresh = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_REDOWNLOAD), - KeyEvent.VK_R); - refresh.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - final MetaData meta = selectedBook.getInfo().getMeta(); - mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() { - @Override - public void run(MetaData newMeta) { - if (!newMeta.getSource().equals(meta.getSource())) { - reader.changeSource(newMeta.getLuid(), - meta.getSource()); - } - } - }, GuiReader.trans(StringIdGui.PROGRESS_CHANGE_SOURCE)); - } - } - }); - - return refresh; - } - - /** - * Create the download to cache menu item. - * - * @return the item - */ - private JMenuItem createMenuItemDownloadToCache() { - JMenuItem refresh = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE), - KeyEvent.VK_T); - refresh.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - mainPanel.prefetchBook(selectedBook); - } - } - }); - - return refresh; - } - - - /** - * Create the delete menu item. - * - * @return the item - */ - private JMenuItem createMenuItemDelete() { - JMenuItem delete = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_DELETE), KeyEvent.VK_D); - delete.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null - && selectedBook.getInfo().getMeta() != null) { - - final MetaData meta = selectedBook.getInfo().getMeta(); - int rep = JOptionPane.showConfirmDialog( - GuiReaderFrame.this, - GuiReader.trans(StringIdGui.SUBTITLE_DELETE, - meta.getLuid(), meta.getTitle()), - GuiReader.trans(StringIdGui.TITLE_DELETE), - JOptionPane.OK_CANCEL_OPTION); - - if (rep == JOptionPane.OK_OPTION) { - mainPanel.outOfUi(null, true, new Runnable() { - @Override - public void run() { - reader.delete(meta.getLuid()); - mainPanel.unsetSelectedBook(); - } - }); - } - } - } - }); - - return delete; - } - - /** - * Create the properties menu item. - * - * @return the item - */ - private JMenuItem createMenuItemProperties() { - JMenuItem delete = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_PROPERTIES), - KeyEvent.VK_P); - delete.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - mainPanel.outOfUi(null, false, new Runnable() { - @Override - public void run() { - new GuiReaderPropertiesFrame(reader.getLibrary(), - selectedBook.getInfo().getMeta()) - .setVisible(true); - } - }); - } - } - }); - - return delete; - } - - /** - * Create the open menu item for a book, a source/type or an author. - * - * @return the item - */ - public JMenuItem createMenuItemOpenBook() { - JMenuItem open = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_FILE_OPEN), KeyEvent.VK_O); - open.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - if (selectedBook.getInfo().getMeta() == null) { - mainPanel.removeBookPanes(); - mainPanel.addBookPane(selectedBook.getInfo() - .getMainInfo(), mainPanel.getCurrentType()); - mainPanel.refreshBooks(); - } else { - mainPanel.openBook(selectedBook); - } - } - } - }); - - return open; - } - - /** - * Create the SetCover menu item for a book to change the linked source - * cover. - * - * @return the item - */ - private JMenuItem createMenuItemSetCoverForSource() { - JMenuItem open = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_SOURCE), - KeyEvent.VK_C); - open.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - BasicLibrary lib = reader.getLibrary(); - String luid = selectedBook.getInfo().getMeta().getLuid(); - String source = selectedBook.getInfo().getMeta() - .getSource(); - - try { - lib.setSourceCover(source, luid); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - - GuiReaderBookInfo sourceInfo = GuiReaderBookInfo - .fromSource(lib, source); - GuiReaderCoverImager.clearIcon(sourceInfo); - } - } - }); - - return open; - } - - /** - * Create the SetCover menu item for a book to change the linked source - * cover. - * - * @return the item - */ - private JMenuItem createMenuItemSetCoverForAuthor() { - JMenuItem open = new JMenuItem( - GuiReader.trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_AUTHOR), - KeyEvent.VK_A); - open.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); - if (selectedBook != null) { - BasicLibrary lib = reader.getLibrary(); - String luid = selectedBook.getInfo().getMeta().getLuid(); - String author = selectedBook.getInfo().getMeta() - .getAuthor(); - - try { - lib.setAuthorCover(author, luid); - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - } - - GuiReaderBookInfo authorInfo = GuiReaderBookInfo - .fromAuthor(lib, author); - GuiReaderCoverImager.clearIcon(authorInfo); - } - } - }); - - return open; - } - - /** - * Display an error message and log the linked {@link Exception}. - * - * @param message - * the message - * @param title - * the title of the error message - * @param e - * the exception to log if any - */ - public void error(final String message, final String title, Exception e) { - Instance.getInstance().getTraceHandler().error(title + ": " + message); - if (e != null) { - Instance.getInstance().getTraceHandler().error(e); - } - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - JOptionPane.showMessageDialog(GuiReaderFrame.this, message, - title, JOptionPane.ERROR_MESSAGE); - } - }); - } - - @Override - public GuiReader getReader() { - return reader; - } - - /** - * Return the title of the application. - * - * @param libraryName - * the name of the associated {@link BasicLibrary}, which can be - * EMPTY - * - * @return the title - */ - static private String getAppTitle(String libraryName) { - if (!libraryName.isEmpty()) { - return GuiReader.trans(StringIdGui.TITLE_LIBRARY_WITH_NAME, Version - .getCurrentVersion().toString(), libraryName); - } - - return GuiReader.trans(StringIdGui.TITLE_LIBRARY, Version - .getCurrentVersion().toString()); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java deleted file mode 100644 index cc3f1e1..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java +++ /dev/null @@ -1,476 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JLabel; -import javax.swing.JPanel; - -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; -import be.nikiroo.utils.ui.WrapLayout; - -/** - * A group of {@link GuiReaderBook}s for display. - * - * @author niki - */ -public class GuiReaderGroup extends JPanel { - private static final long serialVersionUID = 1L; - private BookActionListener action; - private Color backgroundColor; - private Color backgroundColorDef; - private Color backgroundColorDefPane; - private GuiReader reader; - private List infos; - private List books; - private JPanel pane; - private JLabel titleLabel; - private boolean words; // words or authors (secondary info on books) - private int itemsPerLine; - - /** - * Create a new {@link GuiReaderGroup}. - * - * @param reader - * the {@link GuiReaderBook} used to probe some information about - * the stories - * @param title - * the title of this group (can be NULL for "no title", an empty - * {@link String} will trigger a default title for empty groups) - * @param backgroundColor - * the background colour to use (or NULL for default) - */ - public GuiReaderGroup(GuiReader reader, String title, Color backgroundColor) { - this.reader = reader; - - this.pane = new JPanel(); - pane.setLayout(new WrapLayout(WrapLayout.LEADING, 5, 5)); - - this.backgroundColorDef = getBackground(); - this.backgroundColorDefPane = pane.getBackground(); - setBackground(backgroundColor); - - setLayout(new BorderLayout(0, 10)); - - // Make it focusable: - setFocusable(true); - setEnabled(true); - setVisible(true); - - add(pane, BorderLayout.CENTER); - - titleLabel = new JLabel(); - titleLabel.setHorizontalAlignment(JLabel.CENTER); - add(titleLabel, BorderLayout.NORTH); - setTitle(title); - - // Compute the number of items per line at each resize - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - super.componentResized(e); - computeItemsPerLine(); - } - }); - computeItemsPerLine(); - - addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - onKeyPressed(e); - } - - @Override - public void keyTyped(KeyEvent e) { - onKeyTyped(e); - } - }); - - addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - if (getSelectedBookIndex() < 0) { - setSelectedBook(0, true); - } - } - - @Override - public void focusLost(FocusEvent e) { - setBackground(null); - setSelectedBook(-1, false); - } - }); - } - - /** - * Note: this class supports NULL as a background colour, which will revert - * it to its default state. - *

    - * Note: this class' implementation will also set the main pane background - * colour at the same time. - *

    - * Sets the background colour of this component. The background colour is - * used only if the component is opaque, and only by subclasses of - * JComponent or ComponentUI implementations. - * Direct subclasses of JComponent must override - * paintComponent to honour this property. - *

    - * It is up to the look and feel to honour this property, some may choose to - * ignore it. - * - * @param backgroundColor - * the desired background Colour - * @see java.awt.Component#getBackground - * @see #setOpaque - * - * @beaninfo preferred: true bound: true attribute: visualUpdate true - * description: The background colour of the component. - */ - @Override - public void setBackground(Color backgroundColor) { - this.backgroundColor = backgroundColor; - - Color cme = backgroundColor == null ? backgroundColorDef - : backgroundColor; - Color cpane = backgroundColor == null ? backgroundColorDefPane - : backgroundColor; - - if (pane != null) { // can happen at theme setup time - pane.setBackground(cpane); - } - super.setBackground(cme); - } - - /** - * The title of this group (can be NULL for "no title", an empty - * {@link String} will trigger a default title for empty groups) - * - * @param title - * the title or NULL - */ - public void setTitle(String title) { - if (title != null) { - if (title.isEmpty()) { - title = GuiReader.trans(StringIdGui.MENU_AUTHORS_UNKNOWN); - } - - titleLabel.setText(String.format("" - + "
    " - + "%s" + "" + "", title)); - titleLabel.setVisible(true); - } else { - titleLabel.setVisible(false); - } - } - - /** - * Compute how many items can fit in a line so UP and DOWN can be used to go - * up/down one line at a time. - */ - private void computeItemsPerLine() { - itemsPerLine = 1; - - if (books != null && books.size() > 0) { - // this.pane holds all the books with a hgap of 5 px - int wbook = books.get(0).getWidth() + 5; - itemsPerLine = pane.getWidth() / wbook; - } - } - - /** - * Set the {@link ActionListener} that will be fired on each - * {@link GuiReaderBook} action. - * - * @param action - * the action - */ - public void setActionListener(BookActionListener action) { - this.action = action; - refreshBooks(); - } - - /** - * Clear all the books in this {@link GuiReaderGroup}. - */ - public void clear() { - refreshBooks(new ArrayList()); - } - - /** - * Refresh the list of {@link GuiReaderBook}s displayed in the control. - */ - public void refreshBooks() { - refreshBooks(infos, words); - } - - /** - * Refresh the list of {@link GuiReaderBook}s displayed in the control. - * - * @param infos - * the new list of infos - */ - public void refreshBooks(List infos) { - refreshBooks(infos, words); - } - - /** - * Refresh the list of {@link GuiReaderBook}s displayed in the control. - * - * @param infos - * the new list of infos - * @param seeWordcount - * TRUE to see word counts, FALSE to see authors - */ - public void refreshBooks(List infos, boolean seeWordcount) { - this.infos = infos; - refreshBooks(seeWordcount); - } - - /** - * Refresh the list of {@link GuiReaderBook}s displayed in the control. - *

    - * Will not change the current stories. - * - * @param seeWordcount - * TRUE to see word counts, FALSE to see authors - */ - public void refreshBooks(boolean seeWordcount) { - this.words = seeWordcount; - - books = new ArrayList(); - invalidate(); - pane.invalidate(); - pane.removeAll(); - - if (infos != null) { - for (GuiReaderBookInfo info : infos) { - boolean isCached = false; - if (info.getMeta() != null && info.getMeta().getLuid() != null) { - isCached = reader.isCached(info.getMeta().getLuid()); - } - - GuiReaderBook book = new GuiReaderBook(reader, info, isCached, - words); - if (backgroundColor != null) { - book.setBackground(backgroundColor); - } - - books.add(book); - - book.addActionListener(new BookActionListener() { - @Override - public void select(GuiReaderBook book) { - GuiReaderGroup.this.requestFocusInWindow(); - for (GuiReaderBook abook : books) { - abook.setSelected(abook == book); - } - } - - @Override - public void popupRequested(GuiReaderBook book, - Component target, int x, int y) { - } - - @Override - public void action(GuiReaderBook book) { - } - }); - - if (action != null) { - book.addActionListener(action); - } - - pane.add(book); - } - } - - pane.validate(); - pane.repaint(); - validate(); - repaint(); - - computeItemsPerLine(); - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - if (books != null) { - for (GuiReaderBook book : books) { - book.setEnabled(b); - book.repaint(); - } - } - - pane.setEnabled(b); - super.setEnabled(b); - repaint(); - } - - /** - * The number of books in this group. - * - * @return the count - */ - public int getBooksCount() { - return books.size(); - } - - /** - * Return the index of the currently selected book if any, -1 if none. - * - * @return the index or -1 - */ - public int getSelectedBookIndex() { - int index = -1; - for (int i = 0; i < books.size(); i++) { - if (books.get(i).isSelected()) { - index = i; - break; - } - } - return index; - } - - /** - * Select the given book, or unselect all items. - * - * @param index - * the index of the book to select, can be outside the bounds - * (either all the items will be unselected or the first or last - * book will then be selected, see forceRange>) - * @param forceRange - * TRUE to constraint the index to the first/last element, FALSE - * to unselect when outside the range - */ - public void setSelectedBook(int index, boolean forceRange) { - int previousIndex = getSelectedBookIndex(); - - if (index >= books.size()) { - if (forceRange) { - index = books.size() - 1; - } else { - index = -1; - } - } - - if (index < 0 && forceRange) { - index = 0; - } - - if (previousIndex >= 0) { - books.get(previousIndex).setSelected(false); - } - - if (index >= 0 && !books.isEmpty()) { - books.get(index).setSelected(true); - } - } - - /** - * The action to execute when a key is typed. - * - * @param e - * the key event - */ - private void onKeyTyped(KeyEvent e) { - boolean consumed = false; - boolean action = e.getKeyChar() == '\n'; - boolean popup = e.getKeyChar() == ' '; - if (action || popup) { - consumed = true; - - int index = getSelectedBookIndex(); - if (index >= 0) { - GuiReaderBook book = books.get(index); - if (action) { - book.action(); - } else if (popup) { - book.popup(book, book.getWidth() / 2, book.getHeight() / 2); - } - } - } - - if (consumed) { - e.consume(); - } - } - - /** - * The action to execute when a key is pressed. - * - * @param e - * the key event - */ - private void onKeyPressed(KeyEvent e) { - boolean consumed = false; - if (e.isActionKey()) { - int offset = 0; - switch (e.getKeyCode()) { - case KeyEvent.VK_LEFT: - offset = -1; - break; - case KeyEvent.VK_RIGHT: - offset = 1; - break; - case KeyEvent.VK_UP: - offset = -itemsPerLine; - break; - case KeyEvent.VK_DOWN: - offset = itemsPerLine; - break; - } - - if (offset != 0) { - consumed = true; - - int previousIndex = getSelectedBookIndex(); - if (previousIndex >= 0) { - setSelectedBook(previousIndex + offset, true); - } - } - } - - if (consumed) { - e.consume(); - } - } - - @Override - public void paint(Graphics g) { - super.paint(g); - - Rectangle clip = g.getClipBounds(); - if (clip.getWidth() <= 0 || clip.getHeight() <= 0) { - return; - } - - if (!isEnabled()) { - g.setColor(new Color(128, 128, 128, 128)); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java deleted file mode 100644 index a5eb691..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java +++ /dev/null @@ -1,790 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.Toolkit; -import java.awt.datatransfer.DataFlavor; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import javax.swing.BoxLayout; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JMenuBar; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.bundles.UiConfig; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.BasicLibrary.Status; -import be.nikiroo.fanfix.library.LocalLibrary; -import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; -import be.nikiroo.fanfix.reader.ui.GuiReaderBookInfo.Type; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.ui.ProgressBar; - -/** - * A {@link Frame} that will show a {@link GuiReaderBook} item for each - * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a - * way to copy them to the {@link GuiReader} cache ( - * {@link BasicReader#getLibrary()}), read them, delete them... - * - * @author niki - */ -class GuiReaderMainPanel extends JPanel { - private static final long serialVersionUID = 1L; - private FrameHelper helper; - private Map books; - private GuiReaderGroup bookPane; // for more "All" - private JPanel pane; - private Color color; - private ProgressBar pgBar; - private JMenuBar bar; - private GuiReaderBook selectedBook; - private boolean words; // words or authors (secondary info on books) - private boolean currentType; // type/source or author mode (All and Listing) - - /** - * An object that offers some helper methods to access the frame that host - * it and the Fanfix-related functions. - * - * @author niki - */ - public interface FrameHelper { - /** - * Return the reader associated to this {@link FrameHelper}. - * - * @return the reader - */ - public GuiReader getReader(); - - /** - * Create the main menu bar. - *

    - * Will invalidate the layout. - * - * @param status - * the library status, must not be NULL - */ - public void createMenu(Status status); - - /** - * Create a popup menu for a {@link GuiReaderBook} that represents a - * story. - * - * @return the popup menu to display - */ - public JPopupMenu createBookPopup(); - - /** - * Create a popup menu for a {@link GuiReaderBook} that represents a - * source/type or an author. - * - * @return the popup menu to display - */ - public JPopupMenu createSourceAuthorPopup(); - } - - /** - * A {@link Runnable} with a {@link MetaData} parameter. - * - * @author niki - */ - public interface MetaDataRunnable { - /** - * Run the action. - * - * @param meta - * the meta of the story - */ - public void run(MetaData meta); - } - - /** - * Create a new {@link GuiReaderMainPanel}. - * - * @param parent - * the associated {@link FrameHelper} to forward some commands - * and access its {@link LocalLibrary} - * @param type - * the type of {@link Story} to load, or NULL for all types - */ - public GuiReaderMainPanel(FrameHelper parent, String type) { - super(new BorderLayout(), true); - - this.helper = parent; - - pane = new JPanel(); - pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); - JScrollPane scroll = new JScrollPane(pane); - - Integer icolor = Instance.getInstance().getUiConfig().getColor(UiConfig.BACKGROUND_COLOR); - if (icolor != null) { - color = new Color(icolor); - setBackground(color); - pane.setBackground(color); - scroll.setBackground(color); - } - - scroll.getVerticalScrollBar().setUnitIncrement(16); - add(scroll, BorderLayout.CENTER); - - String message = parent.getReader().getLibrary().getLibraryName(); - if (!message.isEmpty()) { - JLabel name = new JLabel(message, SwingConstants.CENTER); - add(name, BorderLayout.NORTH); - } - - pgBar = new ProgressBar(); - add(pgBar, BorderLayout.SOUTH); - - pgBar.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - pgBar.invalidate(); - pgBar.setProgress(null); - setEnabled(true); - validate(); - } - }); - - pgBar.addUpdateListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - pgBar.invalidate(); - validate(); - repaint(); - } - }); - - books = new TreeMap(); - - addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - focus(); - } - }); - - pane.setVisible(false); - final Progress pg = new Progress(); - final String typeF = type; - outOfUi(pg, true, new Runnable() { - @Override - public void run() { - final BasicLibrary lib = helper.getReader().getLibrary(); - final Status status = lib.getStatus(); - - if (status == Status.READ_WRITE) { - lib.refresh(pg); - } - - inUi(new Runnable() { - @Override - public void run() { - if (status.isReady()) { - helper.createMenu(status); - pane.setVisible(true); - if (typeF == null) { - try { - addBookPane(true, false); - } catch (IOException e) { - error(e.getLocalizedMessage(), - "IOException", e); - } - } else { - addBookPane(typeF, true); - } - } else { - helper.createMenu(status); - validate(); - - String desc = Instance.getInstance().getTransGui().getStringX(StringIdGui.ERROR_LIB_STATUS, - status.toString()); - if (desc == null) { - desc = GuiReader - .trans(StringIdGui.ERROR_LIB_STATUS); - } - - String err = lib.getLibraryName() + "\n" + desc; - error(err, GuiReader - .trans(StringIdGui.TITLE_ERROR_LIBRARY), - null); - } - } - }); - } - }); - } - - public boolean getCurrentType() { - return currentType; - } - - /** - * Add a new {@link GuiReaderGroup} on the frame to display all the - * sources/types or all the authors, or a listing of all the books sorted - * either by source or author. - *

    - * A display of all the sources/types or all the authors will show one icon - * per source/type or author. - *

    - * A listing of all the books sorted by source/type or author will display - * all the books. - * - * @param type - * TRUE for type/source, FALSE for author - * @param listMode - * TRUE to get a listing of all the sources or authors, FALSE to - * get one icon per source or author - * - * @throws IOException - * in case of I/O error - */ - public void addBookPane(boolean type, boolean listMode) throws IOException { - this.currentType = type; - BasicLibrary lib = helper.getReader().getLibrary(); - if (type) { - if (!listMode) { - addListPane(GuiReader.trans(StringIdGui.MENU_SOURCES), - lib.getSources(), type); - } else { - for (String tt : lib.getSources()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - } else { - if (!listMode) { - addListPane(GuiReader.trans(StringIdGui.MENU_AUTHORS), - lib.getAuthors(), type); - } else { - for (String tt : lib.getAuthors()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - } - } - - /** - * Add a new {@link GuiReaderGroup} on the frame to display the books of the - * selected type or author. - *

    - * Will invalidate the layout. - * - * @param value - * the author or the type, or NULL to get all the - * authors-or-types - * @param type - * TRUE for type/source, FALSE for author - */ - public void addBookPane(String value, boolean type) { - this.currentType = type; - - GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), value, - color); - - books.put(value, bookPane); - - pane.invalidate(); - pane.add(bookPane); - - bookPane.setActionListener(new BookActionListener() { - @Override - public void select(GuiReaderBook book) { - selectedBook = book; - } - - @Override - public void popupRequested(GuiReaderBook book, Component target, - int x, int y) { - JPopupMenu popup = helper.createBookPopup(); - popup.show(target, x, y); - } - - @Override - public void action(final GuiReaderBook book) { - openBook(book); - } - }); - - focus(); - } - - /** - * Clear the pane from any book that may be present, usually prior to adding - * new ones. - *

    - * Will invalidate the layout. - */ - public void removeBookPanes() { - books.clear(); - pane.invalidate(); - pane.removeAll(); - } - - /** - * Refresh the list of {@link GuiReaderBook}s from disk. - *

    - * Will validate the layout, as it is a "refresh" operation. - */ - public void refreshBooks() { - BasicLibrary lib = helper.getReader().getLibrary(); - for (String value : books.keySet()) { - List infos = new ArrayList(); - - List metas; - try { - if (currentType) { - metas = lib.getList().filter(value, null, null); - } else { - metas = lib.getList().filter(null, value, null); - } - } catch (IOException e) { - error(e.getLocalizedMessage(), "IOException", e); - metas = new ArrayList(); - } - - for (MetaData meta : metas) { - infos.add(GuiReaderBookInfo.fromMeta(meta)); - } - - books.get(value).refreshBooks(infos, words); - } - - if (bookPane != null) { - bookPane.refreshBooks(words); - } - - this.validate(); - } - - /** - * Open a {@link GuiReaderBook} item. - * - * @param book - * the {@link GuiReaderBook} to open - */ - public void openBook(final GuiReaderBook book) { - final Progress pg = new Progress(); - outOfUi(pg, false, new Runnable() { - @Override - public void run() { - try { - helper.getReader().read(book.getInfo().getMeta().getLuid(), - false, pg); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - book.setCached(true); - } - }); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN), - GuiReader.trans(StringIdGui.TITLE_ERROR), e); - } - } - }); - } - - /** - * Prefetch a {@link GuiReaderBook} item (which can be a group, in which - * case we prefetch all its members). - * - * @param book - * the {@link GuiReaderBook} to open - */ - public void prefetchBook(final GuiReaderBook book) { - final List luids = new LinkedList(); - try { - switch (book.getInfo().getType()) { - case STORY: - luids.add(book.getInfo().getMeta().getLuid()); - break; - case SOURCE: - for (MetaData meta : helper.getReader().getLibrary() - .getList().filter(book.getInfo().getMainInfo(), null, null)) { - luids.add(meta.getLuid()); - } - break; - case AUTHOR: - for (MetaData meta : helper.getReader().getLibrary() - .getList().filter(null, book.getInfo().getMainInfo(), null)) { - luids.add(meta.getLuid()); - } - break; - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - final Progress pg = new Progress(); - pg.setMax(luids.size()); - - outOfUi(pg, false, new Runnable() { - @Override - public void run() { - try { - for (String luid : luids) { - Progress pgStep = new Progress(); - pg.addProgress(pgStep, 1); - - helper.getReader().prefetch(luid, pgStep); - } - - // TODO: also set the green button on sources/authors? - // requires to do the same when all stories inside are green - if (book.getInfo().getType() == Type.STORY) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - book.setCached(true); - } - }); - } - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN), - GuiReader.trans(StringIdGui.TITLE_ERROR), e); - } - } - }); - } - - /** - * Process the given action out of the Swing UI thread and link the given - * {@link ProgressBar} to the action. - *

    - * The code will make sure that the {@link ProgressBar} (if not NULL) is set - * to done when the action is done. - * - * @param progress - * the {@link ProgressBar} or NULL - * @param refreshBooks - * TRUE to refresh the books after - * @param run - * the action to run - */ - public void outOfUi(Progress progress, final boolean refreshBooks, - final Runnable run) { - final Progress pg = new Progress(); - final Progress reload = new Progress( - GuiReader.trans(StringIdGui.PROGRESS_OUT_OF_UI_RELOAD_BOOKS)); - - if (progress == null) { - progress = new Progress(); - } - - if (refreshBooks) { - pg.addProgress(progress, 100); - } else { - pg.addProgress(progress, 90); - pg.addProgress(reload, 10); - } - - invalidate(); - pgBar.setProgress(pg); - validate(); - setEnabled(false); - - new Thread(new Runnable() { - @Override - public void run() { - try { - run.run(); - if (refreshBooks) { - refreshBooks(); - } - } finally { - reload.done(); - if (!pg.isDone()) { - // will trigger pgBar ActionListener: - pg.done(); - } - } - } - }, "outOfUi thread").start(); - } - - /** - * Process the given action in the main Swing UI thread. - *

    - * The code will make sure the current thread is the main UI thread and, if - * not, will switch to it before executing the runnable. - *

    - * Synchronous operation. - * - * @param run - * the action to run - */ - public void inUi(final Runnable run) { - if (EventQueue.isDispatchThread()) { - run.run(); - } else { - try { - EventQueue.invokeAndWait(run); - } catch (InterruptedException e) { - Instance.getInstance().getTraceHandler().error(e); - } catch (InvocationTargetException e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - } - - /** - * Import a {@link Story} into the main {@link LocalLibrary}. - *

    - * Should be called inside the UI thread. - * - * @param askUrl - * TRUE for an {@link URL}, false for a {@link File} - */ - public void imprt(boolean askUrl) { - JFileChooser fc = new JFileChooser(); - - Object url; - if (askUrl) { - String clipboard = ""; - try { - clipboard = ("" + Toolkit.getDefaultToolkit() - .getSystemClipboard().getData(DataFlavor.stringFlavor)) - .trim(); - } catch (Exception e) { - // No data will be handled - } - - if (clipboard == null || !(clipboard.startsWith("http://") || // - clipboard.startsWith("https://"))) { - clipboard = ""; - } - - url = JOptionPane.showInputDialog(GuiReaderMainPanel.this, - GuiReader.trans(StringIdGui.SUBTITLE_IMPORT_URL), - GuiReader.trans(StringIdGui.TITLE_IMPORT_URL), - JOptionPane.QUESTION_MESSAGE, null, null, clipboard); - } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { - url = fc.getSelectedFile().getAbsolutePath(); - } else { - url = null; - } - - if (url != null && !url.toString().isEmpty()) { - imprt(url.toString(), null, null); - } - } - - /** - * Actually import the {@link Story} into the main {@link LocalLibrary}. - *

    - * Should be called inside the UI thread. - * - * @param url - * the {@link Story} to import by {@link URL} - * @param onSuccess - * Action to execute on success - * @param onSuccessPgName - * the name to use for the onSuccess progress bar - */ - public void imprt(final String url, final MetaDataRunnable onSuccess, - String onSuccessPgName) { - final Progress pg = new Progress(); - final Progress pgImprt = new Progress(); - final Progress pgOnSuccess = new Progress(onSuccessPgName); - pg.addProgress(pgImprt, 95); - pg.addProgress(pgOnSuccess, 5); - - outOfUi(pg, true, new Runnable() { - @Override - public void run() { - Exception ex = null; - MetaData meta = null; - try { - meta = helper.getReader().getLibrary() - .imprt(BasicReader.getUrl(url), pgImprt); - } catch (IOException e) { - ex = e; - } - - final Exception e = ex; - - final boolean ok = (e == null); - - pgOnSuccess.setProgress(0); - if (!ok) { - if (e instanceof UnknownHostException) { - error(GuiReader.trans( - StringIdGui.ERROR_URL_NOT_SUPPORTED, url), - GuiReader.trans(StringIdGui.TITLE_ERROR), null); - } else { - error(GuiReader.trans( - StringIdGui.ERROR_URL_IMPORT_FAILED, url, - e.getMessage()), GuiReader - .trans(StringIdGui.TITLE_ERROR), e); - } - } else { - if (onSuccess != null) { - onSuccess.run(meta); - } - } - pgOnSuccess.done(); - } - }); - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Enabling or disabling this component will also affect its - * children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - if (bar != null) { - bar.setEnabled(b); - } - - for (GuiReaderGroup group : books.values()) { - group.setEnabled(b); - } - super.setEnabled(b); - repaint(); - } - - public void setWords(boolean words) { - this.words = words; - } - - public GuiReaderBook getSelectedBook() { - return selectedBook; - } - - public void unsetSelectedBook() { - selectedBook = null; - } - - private void addListPane(String name, List values, - final boolean type) { - GuiReader reader = helper.getReader(); - BasicLibrary lib = reader.getLibrary(); - - bookPane = new GuiReaderGroup(reader, name, color); - - List infos = new ArrayList(); - for (String value : values) { - if (type) { - infos.add(GuiReaderBookInfo.fromSource(lib, value)); - } else { - infos.add(GuiReaderBookInfo.fromAuthor(lib, value)); - } - } - - bookPane.refreshBooks(infos, words); - - this.invalidate(); - pane.invalidate(); - pane.add(bookPane); - pane.validate(); - this.validate(); - - bookPane.setActionListener(new BookActionListener() { - @Override - public void select(GuiReaderBook book) { - selectedBook = book; - } - - @Override - public void popupRequested(GuiReaderBook book, Component target, - int x, int y) { - JPopupMenu popup = helper.createSourceAuthorPopup(); - popup.show(target, x, y); - } - - @Override - public void action(final GuiReaderBook book) { - removeBookPanes(); - addBookPane(book.getInfo().getMainInfo(), type); - refreshBooks(); - } - }); - - focus(); - } - - /** - * Focus the first {@link GuiReaderGroup} we find. - */ - private void focus() { - GuiReaderGroup group = null; - Map books = this.books; - if (books.size() > 0) { - group = books.values().iterator().next(); - } - - if (group == null) { - group = bookPane; - } - - if (group != null) { - group.requestFocusInWindow(); - } - } - - /** - * Display an error message and log the linked {@link Exception}. - * - * @param message - * the message - * @param title - * the title of the error message - * @param e - * the exception to log if any - */ - private void error(final String message, final String title, Exception e) { - Instance.getInstance().getTraceHandler().error(title + ": " + message); - if (e != null) { - Instance.getInstance().getTraceHandler().error(e); - } - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - JOptionPane.showMessageDialog(GuiReaderMainPanel.this, message, - title, JOptionPane.ERROR_MESSAGE); - } - }); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java deleted file mode 100644 index 43c1a99..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java +++ /dev/null @@ -1,343 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.Color; -import java.awt.LayoutManager; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import be.nikiroo.fanfix.Instance; - -/** - * A Swing-based navigation bar, that displays first/previous/next/last page - * buttons. - * - * @author niki - */ -public class GuiReaderNavBar extends JPanel { - private static final long serialVersionUID = 1L; - - private JLabel label; - private int index = 0; - private int min = 0; - private int max = 0; - private JButton[] navButtons; - String extraLabel = null; - - private List listeners = new ArrayList(); - - /** - * Create a new navigation bar. - *

    - * The minimum must be lower or equal to the maximum. - *

    - * Note than a max of "-1" means "infinite". - * - * @param min - * the minimum page number (cannot be negative) - * @param max - * the maximum page number (cannot be lower than min, except if - * -1 (infinite)) - * - * @throws IndexOutOfBoundsException - * if min > max and max is not "-1" - */ - public GuiReaderNavBar(int min, int max) { - if (min > max && max != -1) { - throw new IndexOutOfBoundsException(String.format( - "min (%d) > max (%d)", min, max)); - } - - LayoutManager layout = new BoxLayout(this, BoxLayout.X_AXIS); - setLayout(layout); - - // TODO: - // JButton up = new BasicArrowButton(BasicArrowButton.NORTH); - // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH); - - navButtons = new JButton[4]; - - navButtons[0] = createNavButton("<<", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setIndex(GuiReaderNavBar.this.min); - fireEvent(); - } - }); - navButtons[1] = createNavButton(" < ", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setIndex(index - 1); - fireEvent(); - } - }); - navButtons[2] = createNavButton(" > ", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setIndex(index + 1); - fireEvent(); - } - }); - navButtons[3] = createNavButton(">>", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setIndex(GuiReaderNavBar.this.max); - fireEvent(); - } - }); - - for (JButton navButton : navButtons) { - add(navButton); - } - - label = new JLabel(""); - add(label); - - this.min = min; - this.max = max; - this.index = min; - - updateEnabled(); - updateLabel(); - fireEvent(); - } - - /** - * The current index, must be between {@link GuiReaderNavBar#min} and - * {@link GuiReaderNavBar#max}, both inclusive. - * - * @return the index - */ - public int getIndex() { - return index; - } - - /** - * The current index, must be between {@link GuiReaderNavBar#min} and - * {@link GuiReaderNavBar#max}, both inclusive. - * - * @param index - * the new index - */ - public void setIndex(int index) { - if (index != this.index) { - if (index < min || (index > max && max != -1)) { - throw new IndexOutOfBoundsException(String.format( - "Index %d but min/max is [%d/%d]", index, min, max)); - } - - this.index = index; - updateLabel(); - } - - updateEnabled(); - } - - /** - * The minimun page number. Cannot be negative. - * - * @return the min - */ - public int getMin() { - return min; - } - - /** - * The minimum page number. Cannot be negative. - *

    - * May update the index if needed (if the index is < the new min). - *

    - * Will also (always) update the label and enable/disable the required - * buttons. - * - * @param min - * the new min - */ - public void setMin(int min) { - this.min = min; - if (index < min) { - index = min; - } - updateEnabled(); - updateLabel(); - - } - - /** - * The maximum page number. Cannot be lower than min, except if -1 - * (infinite). - * - * @return the max - */ - public int getMax() { - return max; - } - - /** - * The maximum page number. Cannot be lower than min, except if -1 - * (infinite). - *

    - * May update the index if needed (if the index is > the new max). - *

    - * Will also (always) update the label and enable/disable the required - * buttons. - * - * @param max - * the new max - */ - public void setMax(int max) { - this.max = max; - if (index > max && max != -1) { - index = max; - } - updateEnabled(); - updateLabel(); - } - - /** - * The current extra label to display with the default - * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation. - * - * @return the current label - */ - public String getExtraLabel() { - return extraLabel; - } - - /** - * The current extra label to display with the default - * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation. - * - * @param currentLabel - * the new current label - */ - public void setExtraLabel(String currentLabel) { - this.extraLabel = currentLabel; - updateLabel(); - } - - /** - * Add a listener that will be called on each page change. - * - * @param listener - * the new listener - */ - public void addActionListener(ActionListener listener) { - listeners.add(listener); - } - - /** - * Remove the given listener if possible. - * - * @param listener - * the listener to remove - * @return TRUE if it was removed, FALSE if it was not found - */ - public boolean removeActionListener(ActionListener listener) { - return listeners.remove(listener); - } - - /** - * Remove all the listeners. - */ - public void clearActionsListeners() { - listeners.clear(); - } - - /** - * Notify a change of page. - */ - public void fireEvent() { - for (ActionListener listener : listeners) { - try { - listener.actionPerformed(new ActionEvent(this, - ActionEvent.ACTION_FIRST, "page changed")); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - } - } - - /** - * Create a single navigation button. - * - * @param text - * the text to display - * @param action - * the action to take on click - * @return the button - */ - private JButton createNavButton(String text, ActionListener action) { - JButton navButton = new JButton(text); - navButton.addActionListener(action); - navButton.setForeground(Color.BLUE); - return navButton; - } - - /** - * Update the label displayed in the UI. - */ - private void updateLabel() { - label.setText(computeLabel(index, min, max)); - } - - /** - * Update the navigation buttons "enabled" state according to the current - * index value. - */ - private void updateEnabled() { - navButtons[0].setEnabled(index > min); - navButtons[1].setEnabled(index > min); - navButtons[2].setEnabled(index < max || max == -1); - navButtons[3].setEnabled(index < max || max == -1); - } - - /** - * Return the label to display for the given index. - *

    - * Swing HTML (HTML3) is supported if surrounded by <HTML> and - * </HTML>. - *

    - * By default, return "Page 1/5: current_label" (with the current index and - * {@link GuiReaderNavBar#getCurrentLabel()}). - * - * @param index - * the new index number - * @param mix - * the minimum index (inclusive) - * @param max - * the maximum index (inclusive) - * @return the label - */ - protected String computeLabel(int index, - @SuppressWarnings("unused") int min, int max) { - - String base = "  Page %d "; - if (max >= 0) { - base += "/ %d"; - } - base += ""; - - String ifLabel = ": %s"; - - String display = base; - String label = getExtraLabel(); - if (label != null && !label.trim().isEmpty()) { - display += ifLabel; - } - - display = "" + display + ""; - - if (max >= 0) { - return String.format(display, index, max, label); - } - - return String.format(display, index, label); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java deleted file mode 100644 index 9615d75..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java +++ /dev/null @@ -1,40 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; - -import javax.swing.JFrame; - -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; - -/** - * A frame displaying properties and other information of a {@link Story}. - * - * @author niki - */ -public class GuiReaderPropertiesFrame extends JFrame { - private static final long serialVersionUID = 1L; - - /** - * Create a new {@link GuiReaderPropertiesFrame}. - * - * @param lib - * the library to use for the cover image - * @param meta - * the meta to describe - */ - public GuiReaderPropertiesFrame(BasicLibrary lib, MetaData meta) { - setTitle(GuiReader.trans(StringIdGui.TITLE_STORY, meta.getLuid(), - meta.getTitle())); - - GuiReaderPropertiesPane desc = new GuiReaderPropertiesPane(lib, meta); - setSize(800, - (int) desc.getPreferredSize().getHeight() + 2 - * desc.getBorderThickness()); - - setLayout(new BorderLayout()); - add(desc, BorderLayout.NORTH); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java deleted file mode 100644 index 2c9c7e7..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java +++ /dev/null @@ -1,99 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Font; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextArea; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.reader.BasicReader; - -/** - * A panel displaying properties and other information of a {@link Story}. - * - * @author niki - */ -public class GuiReaderPropertiesPane extends JPanel { - private static final long serialVersionUID = 1L; - private final int space = 10; - - /** - * Create a new {@link GuiReaderPropertiesPane}. - * - * @param lib - * the library to use for the cover image - * @param meta - * the meta to describe - */ - public GuiReaderPropertiesPane(BasicLibrary lib, MetaData meta) { - // Image - ImageIcon img = GuiReaderCoverImager.generateCoverIcon(lib, meta); - - setLayout(new BorderLayout()); - - // Main panel - JPanel mainPanel = new JPanel(new BorderLayout()); - JPanel mainPanelKeys = new JPanel(); - mainPanelKeys.setLayout(new BoxLayout(mainPanelKeys, BoxLayout.Y_AXIS)); - JPanel mainPanelValues = new JPanel(); - mainPanelValues.setLayout(new BoxLayout(mainPanelValues, - BoxLayout.Y_AXIS)); - - mainPanel.add(mainPanelKeys, BorderLayout.WEST); - mainPanel.add(mainPanelValues, BorderLayout.CENTER); - - Map desc = BasicReader.getMetaDesc(meta); - - Color trans = new Color(0, 0, 0, 1); - Color base = mainPanelValues.getBackground(); - for (String key : desc.keySet()) { - JTextArea jKey = new JTextArea(key); - jKey.setFont(new Font(jKey.getFont().getFontName(), Font.BOLD, jKey - .getFont().getSize())); - jKey.setEditable(false); - jKey.setLineWrap(false); - jKey.setBackground(trans); - mainPanelKeys.add(jKey); - - final JTextArea jValue = new JTextArea(desc.get(key)); - jValue.setEditable(false); - jValue.setLineWrap(false); - jValue.setBackground(base); - mainPanelValues.add(jValue); - } - - // Image - JLabel imgLabel = new JLabel(img); - imgLabel.setVerticalAlignment(JLabel.TOP); - - // Borders - mainPanelKeys.setBorder(BorderFactory.createEmptyBorder(space, space, - space, space)); - mainPanelValues.setBorder(BorderFactory.createEmptyBorder(space, space, - space, space)); - imgLabel.setBorder(BorderFactory.createEmptyBorder(0, space, space, 0)); - - // Add all - add(imgLabel, BorderLayout.WEST); - add(mainPanel, BorderLayout.CENTER); - } - - /** - * The invisible border size (multiply by 2 if you need the total width or - * the total height). - * - * @return the invisible border thickness - */ - public int getBorderThickness() { - return space; - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java deleted file mode 100644 index 6d91a0c..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java +++ /dev/null @@ -1,91 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.net.URL; - -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JPanel; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.ui.ProgressBar; - -public class GuiReaderSearchAction extends JFrame { - private static final long serialVersionUID = 1L; - - private GuiReaderBookInfo info; - private ProgressBar pgBar; - - public GuiReaderSearchAction(BasicLibrary lib, GuiReaderBookInfo info) { - super(info.getMainInfo()); - this.setSize(800, 600); - this.info = info; - - setLayout(new BorderLayout()); - - JPanel main = new JPanel(new BorderLayout()); - JPanel props = new GuiReaderPropertiesPane(lib, info.getMeta()); - - main.add(props, BorderLayout.NORTH); - main.add(new GuiReaderViewerPanel(info.getMeta(), info.getMeta() - .isImageDocument()), BorderLayout.CENTER); - main.add(createImportButton(lib), BorderLayout.SOUTH); - - add(main, BorderLayout.CENTER); - - pgBar = new ProgressBar(); - pgBar.setVisible(false); - add(pgBar, BorderLayout.SOUTH); - - pgBar.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - pgBar.invalidate(); - pgBar.setProgress(null); - setEnabled(true); - validate(); - } - }); - - pgBar.addUpdateListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - pgBar.invalidate(); - validate(); - repaint(); - } - }); - } - - private Component createImportButton(final BasicLibrary lib) { - JButton imprt = new JButton("Import into library"); - imprt.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - final Progress pg = new Progress(); - pgBar.setProgress(pg); - - new Thread(new Runnable() { - @Override - public void run() { - try { - lib.imprt(new URL(info.getMeta().getUrl()), null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - - pg.done(); - } - }).start(); - } - }); - - return imprt; - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java deleted file mode 100644 index ebdb21a..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java +++ /dev/null @@ -1,246 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable; -import be.nikiroo.fanfix.searchable.BasicSearchable; - -/** - * This panel represents a search panel that works for keywords and tags based - * searches. - * - * @author niki - */ -public class GuiReaderSearchByNamePanel extends JPanel { - private static final long serialVersionUID = 1L; - - private BasicSearchable searchable; - - private JTextField keywordsField; - private JButton submitKeywords; - - private int page; - private int maxPage; - private List stories = new ArrayList(); - private int storyItem; - - public GuiReaderSearchByNamePanel(final Waitable waitable) { - super(new BorderLayout()); - - keywordsField = new JTextField(); - add(keywordsField, BorderLayout.CENTER); - - submitKeywords = new JButton("Search"); - add(submitKeywords, BorderLayout.EAST); - - // should be done out of UI - final Runnable go = new Runnable() { - @Override - public void run() { - waitable.setWaiting(true); - try { - search(keywordsField.getText(), 1, 0); - waitable.fireEvent(); - } finally { - waitable.setWaiting(false); - } - } - }; - - keywordsField.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - new Thread(go).start(); - } else { - super.keyReleased(e); - } - } - }); - - submitKeywords.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - new Thread(go).start(); - } - }); - - setSearchable(null); - } - - /** - * The {@link BasicSearchable} object use for the searches themselves. - *

    - * Can be NULL, but no searches will work. - * - * @param searchable - * the new searchable - */ - public void setSearchable(BasicSearchable searchable) { - this.searchable = searchable; - page = 0; - maxPage = -1; - storyItem = 0; - stories = new ArrayList(); - updateKeywords(""); - } - - /** - * The currently displayed page of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByNamePanel#search(String, int, int)}). - * - * @return the currently displayed page of results - */ - public int getPage() { - return page; - } - - /** - * The number of pages of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByPanel#search(String, int, int)}). - *

    - * For an unknown number or when not applicable, -1 is returned. - * - * @return the number of pages of results or -1 - */ - public int getMaxPage() { - return maxPage; - } - - /** - * Return the keywords used for the current search. - * - * @return the keywords - */ - public String getCurrentKeywords() { - return keywordsField.getText(); - } - - /** - * The currently loaded stories (the result of the latest search). - * - * @return the stories - */ - public List getStories() { - return stories; - } - - /** - * Return the currently selected story (the item) if it was - * specified in the latest, or 0 if not. - *

    - * Note: this is thus a 1-based index, not a 0-based index. - * - * @return the item - */ - public int getStoryItem() { - return storyItem; - } - - /** - * Update the keywords displayed on screen. - * - * @param keywords - * the keywords - */ - private void updateKeywords(final String keywords) { - if (!keywords.equals(keywordsField.getText())) { - GuiReaderSearchFrame.inUi(new Runnable() { - @Override - public void run() { - keywordsField.setText(keywords); - } - }); - } - } - - /** - * Search for the given terms on the currently selected searchable. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param keywords - * the keywords to search for - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - * - * @throw IndexOutOfBoundsException if the page is out of bounds - */ - public void search(String keywords, int page, int item) { - List stories = new ArrayList(); - int storyItem = 0; - - updateKeywords(keywords); - - int maxPage = -1; - if (searchable != null) { - try { - maxPage = searchable.searchPages(keywords); - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } - - if (page > 0) { - if (maxPage >= 0 && (page <= 0 || page > maxPage)) { - throw new IndexOutOfBoundsException("Page " + page + " out of " - + maxPage); - } - - if (searchable != null) { - try { - stories = searchable.search(keywords, page); - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } - - if (item > 0 && item <= stories.size()) { - storyItem = item; - } else if (item > 0) { - GuiReaderSearchFrame.error(String.format( - "Story item does not exist: Search [%s], item %d", - keywords, item)); - } - } - - this.page = page; - this.maxPage = maxPage; - this.stories = stories; - this.storyItem = storyItem; - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - super.setEnabled(b); - keywordsField.setEnabled(b); - submitKeywords.setEnabled(b); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java deleted file mode 100644 index 8f95d4c..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java +++ /dev/null @@ -1,281 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.util.List; - -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.searchable.SearchableTag; -import be.nikiroo.fanfix.supported.SupportType; - -/** - * This panel represents a search panel that works for keywords and tags based - * searches. - * - * @author niki - */ -public class GuiReaderSearchByPanel extends JPanel { - private static final long serialVersionUID = 1L; - - private Waitable waitable; - - private boolean searchByTags; - private JTabbedPane searchTabs; - private GuiReaderSearchByNamePanel byName; - private GuiReaderSearchByTagPanel byTag; - - /** - * This interface represents an item that wan be put in "wait" mode. It is - * supposed to be used for long running operations during which we want to - * disable UI interactions. - *

    - * It also allows reporting an event to the item. - * - * @author niki - */ - public interface Waitable { - /** - * Set the item in wait mode, blocking it from accepting UI input. - * - * @param waiting - * TRUE for wait more, FALSE to restore normal mode - */ - public void setWaiting(boolean waiting); - - /** - * Notify the {@link Waitable} that an event occured (i.e., new stories - * were found). - */ - public void fireEvent(); - } - - /** - * Create a new {@link GuiReaderSearchByPanel}. - * - * @param waitable - * the waitable we can wait on for long UI operations - */ - public GuiReaderSearchByPanel(Waitable waitable) { - setLayout(new BorderLayout()); - - this.waitable = waitable; - searchByTags = false; - - byName = new GuiReaderSearchByNamePanel(waitable); - byTag = new GuiReaderSearchByTagPanel(waitable); - - searchTabs = new JTabbedPane(); - searchTabs.addTab("By name", byName); - searchTabs.addTab("By tags", byTag); - searchTabs.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - searchByTags = (searchTabs.getSelectedComponent() == byTag); - } - }); - - add(searchTabs, BorderLayout.CENTER); - updateSearchBy(searchByTags); - } - - /** - * Set the new {@link SupportType}. - *

    - * This operation can be long and should be run outside the UI thread. - *

    - * Note that if a non-searchable {@link SupportType} is used, an - * {@link IllegalArgumentException} will be thrown. - * - * @param supportType - * the support mode, must be searchable or NULL - * - * @throws IllegalArgumentException - * if the {@link SupportType} is not NULL but not searchable - * (see {@link BasicSearchable#getSearchable(SupportType)}) - */ - public void setSupportType(SupportType supportType) { - BasicSearchable searchable = BasicSearchable.getSearchable(supportType); - if (searchable == null && supportType != null) { - throw new IllegalArgumentException("Unupported support type: " - + supportType); - } - - byName.setSearchable(searchable); - byTag.setSearchable(searchable); - } - - /** - * The currently displayed page of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByPanel#search(String, int, int)} or - * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)} - * ). - * - * @return the currently displayed page of results - */ - public int getPage() { - if (!searchByTags) { - return byName.getPage(); - } - - return byTag.getPage(); - } - - /** - * The number of pages of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByPanel#search(String, int, int)} or - * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)} - * ). - *

    - * For an unknown number or when not applicable, -1 is returned. - * - * @return the number of pages of results or -1 - */ - public int getMaxPage() { - if (!searchByTags) { - return byName.getMaxPage(); - } - - return byTag.getMaxPage(); - } - - /** - * Set the page of results to display for the current search. This will - * cause {@link Waitable#fireEvent()} to be called if needed. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param page - * the page of results to set - * - * @throw IndexOutOfBoundsException if the page is out of bounds - */ - public void setPage(int page) { - if (searchByTags) { - searchTag(byTag.getCurrentTag(), page, 0); - } else { - search(byName.getCurrentKeywords(), page, 0); - } - } - - /** - * The currently loaded stories (the result of the latest search). - * - * @return the stories - */ - public List getStories() { - if (!searchByTags) { - return byName.getStories(); - } - - return byTag.getStories(); - } - - /** - * Return the currently selected story (the item) if it was - * specified in the latest, or 0 if not. - *

    - * Note: this is thus a 1-based index, not a 0-based index. - * - * @return the item - */ - public int getStoryItem() { - if (!searchByTags) { - return byName.getStoryItem(); - } - - return byTag.getStoryItem(); - } - - /** - * Update the kind of searches to make: search by keywords or search by tags - * (it will impact what the user can see and interact with on the UI). - * - * @param byTag - * TRUE for tag-based searches, FALSE for keywords-based searches - */ - private void updateSearchBy(final boolean byTag) { - GuiReaderSearchFrame.inUi(new Runnable() { - @Override - public void run() { - if (!byTag) { - searchTabs.setSelectedIndex(0); - } else { - searchTabs.setSelectedIndex(1); - } - } - }); - } - - /** - * Search for the given terms on the currently selected searchable. This - * will cause {@link Waitable#fireEvent()} to be called if needed. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param keywords - * the keywords to search for - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - * - * @throw IndexOutOfBoundsException if the page is out of bounds - */ - public void search(final String keywords, final int page, final int item) { - updateSearchBy(false); - byName.search(keywords, page, item); - waitable.fireEvent(); - } - - /** - * Search for the given tag on the currently selected searchable. This will - * cause {@link Waitable#fireEvent()} to be called if needed. - *

    - * If the tag contains children tags, those will be displayed so you can - * select them; if the tag is a leaf tag, the linked stories will be - * displayed. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param tag - * the tag to search for, or NULL for base tags - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - * - * @throw IndexOutOfBoundsException if the page is out of bounds - */ - public void searchTag(final SearchableTag tag, final int page, - final int item) { - updateSearchBy(true); - byTag.searchTag(tag, page, item); - waitable.fireEvent(); - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - super.setEnabled(b); - searchTabs.setEnabled(b); - byName.setEnabled(b); - byTag.setEnabled(b); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java deleted file mode 100644 index 260fc48..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java +++ /dev/null @@ -1,458 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BoxLayout; -import javax.swing.JComboBox; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.ListCellRenderer; - -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.searchable.SearchableTag; -import be.nikiroo.fanfix.supported.SupportType; - -/** - * This panel represents a search panel that works for keywords and tags based - * searches. - * - * @author niki - */ -// JCombobox not 1.6 compatible -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class GuiReaderSearchByTagPanel extends JPanel { - private static final long serialVersionUID = 1L; - - private BasicSearchable searchable; - private Waitable waitable; - - private SearchableTag currentTag; - private JPanel tagBars; - private List combos; - - private int page; - private int maxPage; - private List stories = new ArrayList(); - private int storyItem; - - public GuiReaderSearchByTagPanel(Waitable waitable) { - setLayout(new BorderLayout()); - - this.waitable = waitable; - combos = new ArrayList(); - page = 0; - maxPage = -1; - - tagBars = new JPanel(); - tagBars.setLayout(new BoxLayout(tagBars, BoxLayout.Y_AXIS)); - add(tagBars, BorderLayout.NORTH); - } - - /** - * The {@link BasicSearchable} object use for the searches themselves. - *

    - * This operation can be long and should be run outside the UI thread. - *

    - * Can be NULL, but no searches will work. - * - * @param searchable - * the new searchable - */ - public void setSearchable(BasicSearchable searchable) { - this.searchable = searchable; - page = 0; - maxPage = -1; - storyItem = 0; - stories = new ArrayList(); - updateTags(null); - } - - /** - * The currently displayed page of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByTagPanel#searchTag(SupportType, int, int, SearchableTag)} - * ). - * - * @return the currently displayed page of results - */ - public int getPage() { - return page; - } - - /** - * The number of pages of result for the current search (see the - * page parameter of - * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)} - * ). - *

    - * For an unknown number or when not applicable, -1 is returned. - * - * @return the number of pages of results or -1 - */ - public int getMaxPage() { - return maxPage; - } - - /** - * Return the tag used for the current search. - * - * @return the tag (which can be NULL, for "base tags") - */ - public SearchableTag getCurrentTag() { - return currentTag; - } - - /** - * The currently loaded stories (the result of the latest search). - * - * @return the stories - */ - public List getStories() { - return stories; - } - - /** - * Return the currently selected story (the item) if it was - * specified in the latest, or 0 if not. - *

    - * Note: this is thus a 1-based index, not a 0-based index. - * - * @return the item - */ - public int getStoryItem() { - return storyItem; - } - - /** - * Update the tags displayed on screen and reset the tags bar. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param tag - * the tag to use, or NULL for base tags - */ - private void updateTags(final SearchableTag tag) { - final List parents = new ArrayList(); - SearchableTag parent = (tag == null) ? null : tag; - while (parent != null) { - parents.add(parent); - parent = parent.getParent(); - } - - List rootTags = new ArrayList(); - SearchableTag selectedRootTag = null; - selectedRootTag = parents.isEmpty() ? null : parents - .get(parents.size() - 1); - - if (searchable != null) { - try { - rootTags = searchable.getTags(); - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } - - final List rootTagsF = rootTags; - final SearchableTag selectedRootTagF = selectedRootTag; - - GuiReaderSearchFrame.inUi(new Runnable() { - @Override - public void run() { - tagBars.invalidate(); - tagBars.removeAll(); - - addTagBar(rootTagsF, selectedRootTagF); - - for (int i = parents.size() - 1; i >= 0; i--) { - SearchableTag selectedChild = null; - if (i > 0) { - selectedChild = parents.get(i - 1); - } - - SearchableTag parent = parents.get(i); - addTagBar(parent.getChildren(), selectedChild); - } - - tagBars.validate(); - } - }); - } - - /** - * Add a tags bar (do not remove possible previous ones). - *

    - * Will always add an "empty" (NULL) tag as first option. - * - * @param tags - * the tags to display - * @param selected - * the selected tag if any, or NULL for none - */ - private void addTagBar(List tags, - final SearchableTag selected) { - tags.add(0, null); - - final int comboIndex = combos.size(); - - final JComboBox combo = new JComboBox( - tags.toArray(new SearchableTag[] {})); - combo.setSelectedItem(selected); - - final ListCellRenderer basic = combo.getRenderer(); - - combo.setRenderer(new ListCellRenderer() { - @Override - public Component getListCellRendererComponent(JList list, - Object value, int index, boolean isSelected, - boolean cellHasFocus) { - - Object displayValue = value; - if (value instanceof SearchableTag) { - displayValue = ((SearchableTag) value).getName(); - } else { - displayValue = "Select a tag..."; - cellHasFocus = false; - isSelected = false; - } - - Component rep = basic.getListCellRendererComponent(list, - displayValue, index, isSelected, cellHasFocus); - - if (value == null) { - rep.setForeground(Color.GRAY); - } - - return rep; - } - }); - - combo.addActionListener(createComboTagAction(comboIndex)); - - combos.add(combo); - tagBars.add(combo); - } - - /** - * The action to do on {@link JComboBox} selection. - *

    - * The content of the action is: - *

    - * - * @param comboIndex - * the index of the related {@link JComboBox} - * - * @return the action - */ - private ActionListener createComboTagAction(final int comboIndex) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - List combos = GuiReaderSearchByTagPanel.this.combos; - if (combos == null || comboIndex < 0 - || comboIndex >= combos.size()) { - return; - } - - // Tag can be NULL - final SearchableTag tag = (SearchableTag) combos - .get(comboIndex).getSelectedItem(); - - while (comboIndex + 1 < combos.size()) { - JComboBox combo = combos.remove(comboIndex + 1); - tagBars.remove(combo); - } - - new Thread(new Runnable() { - @Override - public void run() { - waitable.setWaiting(true); - try { - final List children = getChildrenForTag(tag); - if (children != null) { - GuiReaderSearchFrame.inUi(new Runnable() { - @Override - public void run() { - addTagBar(children, tag); - } - }); - } - - if (tag != null && tag.isLeaf()) { - storyItem = 0; - try { - searchable.fillTag(tag); - page = 1; - stories = searchable.search(tag, 1); - maxPage = searchable.searchPages(tag); - currentTag = tag; - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - page = 0; - maxPage = -1; - stories = new ArrayList(); - } - - waitable.fireEvent(); - } - } finally { - waitable.setWaiting(false); - } - } - }).start(); - } - }; - } - - /** - * Get the children of the given tag (or the base tags if the given tag is - * NULL). - *

    - * This action will "fill" ({@link BasicSearchable#fillTag(SearchableTag)}) - * the given tag if needed first. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param tag - * the tag to search into or NULL for the base tags - * @return the children - */ - private List getChildrenForTag(final SearchableTag tag) { - List children = new ArrayList(); - if (tag == null) { - try { - List baseTags = searchable.getTags(); - children = baseTags; - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } else { - try { - searchable.fillTag(tag); - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - - if (!tag.isLeaf()) { - children = tag.getChildren(); - } else { - children = null; - } - } - - return children; - } - - /** - * Search for the given tag on the currently selected searchable. - *

    - * If the tag contains children tags, those will be displayed so you can - * select them; if the tag is a leaf tag, the linked stories will be - * displayed. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param tag - * the tag to search for, or NULL for base tags - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - * - * @throw IndexOutOfBoundsException if the page is out of bounds - */ - public void searchTag(SearchableTag tag, int page, int item) { - List stories = new ArrayList(); - int storyItem = 0; - - currentTag = tag; - updateTags(tag); - - int maxPage = -1; - if (tag != null) { - try { - searchable.fillTag(tag); - - if (!tag.isLeaf()) { - List subtags = tag.getChildren(); - if (item > 0 && item <= subtags.size()) { - SearchableTag subtag = subtags.get(item - 1); - try { - tag = subtag; - searchable.fillTag(tag); - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } else if (item > 0) { - GuiReaderSearchFrame.error(String.format( - "Tag item does not exist: Tag [%s], item %d", - tag.getFqName(), item)); - } - } - - maxPage = searchable.searchPages(tag); - if (page > 0 && tag.isLeaf()) { - if (maxPage >= 0 && (page <= 0 || page > maxPage)) { - throw new IndexOutOfBoundsException("Page " + page - + " out of " + maxPage); - } - - try { - stories = searchable.search(tag, page); - if (item > 0 && item <= stories.size()) { - storyItem = item; - } else if (item > 0) { - GuiReaderSearchFrame - .error(String - .format("Story item does not exist: Tag [%s], item %d", - tag.getFqName(), item)); - } - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - } - } - } catch (IOException e) { - GuiReaderSearchFrame.error(e); - maxPage = 0; - } - } - - this.stories = stories; - this.storyItem = storyItem; - this.page = page; - this.maxPage = maxPage; - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - super.setEnabled(b); - tagBars.setEnabled(b); - for (JComboBox combo : combos) { - combo.setEnabled(b); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java deleted file mode 100644 index def9fc6..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java +++ /dev/null @@ -1,380 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.EventQueue; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JComboBox; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; -import be.nikiroo.fanfix.searchable.BasicSearchable; -import be.nikiroo.fanfix.searchable.SearchableTag; -import be.nikiroo.fanfix.supported.SupportType; - -/** - * This frame will allow you to search through the supported websites for new - * stories/comics. - * - * @author niki - */ -// JCombobox not 1.6 compatible -@SuppressWarnings({ "unchecked", "rawtypes" }) -public class GuiReaderSearchFrame extends JFrame { - private static final long serialVersionUID = 1L; - - private List supportTypes; - - private JComboBox comboSupportTypes; - private ActionListener comboSupportTypesListener; - private GuiReaderSearchByPanel searchPanel; - private GuiReaderNavBar navbar; - - private boolean seeWordcount; - private GuiReaderGroup books; - - public GuiReaderSearchFrame(final GuiReader reader) { - super("Browse stories"); - setLayout(new BorderLayout()); - setSize(800, 600); - - supportTypes = new ArrayList(); - supportTypes.add(null); - for (SupportType type : SupportType.values()) { - if (BasicSearchable.getSearchable(type) != null) { - supportTypes.add(type); - } - } - - comboSupportTypes = new JComboBox( - supportTypes.toArray(new SupportType[] {})); - - comboSupportTypesListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final SupportType support = (SupportType) comboSupportTypes - .getSelectedItem(); - setWaiting(true); - new Thread(new Runnable() { - @Override - public void run() { - try { - updateSupportType(support); - } finally { - setWaiting(false); - } - } - }).start(); - } - }; - comboSupportTypes.addActionListener(comboSupportTypesListener); - - JPanel searchSites = new JPanel(new BorderLayout()); - searchSites.add(comboSupportTypes, BorderLayout.CENTER); - searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST); - - searchPanel = new GuiReaderSearchByPanel( - new GuiReaderSearchByPanel.Waitable() { - @Override - public void setWaiting(boolean waiting) { - GuiReaderSearchFrame.this.setWaiting(waiting); - } - - @Override - public void fireEvent() { - updatePages(searchPanel.getPage(), - searchPanel.getMaxPage()); - List infos = new ArrayList(); - for (MetaData meta : searchPanel.getStories()) { - infos.add(GuiReaderBookInfo.fromMeta(meta)); - } - - int page = searchPanel.getPage(); - if (page <= 0) { - navbar.setMin(1); - navbar.setMax(1); - } else { - int max = searchPanel.getMaxPage(); - navbar.setMin(1); - navbar.setMax(max); - navbar.setIndex(page); - } - updateBooks(infos); - - // ! 1-based index ! - int item = searchPanel.getStoryItem(); - if (item > 0 && item <= books.getBooksCount()) { - books.setSelectedBook(item - 1, false); - } - } - }); - - JPanel top = new JPanel(new BorderLayout()); - top.add(searchSites, BorderLayout.NORTH); - top.add(searchPanel, BorderLayout.CENTER); - - add(top, BorderLayout.NORTH); - - books = new GuiReaderGroup(reader, null, null); - books.setActionListener(new BookActionListener() { - @Override - public void select(GuiReaderBook book) { - } - - @Override - public void popupRequested(GuiReaderBook book, Component target, - int x, int y) { - } - - @Override - public void action(GuiReaderBook book) { - new GuiReaderSearchAction(reader.getLibrary(), book.getInfo()) - .setVisible(true); - } - }); - JScrollPane scroll = new JScrollPane(books); - scroll.getVerticalScrollBar().setUnitIncrement(16); - add(scroll, BorderLayout.CENTER); - - navbar = new GuiReaderNavBar(-1, -1) { - private static final long serialVersionUID = 1L; - - @Override - protected String computeLabel(int index, int min, int max) { - if (index <= 0) { - return ""; - } - return super.computeLabel(index, min, max); - } - }; - - navbar.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - searchPanel.setPage(navbar.getIndex()); - } - }); - - add(navbar, BorderLayout.SOUTH); - } - - /** - * Update the {@link SupportType} currently displayed to the user. - *

    - * Will also cause a search for the new base tags of the given support if - * not NULL. - *

    - * This operation can be long and should be run outside the UI thread. - * - * @param supportType - * the new {@link SupportType} - */ - private void updateSupportType(final SupportType supportType) { - inUi(new Runnable() { - @Override - public void run() { - books.clear(); - - comboSupportTypes - .removeActionListener(comboSupportTypesListener); - comboSupportTypes.setSelectedItem(supportType); - comboSupportTypes.addActionListener(comboSupportTypesListener); - } - }); - - searchPanel.setSupportType(supportType); - } - - /** - * Update the pages and the lined buttons currently displayed on screen. - *

    - * Those are the same pages and maximum pages used by - * {@link GuiReaderSearchByPanel#search(String, int, int)} and - * {@link GuiReaderSearchByPanel#searchTag(SearchableTag, int, int)}. - * - * @param page - * the current page of results - * @param maxPage - * the maximum number of pages of results - */ - private void updatePages(final int page, final int maxPage) { - inUi(new Runnable() { - @Override - public void run() { - if (maxPage >= 1) { - navbar.setMin(1); - navbar.setMax(maxPage); - navbar.setIndex(page); - } else { - navbar.setMin(-1); - navbar.setMax(-1); - } - } - }); - } - - /** - * Update the currently displayed books. - * - * @param infos - * the new books - */ - private void updateBooks(final List infos) { - inUi(new Runnable() { - @Override - public void run() { - books.refreshBooks(infos, seeWordcount); - } - }); - } - - /** - * Search for the given terms on the currently selected searchable. This - * will update the displayed books if needed. - *

    - * This operation is asynchronous. - * - * @param keywords - * the keywords to search for - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - */ - public void search(final SupportType searchOn, final String keywords, - final int page, final int item) { - setWaiting(true); - new Thread(new Runnable() { - @Override - public void run() { - try { - updateSupportType(searchOn); - searchPanel.search(keywords, page, item); - } finally { - setWaiting(false); - } - } - }).start(); - } - - /** - * Search for the given tag on the currently selected searchable. This will - * update the displayed books if needed. - *

    - * If the tag contains children tags, those will be displayed so you can - * select them; if the tag is a leaf tag, the linked stories will be - * displayed. - *

    - * This operation is asynchronous. - * - * @param tag - * the tag to search for, or NULL for base tags - * @param page - * the page of results to load - * @param item - * the item to select (or 0 for none by default) - */ - public void searchTag(final SupportType searchOn, final int page, - final int item, final SearchableTag tag) { - setWaiting(true); - new Thread(new Runnable() { - @Override - public void run() { - try { - updateSupportType(searchOn); - searchPanel.searchTag(tag, page, item); - } finally { - setWaiting(false); - } - } - }).start(); - } - - /** - * Process the given action in the main Swing UI thread. - *

    - * The code will make sure the current thread is the main UI thread and, if - * not, will switch to it before executing the runnable. - *

    - * Synchronous operation. - * - * @param run - * the action to run - */ - static void inUi(final Runnable run) { - if (EventQueue.isDispatchThread()) { - run.run(); - } else { - try { - EventQueue.invokeAndWait(run); - } catch (InterruptedException e) { - error(e); - } catch (InvocationTargetException e) { - error(e); - } - } - } - - /** - * An error occurred, inform the user and/or log the error. - * - * @param e - * the error - */ - static void error(Exception e) { - Instance.getInstance().getTraceHandler().error(e); - } - - /** - * An error occurred, inform the user and/or log the error. - * - * @param e - * the error message - */ - static void error(String e) { - Instance.getInstance().getTraceHandler().error(e); - } - - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

    - * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - super.setEnabled(b); - books.setEnabled(b); - searchPanel.setEnabled(b); - } - - /** - * Set the item in wait mode, blocking it from accepting UI input. - * - * @param waiting - * TRUE for wait more, FALSE to restore normal mode - */ - private void setWaiting(final boolean waiting) { - inUi(new Runnable() { - @Override - public void run() { - GuiReaderSearchFrame.this.setEnabled(!waiting); - } - }); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java deleted file mode 100644 index bfb1892..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java +++ /dev/null @@ -1,158 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.Font; -import java.awt.LayoutManager; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingConstants; - -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; - -/** - * An internal, Swing-based {@link Story} viewer. - *

    - * Works on both text and image document (see {@link MetaData#isImageDocument()} - * ). - * - * @author niki - */ -public class GuiReaderViewer extends JFrame { - private static final long serialVersionUID = 1L; - - private Story story; - private MetaData meta; - private JLabel title; - private GuiReaderPropertiesPane descPane; - private GuiReaderViewerPanel mainPanel; - private GuiReaderNavBar navbar; - - /** - * Create a new {@link Story} viewer. - * - * @param lib - * the {@link BasicLibrary} to load the cover from - * @param story - * the {@link Story} to display - */ - public GuiReaderViewer(BasicLibrary lib, Story story) { - setTitle(GuiReader.trans(StringIdGui.TITLE_STORY, story.getMeta() - .getLuid(), story.getMeta().getTitle())); - - setSize(800, 600); - - this.story = story; - this.meta = story.getMeta(); - - initGuiBase(lib); - initGuiNavButtons(); - - setChapter(-1); - } - - /** - * Initialise the base panel with everything but the navigation buttons. - * - * @param lib - * the {@link BasicLibrary} to use to retrieve the cover image in - * the description panel - */ - private void initGuiBase(BasicLibrary lib) { - setLayout(new BorderLayout()); - - title = new JLabel(); - title.setFont(new Font(Font.SERIF, Font.BOLD, - title.getFont().getSize() * 3)); - title.setText(meta.getTitle()); - title.setHorizontalAlignment(SwingConstants.CENTER); - title.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - add(title, BorderLayout.NORTH); - - JPanel contentPane = new JPanel(new BorderLayout()); - add(contentPane, BorderLayout.CENTER); - - descPane = new GuiReaderPropertiesPane(lib, meta); - contentPane.add(descPane, BorderLayout.NORTH); - - mainPanel = new GuiReaderViewerPanel(story); - contentPane.add(mainPanel, BorderLayout.CENTER); - } - - /** - * Create the 4 navigation buttons in {@link GuiReaderViewer#navButtons} and - * initialise them. - */ - private void initGuiNavButtons() { - navbar = new GuiReaderNavBar(-1, story.getChapters().size() - 1) { - private static final long serialVersionUID = 1L; - - @Override - protected String computeLabel(int index, int min, int max) { - int chapter = index; - Chapter chap; - if (chapter < 0) { - chap = meta.getResume(); - descPane.setVisible(true); - } else { - chap = story.getChapters().get(chapter); - descPane.setVisible(false); - } - - String chapterDisplay = GuiReader.trans( - StringIdGui.CHAPTER_HTML_UNNAMED, chap.getNumber(), - story.getChapters().size()); - if (chap.getName() != null && !chap.getName().trim().isEmpty()) { - chapterDisplay = GuiReader.trans( - StringIdGui.CHAPTER_HTML_NAMED, chap.getNumber(), - story.getChapters().size(), chap.getName()); - } - - return "" + chapterDisplay + ""; - } - }; - - navbar.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setChapter(navbar.getIndex()); - } - }); - - JPanel navButtonsPane = new JPanel(); - LayoutManager layout = new BoxLayout(navButtonsPane, BoxLayout.X_AXIS); - navButtonsPane.setLayout(layout); - - add(navbar, BorderLayout.SOUTH); - } - - /** - * Set the current chapter, 0-based. - *

    - * Chapter -1 is reserved for the description page. - * - * @param chapter - * the chapter number to set - */ - private void setChapter(int chapter) { - Chapter chap; - if (chapter < 0) { - chap = meta.getResume(); - descPane.setVisible(true); - } else { - chap = story.getChapters().get(chapter); - descPane.setVisible(false); - } - - mainPanel.setChapter(chap); - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java deleted file mode 100644 index 0577a0a..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java +++ /dev/null @@ -1,298 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.awt.BorderLayout; -import java.awt.EventQueue; -import java.awt.Graphics2D; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.image.BufferedImage; - -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JEditorPane; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JScrollPane; -import javax.swing.SwingConstants; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.bundles.StringIdGui; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.MetaData; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ui.ImageUtilsAwt; - -/** - * A {@link JPanel} that will show a {@link Story} chapter on screen. - * - * @author niki - */ -public class GuiReaderViewerPanel extends JPanel { - private static final long serialVersionUID = 1L; - - private boolean imageDocument; - private Chapter chap; - private JScrollPane scroll; - private GuiReaderViewerTextOutput htmlOutput; - - // text only: - private JEditorPane text; - - // image only: - private JLabel image; - private JProgressBar imageProgress; - private int currentImage; - private JButton left; - private JButton right; - - /** - * Create a new viewer. - * - * @param story - * the {@link Story} to work on - */ - public GuiReaderViewerPanel(Story story) { - this(story.getMeta(), story.getMeta().isImageDocument()); - } - - /** - * Create a new viewer. - * - * @param meta - * the {@link MetaData} of the story to show - * @param isImageDocument - * TRUE if it is an image document, FALSE if not - */ - public GuiReaderViewerPanel(MetaData meta, boolean isImageDocument) { - super(new BorderLayout()); - - this.imageDocument = isImageDocument; - - this.text = new JEditorPane("text/html", ""); - text.setEditable(false); - text.setAlignmentY(TOP_ALIGNMENT); - htmlOutput = new GuiReaderViewerTextOutput(); - - image = new JLabel(); - image.setHorizontalAlignment(SwingConstants.CENTER); - - scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - scroll.getVerticalScrollBar().setUnitIncrement(16); - - // TODO: - // JButton up = new BasicArrowButton(BasicArrowButton.NORTH); - // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH); - - if (!imageDocument) { - add(scroll, BorderLayout.CENTER); - } else { - imageProgress = new JProgressBar(); - imageProgress.setStringPainted(true); - add(imageProgress, BorderLayout.SOUTH); - - JPanel main = new JPanel(new BorderLayout()); - main.add(scroll, BorderLayout.CENTER); - - left = new JButton("    <    "); - left.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setImage(--currentImage); - } - }); - main.add(left, BorderLayout.WEST); - - right = new JButton("    >    "); - right.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setImage(++currentImage); - } - }); - main.add(right, BorderLayout.EAST); - - add(main, BorderLayout.CENTER); - main.invalidate(); - } - - setChapter(meta.getResume()); - } - - /** - * Load the given chapter. - *

    - * Will always be text for a non-image document. - *

    - * Will be an image and left/right controls for an image-document, except - * for chapter 0 which will be text (chapter 0 = resume). - * - * @param chap - * the chapter to load - */ - public void setChapter(Chapter chap) { - this.chap = chap; - - if (!imageDocument) { - setText(chap); - } else { - left.setVisible(chap.getNumber() > 0); - right.setVisible(chap.getNumber() > 0); - imageProgress.setVisible(chap.getNumber() > 0); - - imageProgress.setMinimum(0); - imageProgress.setMaximum(chap.getParagraphs().size() - 1); - - if (chap.getNumber() == 0) { - setText(chap); - } else { - setImage(0); - } - } - } - - /** - * Will set and display the current chapter text. - * - * @param chap - * the chapter to display - */ - private void setText(final Chapter chap) { - new Thread(new Runnable() { - @Override - public void run() { - final String content = htmlOutput.convert(chap); - // Wait until size computations are correct - while (!scroll.isValid()) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - } - } - - setText(content); - } - }).start(); - } - - /** - * Actually set the text in the UI. - *

    - * Do NOT use this method from the UI thread. - * - * @param content - * the text - */ - private void setText(final String content) { - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - text.setText(content); - text.setCaretPosition(0); - scroll.setViewportView(text); - } - }); - } - - /** - * Will set and display the current image, take care about the progression - * and update the left and right cursors' enabled property. - * - * @param i - * the image index to load - */ - private void setImage(int i) { - left.setEnabled(i > 0); - right.setEnabled(i + 1 < chap.getParagraphs().size()); - - if (i < 0 || i >= chap.getParagraphs().size()) { - return; - } - - imageProgress.setValue(i); - imageProgress.setString(GuiReader.trans(StringIdGui.IMAGE_PROGRESSION, - i + 1, chap.getParagraphs().size())); - - currentImage = i; - - final Image img = chap.getParagraphs().get(i).getContentImage(); - - // prepare the viewport to get the right sizes later on - image.setIcon(null); - scroll.setViewportView(image); - - new Thread(new Runnable() { - @Override - public void run() { - // Wait until size computations are correct - while (!scroll.isValid()) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - } - } - - if (img == null) { - setText("Error: cannot render image."); - } else { - setImage(img); - } - } - }).start(); - } - - /** - * Actually set the image in the UI. - *

    - * Do NOT use this method from the UI thread. - * - * @param img - * the image to set - */ - private void setImage(Image img) { - try { - int scrollWidth = scroll.getWidth() - - scroll.getVerticalScrollBar().getWidth(); - - BufferedImage buffImg = ImageUtilsAwt.fromImage(img); - - int iw = buffImg.getWidth(); - int ih = buffImg.getHeight(); - double ratio = ((double) ih) / iw; - - int w = scrollWidth; - int h = (int) (ratio * scrollWidth); - - BufferedImage resizedImage = new BufferedImage(w, h, - BufferedImage.TYPE_4BYTE_ABGR); - - Graphics2D g = resizedImage.createGraphics(); - try { - g.drawImage(buffImg, 0, 0, w, h, null); - } finally { - g.dispose(); - } - - final Icon icon = new ImageIcon(resizedImage); - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - image.setIcon(icon); - scroll.setViewportView(image); - } - }); - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error( - new Exception("Failed to load image into label", e)); - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - text.setText("Error: cannot load image."); - } - }); - } - } -} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java deleted file mode 100644 index fc914dd..0000000 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java +++ /dev/null @@ -1,128 +0,0 @@ -package be.nikiroo.fanfix.reader.ui; - -import java.io.IOException; -import java.util.Arrays; - -import be.nikiroo.fanfix.Instance; -import be.nikiroo.fanfix.data.Chapter; -import be.nikiroo.fanfix.data.Paragraph; -import be.nikiroo.fanfix.data.Paragraph.ParagraphType; -import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.output.BasicOutput; - -/** - * This class can export a chapter into HTML3 code ready for Java Swing support. - * - * @author niki - */ -public class GuiReaderViewerTextOutput { - private StringBuilder builder; - private BasicOutput output; - private Story fakeStory; - - /** - * Create a new {@link GuiReaderViewerTextOutput} that will convert a - * {@link Chapter} into HTML3 suited for Java Swing. - */ - public GuiReaderViewerTextOutput() { - builder = new StringBuilder(); - fakeStory = new Story(); - - output = new BasicOutput() { - private boolean paraInQuote; - - @Override - protected void writeChapterHeader(Chapter chap) throws IOException { - builder.append(""); - - builder.append("

    "); - builder.append("Chapter "); - builder.append(chap.getNumber()); - builder.append(": "); - builder.append(chap.getName()); - builder.append("

    "); - - builder.append("
    "); - } - - @Override - protected void writeChapterFooter(Chapter chap) throws IOException { - if (paraInQuote) { - builder.append("
    "); - } - paraInQuote = false; - - builder.append(""); - builder.append(""); - } - - @Override - protected void writeParagraph(Paragraph para) throws IOException { - if ((para.getType() == ParagraphType.QUOTE) == !paraInQuote) { - paraInQuote = !paraInQuote; - if (paraInQuote) { - builder.append("
    "); - builder.append("
    "); - } else { - builder.append("
    "); - builder.append("
    "); - } - } - - switch (para.getType()) { - case NORMAL: - builder.append("    "); - builder.append(decorateText(para.getContent())); - builder.append("
    "); - break; - case BLANK: - builder.append("

    "); - break; - case BREAK: - builder.append("

    "); - builder.append("* * *"); - builder.append("



    "); - break; - case QUOTE: - builder.append("
    "); - builder.append("    "); - builder.append("— "); - builder.append(decorateText(para.getContent())); - builder.append("
    "); - - break; - case IMAGE: - } - } - - @Override - protected String enbold(String word) { - return "" + word + ""; - } - - @Override - protected String italize(String word) { - return "" + word + ""; - } - }; - } - - /** - * Convert the chapter into HTML3 code. - * - * @param chap - * the {@link Chapter} to convert. - * - * @return HTML3 code tested with Java Swing - */ - public String convert(Chapter chap) { - builder.setLength(0); - try { - fakeStory.setChapters(Arrays.asList(chap)); - output.process(fakeStory, null, null); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error(e); - } - return builder.toString(); - } -} -- 2.27.0