libs: bin
@[ -e bin/libs -o ! -d libs ] || echo Extracting sources from libs...
- @[ -e bin/libs -o ! -d libs ] || (cd src && for lib in ../libs/*-sources.jar; do \
+ @[ -e bin/libs -o ! -d libs ] || (cd src && for lib in ../libs/*-sources.jar ../libs/*-sources.patch.jar; do \
basename "$$lib"; \
jar xf "$$lib"; \
done )
- [x] ..as a screen view
- [x] options screen
- [ ] A TUI reader
+ - [x] Choose an output (Jexer)
+ - [x] Implement it from --set-reader to the actual window
+ - [x] List the stories
+ - [ ] Fix the UI layout
+ - [ ] Status bar and real menus
+ - [ ] Open a story in the reader and/or natively
- [ ] Check if it can work on Android
- [x] First checks: it should work, but with changes
- [ ] Adapt work on images :(
# Fanfix
+## Version WIP
+- New reader type: TUI (a text user interface with windows and menus)
+
## Version 1.5.3
- FimFiction: Fix tags and chapter handling for some stories
* <li>--read-url [URL] ([chapter number]): convert on the fly and read the
* story, without saving it</li>
* <li>--list ([type]): list the stories present in the library</li>
- * <li>--set-reader [reader type]: set the reader type to CLI or LOCAL for
- * this command</li>
+ * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
+ * for this command</li>
* <li>--version: get the version of the program</li>
* </ul>
*
break;
case SET_READER:
exitCode = setReaderType(args[i]);
+ action = MainAction.START;
break;
case START:
exitCode = 255; // not supposed to be selected by user
updates.ok(); // we consider it read
break;
case START:
- UIUtils.setLookAndFeel();
- BasicReader.setDefaultReaderType(ReaderType.LOCAL);
+ //BasicReader.setDefaultReaderType(ReaderType.LOCAL);
BasicReader.getReader().start(null);
break;
}
public enum Config {
@Meta(description = "language (example: en-GB, fr-BE...) or nothing for default system language", format = Format.LOCALE, info = "Force the language (can be overwritten again with the env variable $LANG)")
LANG, //
- @Meta(description = "reader type (CLI = simple output to console, LOCAL = use local system file handler)", format = Format.FIXED_LIST, list = {
- "CLI", "LOCAL" }, info = "Select the default reader to use to read stories")
+ @Meta(description = "reader type (CLI = simple output to console, TUI = Text User Interface with menus and windows, GUI = a GUI with locally stored files)", format = Format.FIXED_LIST, list = {
+ "CLI", "GUI", "TUI" }, info = "Select the default reader to use to read stories")
READER_TYPE, //
@Meta(description = "absolute path, $HOME variable supported, / is always accepted as dir separator", format = Format.DIRECTORY, info = "The directory where to store temporary files, defaults to directory 'tmp' in the conig directory (usually $HOME/.fanfix)")
CACHE_DIR, //
# language (example: en-GB, fr-BE...) or nothing for default system language
# (FORMAT: LOCALE) Force the language (can be overwritten again with the env variable $LANG)
LANG =
-# reader type (CLI = simple output to console, LOCAL = use local system file handler)
+# reader type (CLI = simple output to console, TUI = Text User Interface with menus and windows, GUI = a GUI with locally stored files)
# (FORMAT: FIXED_LIST) Select the default reader to use to read stories
-# ALLOWED VALUES: "CLI" "LOCAL"
+# ALLOWED VALUES: "CLI" "GUI" "TUI"
READER_TYPE =
# absolute path, $HOME variable supported, / is always accepted as dir separator
# (FORMAT: DIRECTORY) The directory where to store temporary files, defaults to directory 'tmp' in the conig directory (usually $HOME/.fanfix)
\t--read [id] ([chapter number]): read the given story from the library\n\
\t--read-url [URL] ([chapter number]): convert on the fly and read the story, without saving it\n\
\t--list: list the stories present in the library\n\
-\t--set-reader [reader type]: set the reader type to CLI or LOCAL for this command\n\
+\t--set-reader [reader type]: set the reader type to CLI, TUI or GUI for this command\n\
\t--help: this help message\n\
\t--version: return the version of the program\n\
\n\
package be.nikiroo.fanfix.reader;
+import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import be.nikiroo.fanfix.Instance;
import be.nikiroo.fanfix.Library;
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.supported.BasicSupport;
import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.ui.UIUtils;
/**
* The class that handles the different {@link Story} readers you can use.
/** Simple reader that outputs everything on the console */
CLI,
/** Reader that starts local programs to handle the stories */
- LOCAL
+ GUI,
+ /** A text (UTF-8) reader with menu and text windows */
+ TUI,
}
- private static ReaderType defaultType = ReaderType.CLI;
+ private static ReaderType defaultType = ReaderType.GUI;
private Story story;
private ReaderType type;
try {
if (defaultType != null) {
switch (defaultType) {
- case LOCAL:
- return new LocalReader().setType(ReaderType.LOCAL);
+ case GUI:
+ UIUtils.setLookAndFeel();
+ return new LocalReader().setType(ReaderType.GUI);
case CLI:
return new CliReader().setType(ReaderType.CLI);
+ case TUI:
+ return new TuiReader().setType(ReaderType.TUI);
}
}
} catch (IOException e) {
return source;
}
+
+ // open with external player the related file
+ public static void open(String luid) throws IOException {
+ MetaData meta = Instance.getLibrary().getInfo(luid);
+ File target = Instance.getLibrary().getFile(luid);
+
+ open(meta, target);
+ }
+
+ // open with external player the related file
+ protected static void open(MetaData meta, File target) throws IOException {
+ String program = null;
+ if (meta.isImageDocument()) {
+ program = Instance.getUiConfig().getString(
+ UiConfig.IMAGES_DOCUMENT_READER);
+ } else {
+ program = Instance.getUiConfig().getString(
+ UiConfig.NON_IMAGES_DOCUMENT_READER);
+ }
+
+ if (program != null && program.trim().isEmpty()) {
+ program = null;
+ }
+
+ if (program == null) {
+ try {
+ Desktop.getDesktop().browse(target.toURI());
+ } catch (UnsupportedOperationException e) {
+ Runtime.getRuntime().exec(
+ new String[] { "xdg-open", target.getAbsolutePath() });
+
+ }
+ } else {
+ Runtime.getRuntime().exec(
+ new String[] { program, target.getAbsolutePath() });
+ }
+ }
}
import be.nikiroo.fanfix.Library;
import be.nikiroo.fanfix.VersionCheck;
import be.nikiroo.fanfix.bundles.UiConfig;
-import be.nikiroo.fanfix.data.MetaData;
import be.nikiroo.fanfix.data.Story;
import be.nikiroo.fanfix.output.BasicOutput.OutputType;
import be.nikiroo.utils.Progress;
}
}
- /**
- * Get the target file related to this {@link Story}.
- *
- * @param luid
- * the LUID of the {@link Story}
- * @param pg
- * the optional progress reporter
- *
- * @return the target file
- *
- * @throws IOException
- * in case of I/O error
- */
- public File getTarget(String luid, Progress pg) throws IOException {
- File file = lib.getFile(luid);
- if (file == null) {
- imprt(luid, pg);
- file = lib.getFile(luid);
- }
-
- return file;
- }
-
/**
* Check if the {@link Story} denoted by this Library UID is present in the
* {@link LocalReader} cache.
// open the given book
void open(String luid, Progress pg) throws IOException {
- MetaData meta = Instance.getLibrary().getInfo(luid);
- File target = getTarget(luid, pg);
-
- String program = null;
- if (meta.isImageDocument()) {
- program = Instance.getUiConfig().getString(
- UiConfig.IMAGES_DOCUMENT_READER);
- } else {
- program = Instance.getUiConfig().getString(
- UiConfig.NON_IMAGES_DOCUMENT_READER);
- }
-
- if (program != null && program.trim().isEmpty()) {
- program = null;
+ File file = lib.getFile(luid);
+ if (file == null) {
+ imprt(luid, pg);
+ file = lib.getFile(luid);
}
- if (program == null) {
- try {
- Desktop.getDesktop().browse(target.toURI());
- } catch (UnsupportedOperationException e) {
- Runtime.getRuntime().exec(
- new String[] { "xdg-open", target.getAbsolutePath() });
-
- }
- } else {
- Runtime.getRuntime().exec(
- new String[] { program, target.getAbsolutePath() });
-
- }
+ open(Instance.getLibrary().getInfo(luid), file);
}
void changeType(String luid, String newType) {
--- /dev/null
+package be.nikiroo.fanfix.reader;
+
+import java.io.IOException;
+import java.util.List;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.data.MetaData;
+
+class TuiReader extends BasicReader {
+ @Override
+ public void read() throws IOException {
+ if (getStory() == null) {
+ throw new IOException("No story to read");
+ }
+
+ open(getStory().getMeta().getLuid());
+ }
+
+ @Override
+ public void read(int chapter) throws IOException {
+ // TODO: show a special page?
+ read();
+ }
+
+ @Override
+ public void start(String type) {
+ List<MetaData> stories = Instance.getLibrary().getListByType(type);
+ try {
+ TuiReaderApplication app = new TuiReaderApplication(stories, this);
+ new Thread(app).start();
+ } catch (Exception e) {
+ Instance.syserr(e);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.reader;
+
+import java.io.IOException;
+import java.util.List;
+
+import jexer.TApplication;
+import jexer.TMessageBox;
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.data.MetaData;
+
+public class TuiReaderApplication extends TApplication {
+ private BasicReader reader;
+
+ private static BackendType guessBackendType() {
+ // Swing is the default backend on Windows unless explicitly
+ // overridden by jexer.Swing.
+ 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;
+ }
+
+ public TuiReaderApplication(List<MetaData> stories, BasicReader reader)
+ throws Exception {
+ this(stories, reader, guessBackendType());
+ }
+
+ public TuiReaderApplication(List<MetaData> stories, BasicReader reader,
+ TApplication.BackendType backend) throws Exception {
+ super(backend);
+
+ this.reader = reader;
+
+ // Add the menus
+ addFileMenu();
+ addEditMenu();
+ addWindowMenu();
+ addHelpMenu();
+
+ getBackend().setTitle("Testy");
+
+ new TuiReaderMainWindow(this, stories);
+ }
+
+ public void open(MetaData meta) {
+ // TODO: open in editor + external option
+ if (true) {
+ if (!meta.isImageDocument()) {
+ new TuiReaderStoryWindow(this, meta);
+ } else {
+ messageBox("Error when trying to open the story",
+ "Images document not yet supported.",
+ TMessageBox.Type.OK);
+ }
+ return;
+ }
+ try {
+ reader.open(meta.getLuid());
+ } catch (IOException e) {
+ messageBox("Error when trying to open the story", e.getMessage(),
+ TMessageBox.Type.OK);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.reader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jexer.TAction;
+import jexer.TList;
+import jexer.TRadioGroup;
+import jexer.TTreeItem;
+import jexer.TTreeView;
+import jexer.TWindow;
+import be.nikiroo.fanfix.data.MetaData;
+
+public class TuiReaderMainWindow extends TWindow {
+ private TList list;
+ private List<MetaData> listKeys;
+ private List<String> listItems;
+ private TuiReaderApplication reader;
+
+ /**
+ * Constructor.
+ *
+ * @param parent
+ * the main application
+ * @param flags
+ * bitmask of MODAL, CENTERED, or RESIZABLE
+ */
+ public TuiReaderMainWindow(TuiReaderApplication reader,
+ List<MetaData> stories) {
+ // Construct a demo window. X and Y don't matter because it will be
+ // centered on screen.
+ super(reader, "Demo Window", 0, 0, 60, 18, CENTERED | RESIZABLE
+ | UNCLOSABLE);
+
+ this.reader = reader;
+
+ maximize();
+
+ listKeys = new ArrayList<MetaData>();
+ listItems = new ArrayList<String>();
+
+ if (stories != null) {
+ for (MetaData meta : stories) {
+ listKeys.add(meta);
+ listItems.add(desc(meta));
+ }
+ }
+
+ list = addList(listItems, 0, 0, getWidth(), getHeight(), new TAction() {
+ @Override
+ public void DO() {
+ if (list.getSelectedIndex() >= 0) {
+ enterOnStory(listKeys.get(list.getSelectedIndex()));
+ }
+ }
+ });
+
+ if (false) {
+ 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 enterOnStory(MetaData meta) {
+ reader.open(meta);
+ }
+
+ private String desc(MetaData meta) {
+ return String.format("%5s: %s", meta.getLuid(), meta.getTitle());
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.reader;
+
+import jexer.TApplication;
+import jexer.TText;
+import jexer.TWindow;
+import jexer.event.TResizeEvent;
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Story;
+
+public class TuiReaderStoryWindow extends TWindow {
+ private MetaData meta;
+ private Story story;
+ private TText textField;
+
+ public TuiReaderStoryWindow(TApplication app, MetaData meta) {
+ super(app, desc(meta), 0, 0, 60, 18, CENTERED | RESIZABLE);
+ this.meta = meta;
+
+ // /TODO: status bar with info, buttons to change chapter << < Chapter 0
+ // : xxx.. > >> (max size for name = getWith() - X)
+
+ // TODO: show all meta info before
+
+ Chapter resume = getStory().getMeta().getResume();
+ StringBuilder text = new StringBuilder();
+ if (resume != null) {
+ // TODO: why does \n not work but \n\n do? bug in jexer?
+ text.append("Resume:\n\n "); // -> to status bar
+ for (Paragraph para : resume) {
+ text.append(para.getContent()).append("\n\n ");
+ }
+ }
+ textField = addText(text.toString(), 0, 0, getWidth(), getHeight());
+ }
+
+ @Override
+ public void onResize(TResizeEvent resize) {
+ super.onResize(resize);
+
+ // Resize the text field
+ textField.setWidth(resize.getWidth());
+ textField.setHeight(resize.getHeight());
+ textField.reflow();
+ }
+
+ private Story getStory() {
+ if (story == null) {
+ // TODO: progress bar
+ story = Instance.getLibrary().getStory(meta.getLuid(), null);
+ }
+ return story;
+ }
+
+ private static String desc(MetaData meta) {
+ return String.format("%s: %s", meta.getLuid(), meta.getTitle());
+ }
+}