+++ /dev/null
-package be.nikiroo.fanfix.reader.cli;
-
-import java.io.IOException;
-import java.util.List;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.StringId;
-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.StringUtils;
-
-/**
- * Command line {@link Story} reader.
- * <p>
- * Will output stories to the console.
- *
- * @author niki
- */
-class CliReader extends BasicReader {
- @Override
- public void read(boolean sync) throws IOException {
- MetaData meta = getMeta();
-
- if (meta == null) {
- throw new IOException("No story to read");
- }
-
- String title = "";
- String author = "";
-
- if (meta.getTitle() != null) {
- title = meta.getTitle();
- }
-
- if (meta.getAuthor() != null) {
- author = "©" + meta.getAuthor();
- if (meta.getDate() != null && !meta.getDate().isEmpty()) {
- author = author + " (" + meta.getDate() + ")";
- }
- }
-
- System.out.println(title);
- System.out.println(author);
- System.out.println("");
-
- // TODO: progress?
- for (Chapter chap : getStory(null)) {
- if (chap.getName() != null && !chap.getName().isEmpty()) {
- System.out.println(Instance.getTrans().getString(
- StringId.CHAPTER_NAMED, chap.getNumber(),
- chap.getName()));
- } else {
- System.out.println(Instance.getTrans().getString(
- StringId.CHAPTER_UNNAMED, chap.getNumber()));
- }
- }
- }
-
- public void read(int chapter) throws IOException {
- MetaData meta = getMeta();
-
- if (meta == null) {
- throw new IOException("No story to read");
- }
-
- // TODO: progress?
- if (chapter > getStory(null).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());
-
- for (Paragraph para : chap) {
- System.out.println(para.getContent());
- System.out.println("");
- }
- }
- }
-
- @Override
- public void browse(String source) throws IOException {
- List<MetaData> stories = getLibrary().getListBySource(source);
-
- 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 {
- for (SupportType type : SupportType.values()) {
- if (BasicSearchable.getSearchable(type) != null) {
- System.out.println(type);
- }
- }
- }
-
- @Override
- public void search(SupportType searchOn, String keywords, int page,
- int item, boolean sync) throws IOException {
- BasicSearchable search = BasicSearchable.getSearchable(searchOn);
-
- if (page == 0) {
- System.out.println(search.searchPages(keywords));
- } else {
- List<MetaData> metas = search.search(keywords, page);
-
- if (item == 0) {
- System.out.println("Page " + page + " of stories for: "
- + keywords);
- displayStories(metas);
- } else {
- // ! 1-based index !
- if (item <= 0 || item > metas.size()) {
- throw new IOException("Index out of bounds: " + item);
- }
-
- MetaData meta = metas.get(item - 1);
- displayStory(meta);
- }
- }
- }
-
- @Override
- public void searchTag(SupportType searchOn, int page, int item,
- boolean sync, Integer... tags) throws IOException {
-
- BasicSearchable search = BasicSearchable.getSearchable(searchOn);
- SearchableTag stag = search.getTag(tags);
-
- if (stag == null) {
- // TODO i18n
- System.out.println("Known tags: ");
- int i = 1;
- for (SearchableTag s : search.getTags()) {
- System.out.println(String.format("%d: %s", i, s.getName()));
- i++;
- }
- } else {
- if (page <= 0) {
- if (stag.isLeaf()) {
- System.out.println(search.searchPages(stag));
- } else {
- System.out.println(stag.getCount());
- }
- } else {
- List<MetaData> metas = null;
- List<SearchableTag> subtags = null;
- int count;
-
- if (stag.isLeaf()) {
- metas = search.search(stag, page);
- count = metas.size();
- } else {
- subtags = stag.getChildren();
- count = subtags.size();
- }
-
- if (item > 0) {
- if (item <= count) {
- if (metas != null) {
- MetaData meta = metas.get(item - 1);
- displayStory(meta);
- } else {
- SearchableTag subtag = subtags.get(item - 1);
- displayTag(subtag);
- }
- } else {
- System.out.println("Invalid item: only " + count
- + " items found");
- }
- } else {
- if (metas != null) {
- // TODO i18n
- System.out.println(String.format("Content of %s: ",
- stag.getFqName()));
- displayStories(metas);
- } else {
- // TODO i18n
- System.out.println(String.format("Subtags of %s: ",
- stag.getFqName()));
- displayTags(subtags);
- }
- }
- }
- }
- }
-
- private void displayTag(SearchableTag subtag) {
- // TODO: i18n
- String stories = "stories";
- String num = StringUtils.formatNumber(subtag.getCount());
- System.out.println(String.format("%s (%s), %s %s", subtag.getName(),
- subtag.getFqName(), num, stories));
- }
-
- private void displayStory(MetaData meta) {
- System.out.println(meta.getTitle());
- System.out.println();
- System.out.println(meta.getUrl());
- System.out.println();
- System.out.println("Tags: " + meta.getTags());
- System.out.println();
- for (Paragraph para : meta.getResume()) {
- System.out.println(para.getContent());
- System.out.println("");
- }
- }
-
- private void displayTags(List<SearchableTag> subtags) {
- int i = 1;
- for (SearchableTag subtag : subtags) {
- String total = "";
- if (subtag.getCount() > 0) {
- total = StringUtils.formatNumber(subtag.getCount());
- }
-
- if (total.isEmpty()) {
- System.out
- .println(String.format("%d: %s", i, subtag.getName()));
- } else {
- System.out.println(String.format("%d: %s (%s)", i,
- subtag.getName(), total));
- }
-
- i++;
- }
- }
-
- private void displayStories(List<MetaData> metas) {
- int i = 1;
- for (MetaData meta : metas) {
- System.out.println(i + ": " + meta.getTitle());
- i++;
- }
- }
-}
+++ /dev/null
-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}.
- * <p>
- * 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 <E>
- * the type of {@link Bundle} to edit
- */
-public abstract class ConfigItem<E extends Enum<E>> extends TWidget {
- /** The code base */
- private final ConfigItemBase<TWidget, E> 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<E> info,
- boolean autoDirtyHandling) {
- super(parent);
-
- base = new ConfigItemBase<TWidget, E>(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<TWidget> reload() {
- List<TWidget> 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<E> 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.
- * <p>
- * 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}.
- * <p>
- * 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)}.
- * <p>
- * 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 <E>
- * 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 <E extends Enum<E>> ConfigItem<E> createItem(TWidget parent,
- int x, int y, MetaInfo<E> info, int nhgap) {
-
- ConfigItem<E> configItem;
- switch (info.getFormat()) {
- // TODO
- // case BOOLEAN:
- // configItem = new ConfigItemBoolean<E>(info);
- // break;
- // case COLOR:
- // configItem = new ConfigItemColor<E>(info);
- // break;
- // case FILE:
- // configItem = new ConfigItemBrowse<E>(info, false);
- // break;
- // case DIRECTORY:
- // configItem = new ConfigItemBrowse<E>(info, true);
- // break;
- // case COMBO_LIST:
- // configItem = new ConfigItemCombobox<E>(info, true);
- // break;
- // case FIXED_LIST:
- // configItem = new ConfigItemCombobox<E>(info, false);
- // break;
- // case INT:
- // configItem = new ConfigItemInteger<E>(info);
- // break;
- // case PASSWORD:
- // configItem = new ConfigItemPassword<E>(info);
- // break;
- // case LOCALE:
- // configItem = new ConfigItemLocale<E>(info);
- // break;
- // case STRING:
- default:
- configItem = new ConfigItemString<E>(parent, info);
- break;
- }
-
- configItem.init(nhgap);
- configItem.setX(x);
- configItem.setY(y);
-
- return configItem;
- }
-}
+++ /dev/null
-package be.nikiroo.fanfix.reader.tui;
-
-import jexer.TField;
-import jexer.TWidget;
-import be.nikiroo.utils.resources.MetaInfo;
-
-class ConfigItemString<E extends Enum<E>> extends ConfigItem<E> {
- /**
- * Create a new {@link ConfigItemString} for the given {@link MetaInfo}.
- *
- * @param info
- * the {@link MetaInfo}
- */
- public ConfigItemString(TWidget parent, MetaInfo<E> 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);
- }
-}
+++ /dev/null
-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<E extends Enum<E>> extends TSimpleScrollableWindow {
- private List<MetaInfo<E>> items;
-
- public TOptionWindow(TApplication parent, Class<E> type,
- final Bundle<E> bundle, String title) {
- super(parent, title, 0, 0, CENTERED | RESIZABLE);
-
- getMainPane().addLabel(title, 0, 0);
-
- items = new ArrayList<MetaInfo<E>>();
- List<MetaInfo<E>> groupedItems = MetaInfo.getItems(type, bundle);
- int y = 2;
- for (MetaInfo<E> 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<E> item : items) {
- item.reload();
- }
- }
- });
-
- getMainPane().addButton("Default", 15, y, new TAction() {
- @Override
- public void DO() {
- Object snap = bundle.takeSnapshot();
- bundle.reload(true);
- for (MetaInfo<E> item : items) {
- item.reload();
- }
- bundle.reload(false);
- bundle.restoreSnapshot(snap);
- }
- });
-
- getMainPane().addButton("Save", 1, y, new TAction() {
- @Override
- public void DO() {
- for (MetaInfo<E> item : items) {
- item.save(true);
- }
-
- try {
- bundle.updateFile();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- });
- }
-
- private TWidget addItem(TWidget parent, int x, int y, MetaInfo<E> 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<E> 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);
- }
- }
-}
+++ /dev/null
-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();
- }
- }
-}
+++ /dev/null
-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.
- * <p>
- * It is expected to be on par with the GUI version.
- *
- * @author niki
- */
-class TuiReader extends BasicReader {
- /**
- * Will detect the backend to use.
- * <p>
- * Swing is the default backend on Windows and MacOS while evreything else
- * will use XTERM unless explicitly overridden by <tt>jexer.Swing</tt> =
- * <tt>true</tt> or <tt>false</tt>.
- *
- * @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.getTraceHandler().error(e);
- }
- }
-
- @Override
- public void browse(String source) {
- try {
- TuiReaderApplication app = new TuiReaderApplication(this, source,
- guessBackendType());
- app.run();
- } catch (Exception e) {
- Instance.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.");
- }
- }
-}
+++ /dev/null
-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.
- * <p>
- * 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.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.
- * <p>
- * Some shortcuts are always visible, and will be put here.
- * <p>
- * 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.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.
- * <p>
- * 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.getTraceHandler().error(title + ": " + message);
- if (e != null) {
- Instance.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.
- * <p>
- * 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}.
- * <p>
- * 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();
- }
- }
-}
+++ /dev/null
-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<MetaData> listKeys;
- private List<String> listItems;
- private TuiReaderApplication reader;
-
- private Mode mode = Mode.SOURCE;
- private String target = null;
- private String filter = "";
-
- private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
-
- // 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<MetaData>();
- listItems = new ArrayList<String>();
-
- 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<String> selects = new ArrayList<String>();
- selects.add("(show all)");
- selects.add("Sources");
- selects.add("Author");
-
- final List<String> selectTargets = new ArrayList<String>();
- 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.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.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.
- * <p>
- * Will take the current settings into account (filter, source...).
- */
- public void refreshStories() {
- List<MetaData> metas;
-
- try {
- if (mode == Mode.SOURCE) {
- metas = reader.getLibrary().getListBySource(target);
- } else if (mode == Mode.AUTHOR) {
- metas = reader.getLibrary().getListByAuthor(target);
- } else {
- metas = reader.getLibrary().getList();
- }
- } catch (IOException e) {
- Instance.getTraceHandler().error(e);
- metas = new ArrayList<MetaData>();
- }
-
- 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}.
- * <p>
- * 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<MetaData> 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.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
+++ /dev/null
-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.getUiConfig() : Instance.getConfig(),
- "Options");
-
- TStatusBar statusBar = reader.setStatusBar(this, "Options");
- }
-}
+++ /dev/null
-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<TButton> navigationButtons;
- private TLabel currentChapter;
- private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
-
- // 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<TButton>(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<String, String> 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);
- }
- }
-}
+++ /dev/null
-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.
- * <p>
- * It can thus be themed to look native-like, or metal-like, or use any other
- * theme you may want to try.
- * <p>
- * 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.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);
- }
- }
-
- 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,
- "<span style='color: blue;'>https://github.com/nikiroo/fanfix/releases</span>"));
- builder.append("<br>");
- builder.append("<br>");
- for (Version v : updates.getNewer()) {
- builder.append("\t<b>"
- + trans(StringIdGui.NEW_VERSION_VERSION, v.toString())
- + "</b>");
- builder.append("<br>");
- builder.append("<ul>");
- for (String item : updates.getChanges().get(v)) {
- builder.append("<li>" + item + "</li>");
- }
- builder.append("</ul>");
- }
-
- // html content
- updateMessage.setText("<html><body>" //
- + builder//
- + "</body></html>");
-
- // 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.getTraceHandler().error(ee);
- } catch (URISyntaxException ee) {
- Instance.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.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.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 <b>NOT</b>
- * from the main library.
- * <p>
- * 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.getTraceHandler().error(e);
- }
- }
-
- /**
- * Forward the delete operation to the main library.
- * <p>
- * 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.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.getUiConfig().getBoolean(
- UiConfig.NON_IMAGES_DOCUMENT_USE_INTERNAL_READER, true);
- boolean imageInternal = Instance.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}.
- * <p>
- * 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).
- * <p>
- * In other words, <b>move</b> the {@link Story} into other source.
- * <p>
- * 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.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.getTraceHandler().error(e);
- }
- }
-
- /**
- * Change the author of the given {@link Story}.
- * <p>
- * 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.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.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.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) {
- }
- }
- }
-}
+++ /dev/null
-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}.
- * <p>
- * 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<BookActionListener> 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.
- * <p>
- * 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<GuiReaderBook.BookActionListener>();
- 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}.
- * <p>
- * 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("<html>"
- + "<body style='width: %d px; height: %d px; text-align: center'>"
- + "%s" + "<br>" + "<span style='color: %s;'>" + "%s"
- + "</span>" + "</body>" + "</html>",
- GuiReaderCoverImager.TEXT_WIDTH,
- GuiReaderCoverImager.TEXT_HEIGHT, info.getMainInfo(),
- AUTHOR_COLOR, optSecondary));
- }
-}
+++ /dev/null
-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 {
- 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...).
- * <p>
- * 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}.
- * <p>
- * 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.
- * <p>
- * The image is <b>NOT</b> resized in any way, this is the original version.
- * <p>
- * 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.getListBySource(source).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.getListByAuthor(author).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;
- }
-}
+++ /dev/null
-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.getCache().getFromCache(id);
- if (in != null) {
- try {
- resizedImage = ImageUtilsAwt.fromImage(new Image(in));
- in.close();
- in = null;
- } catch (IOException e) {
- Instance.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.getCache().addToCache(in, id);
- in.close();
- in = null;
- }
- } catch (MalformedURLException e) {
- Instance.getTraceHandler().error(e);
- } catch (IOException e) {
- Instance.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.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;
- }
-}
+++ /dev/null
-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<String, List<String>> groupedSources = new HashMap<String, List<String>>();
- 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<String, List<String>> goupedAuthors = new HashMap<String, List<String>>();
- 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<String, List<String>> 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<String> 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<Config> ed = new ConfigEditor<Config>(
- Config.class, Instance.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<UiConfig> ed = new ConfigEditor<UiConfig>(
- UiConfig.class, Instance.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<FileFilter, OutputType> otherFilters = new HashMap<FileFilter, OutputType>();
- 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<FileFilter, OutputType> 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.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<String, List<String>> groupedSources = new HashMap<String, List<String>>();
- 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<String> 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<String, List<String>> groupedAuthors;
-
- try {
- groupedAuthors = reader.getLibrary().getAuthorsGrouped();
- } catch (IOException e) {
- error(e.getLocalizedMessage(), "IOException", e);
- groupedAuthors = new HashMap<String, List<String>>();
-
- }
-
- 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.getTraceHandler().error(title + ": " + message);
- if (e != null) {
- Instance.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());
- }
-}
+++ /dev/null
-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<GuiReaderBookInfo> infos;
- private List<GuiReaderBook> 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.
- * <p>
- * Note: this class' implementation will also set the main pane background
- * colour at the same time.
- * <p>
- * Sets the background colour of this component. The background colour is
- * used only if the component is opaque, and only by subclasses of
- * <code>JComponent</code> or <code>ComponentUI</code> implementations.
- * Direct subclasses of <code>JComponent</code> must override
- * <code>paintComponent</code> to honour this property.
- * <p>
- * It is up to the look and feel to honour this property, some may choose to
- * ignore it.
- *
- * @param backgroundColor
- * the desired background <code>Colour</code>
- * @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("<html>"
- + "<body style='text-align: center; color: gray;'><br><b>"
- + "%s" + "</b></body>" + "</html>", 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<GuiReaderBookInfo>());
- }
-
- /**
- * 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<GuiReaderBookInfo> 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<GuiReaderBookInfo> infos, boolean seeWordcount) {
- this.infos = infos;
- refreshBooks(seeWordcount);
- }
-
- /**
- * Refresh the list of {@link GuiReaderBook}s displayed in the control.
- * <p>
- * 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<GuiReaderBook>();
- 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 <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Disabling this component will also affect its children.
- *
- * @param b
- * If <code>true</code>, 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 <tt>forceRange></tt>)
- * @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);
- }
- }
-}
+++ /dev/null
-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<String, GuiReaderGroup> 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.
- * <p>
- * Will invalidate the layout.
- *
- * @param status
- * the library status, <b>must not</b> 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.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<String, GuiReaderGroup>();
-
- 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.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.
- * <p>
- * A display of all the sources/types or all the authors will show one icon
- * per source/type or author.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * Will invalidate the layout.
- */
- public void removeBookPanes() {
- books.clear();
- pane.invalidate();
- pane.removeAll();
- }
-
- /**
- * Refresh the list of {@link GuiReaderBook}s from disk.
- * <p>
- * Will validate the layout, as it is a "refresh" operation.
- */
- public void refreshBooks() {
- BasicLibrary lib = helper.getReader().getLibrary();
- for (String value : books.keySet()) {
- List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
-
- List<MetaData> metas;
- try {
- if (currentType) {
- metas = lib.getListBySource(value);
- } else {
- metas = lib.getListByAuthor(value);
- }
- } catch (IOException e) {
- error(e.getLocalizedMessage(), "IOException", e);
- metas = new ArrayList<MetaData>();
- }
-
- 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.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<String> luids = new LinkedList<String>();
- try {
- switch (book.getInfo().getType()) {
- case STORY:
- luids.add(book.getInfo().getMeta().getLuid());
- break;
- case SOURCE:
- for (MetaData meta : helper.getReader().getLibrary()
- .getListBySource(book.getInfo().getMainInfo())) {
- luids.add(meta.getLuid());
- }
- break;
- case AUTHOR:
- for (MetaData meta : helper.getReader().getLibrary()
- .getListByAuthor(book.getInfo().getMainInfo())) {
- luids.add(meta.getLuid());
- }
- break;
- }
- } catch (IOException e) {
- Instance.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.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.
- * <p>
- * 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.
- * <p>
- * The code will make sure the current thread is the main UI thread and, if
- * not, will switch to it before executing the runnable.
- * <p>
- * 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.getTraceHandler().error(e);
- } catch (InvocationTargetException e) {
- Instance.getTraceHandler().error(e);
- }
- }
- }
-
- /**
- * Import a {@link Story} into the main {@link LocalLibrary}.
- * <p>
- * 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}.
- * <p>
- * 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 <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Enabling or disabling <b>this</b> component will also affect its
- * children.
- *
- * @param b
- * If <code>true</code>, 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<String> values,
- final boolean type) {
- GuiReader reader = helper.getReader();
- BasicLibrary lib = reader.getLibrary();
-
- bookPane = new GuiReaderGroup(reader, name, color);
-
- List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
- 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<String, GuiReaderGroup> 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.getTraceHandler().error(title + ": " + message);
- if (e != null) {
- Instance.getTraceHandler().error(e);
- }
-
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- JOptionPane.showMessageDialog(GuiReaderMainPanel.this, message,
- title, JOptionPane.ERROR_MESSAGE);
- }
- });
- }
-}
+++ /dev/null
-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<ActionListener> listeners = new ArrayList<ActionListener>();
-
- /**
- * Create a new navigation bar.
- * <p>
- * The minimum must be lower or equal to the maximum.
- * <p>
- * 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.
- * <p>
- * May update the index if needed (if the index is < the new min).
- * <p>
- * 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).
- * <p>
- * May update the index if needed (if the index is > the new max).
- * <p>
- * 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.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.
- * <p>
- * Swing HTML (HTML3) is supported if surrounded by <HTML> and
- * </HTML>.
- * <p>
- * 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 = " <B>Page <SPAN COLOR='#444466'>%d</SPAN> ";
- if (max >= 0) {
- base += "/ %d";
- }
- base += "</B>";
-
- String ifLabel = ": %s";
-
- String display = base;
- String label = getExtraLabel();
- if (label != null && !label.trim().isEmpty()) {
- display += ifLabel;
- }
-
- display = "<HTML>" + display + "</HTML>";
-
- if (max >= 0) {
- return String.format(display, index, max, label);
- }
-
- return String.format(display, index, label);
- }
-}
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-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<String, String> 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;
- }
-}
+++ /dev/null
-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.getTraceHandler().error(e);
- }
-
- pg.done();
- }
- }).start();
- }
- });
-
- return imprt;
- }
-}
+++ /dev/null
-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<MetaData> stories = new ArrayList<MetaData>();
- 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.
- * <p>
- * 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<MetaData>();
- updateKeywords("");
- }
-
- /**
- * The currently displayed page of result for the current search (see the
- * <tt>page</tt> 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
- * <tt>page</tt> parameter of
- * {@link GuiReaderSearchByPanel#search(String, int, int)}).
- * <p>
- * 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<MetaData> getStories() {
- return stories;
- }
-
- /**
- * Return the currently selected story (the <tt>item</tt>) if it was
- * specified in the latest, or 0 if not.
- * <p>
- * Note: this is thus a 1-based index, <b>not</b> 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.
- * <p>
- * 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<MetaData> stories = new ArrayList<MetaData>();
- 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 <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Disabling this component will also affect its children.
- *
- * @param b
- * If <code>true</code>, this component is enabled; otherwise
- * this component is disabled
- */
- @Override
- public void setEnabled(boolean b) {
- super.setEnabled(b);
- keywordsField.setEnabled(b);
- submitKeywords.setEnabled(b);
- }
-}
+++ /dev/null
-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.
- * <p>
- * 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}.
- * <p>
- * This operation can be long and should be run outside the UI thread.
- * <p>
- * 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
- * <tt>page</tt> 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
- * <tt>page</tt> parameter of
- * {@link GuiReaderSearchByPanel#search(String, int, int)} or
- * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
- * ).
- * <p>
- * 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.
- * <p>
- * 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<MetaData> getStories() {
- if (!searchByTags) {
- return byName.getStories();
- }
-
- return byTag.getStories();
- }
-
- /**
- * Return the currently selected story (the <tt>item</tt>) if it was
- * specified in the latest, or 0 if not.
- * <p>
- * Note: this is thus a 1-based index, <b>not</b> 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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 <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Disabling this component will also affect its children.
- *
- * @param b
- * If <code>true</code>, 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);
- }
-}
+++ /dev/null
-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<E> 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<JComboBox> combos;
-
- private int page;
- private int maxPage;
- private List<MetaData> stories = new ArrayList<MetaData>();
- private int storyItem;
-
- public GuiReaderSearchByTagPanel(Waitable waitable) {
- setLayout(new BorderLayout());
-
- this.waitable = waitable;
- combos = new ArrayList<JComboBox>();
- 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.
- * <p>
- * This operation can be long and should be run outside the UI thread.
- * <p>
- * 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<MetaData>();
- updateTags(null);
- }
-
- /**
- * The currently displayed page of result for the current search (see the
- * <tt>page</tt> 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
- * <tt>page</tt> parameter of
- * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
- * ).
- * <p>
- * 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<MetaData> getStories() {
- return stories;
- }
-
- /**
- * Return the currently selected story (the <tt>item</tt>) if it was
- * specified in the latest, or 0 if not.
- * <p>
- * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
- *
- * @return the item
- */
- public int getStoryItem() {
- return storyItem;
- }
-
- /**
- * Update the tags displayed on screen and reset the tags bar.
- * <p>
- * 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<SearchableTag> parents = new ArrayList<SearchableTag>();
- SearchableTag parent = (tag == null) ? null : tag;
- while (parent != null) {
- parents.add(parent);
- parent = parent.getParent();
- }
-
- List<SearchableTag> rootTags = new ArrayList<SearchableTag>();
- 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<SearchableTag> 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).
- * <p>
- * 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<SearchableTag> 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.
- * <p>
- * The content of the action is:
- * <ul>
- * <li>Remove all tags bar below this one</li>
- * <li>Load the subtags if any in anew tags bar</li>
- * <li>Load the related stories if the tag was a leaf tag and notify the
- * {@link Waitable} (via {@link Waitable#fireEvent()})</li>
- * </ul>
- *
- * @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<JComboBox> 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<SearchableTag> 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<MetaData>();
- }
-
- waitable.fireEvent();
- }
- } finally {
- waitable.setWaiting(false);
- }
- }
- }).start();
- }
- };
- }
-
- /**
- * Get the children of the given tag (or the base tags if the given tag is
- * NULL).
- * <p>
- * This action will "fill" ({@link BasicSearchable#fillTag(SearchableTag)})
- * the given tag if needed first.
- * <p>
- * 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<SearchableTag> getChildrenForTag(final SearchableTag tag) {
- List<SearchableTag> children = new ArrayList<SearchableTag>();
- if (tag == null) {
- try {
- List<SearchableTag> 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.
- * <p>
- * 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.
- * <p>
- * 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<MetaData> stories = new ArrayList<MetaData>();
- int storyItem = 0;
-
- currentTag = tag;
- updateTags(tag);
-
- int maxPage = -1;
- if (tag != null) {
- try {
- searchable.fillTag(tag);
-
- if (!tag.isLeaf()) {
- List<SearchableTag> 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 <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Disabling this component will also affect its children.
- *
- * @param b
- * If <code>true</code>, 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);
- }
- }
-}
+++ /dev/null
-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<E> not 1.6 compatible
-@SuppressWarnings({ "unchecked", "rawtypes" })
-public class GuiReaderSearchFrame extends JFrame {
- private static final long serialVersionUID = 1L;
-
- private List<SupportType> 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<SupportType>();
- 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<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
- 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.
- * <p>
- * Will also cause a search for the new base tags of the given support if
- * not NULL.
- * <p>
- * 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.
- * <p>
- * 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<GuiReaderBookInfo> 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * The code will make sure the current thread is the main UI thread and, if
- * not, will switch to it before executing the runnable.
- * <p>
- * 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.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.getTraceHandler().error(e);
- }
-
- /**
- * Enables or disables this component, depending on the value of the
- * parameter <code>b</code>. An enabled component can respond to user input
- * and generate events. Components are enabled initially by default.
- * <p>
- * Disabling this component will also affect its children.
- *
- * @param b
- * If <code>true</code>, 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);
- }
- });
- }
-}
+++ /dev/null
-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.
- * <p>
- * 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 "<HTML>" + chapterDisplay + "</HTML>";
- }
- };
-
- 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.
- * <p>
- * 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);
- }
-}
+++ /dev/null
-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("<HTML> < </HTML>");
- left.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- setImage(--currentImage);
- }
- });
- main.add(left, BorderLayout.WEST);
-
- right = new JButton("<HTML> > </HTML>");
- 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.
- * <p>
- * Will always be text for a non-image document.
- * <p>
- * 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.
- * <p>
- * Do <b>NOT</b> 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' <tt>enabled</tt> 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.
- * <p>
- * Do <b>NOT</b> 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.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.");
- }
- });
- }
- }
-}
+++ /dev/null
-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("<HTML>");
-
- builder.append("<H1>");
- builder.append("Chapter ");
- builder.append(chap.getNumber());
- builder.append(": ");
- builder.append(chap.getName());
- builder.append("</H1>");
-
- builder.append("<DIV align='justify'>");
- }
-
- @Override
- protected void writeChapterFooter(Chapter chap) throws IOException {
- if (paraInQuote) {
- builder.append("</DIV>");
- }
- paraInQuote = false;
-
- builder.append("</DIV>");
- builder.append("</HTML>");
- }
-
- @Override
- protected void writeParagraph(Paragraph para) throws IOException {
- if ((para.getType() == ParagraphType.QUOTE) == !paraInQuote) {
- paraInQuote = !paraInQuote;
- if (paraInQuote) {
- builder.append("<BR>");
- builder.append("<DIV>");
- } else {
- builder.append("</DIV>");
- builder.append("<BR>");
- }
- }
-
- switch (para.getType()) {
- case NORMAL:
- builder.append(" ");
- builder.append(decorateText(para.getContent()));
- builder.append("<BR>");
- break;
- case BLANK:
- builder.append("<BR><BR>");
- break;
- case BREAK:
- builder.append("<BR><P COLOR='#7777DD' ALIGN='CENTER'><B>");
- builder.append("* * *");
- builder.append("</B></P><BR><BR>");
- break;
- case QUOTE:
- builder.append("<DIV>");
- builder.append(" ");
- builder.append("— ");
- builder.append(decorateText(para.getContent()));
- builder.append("</DIV>");
-
- break;
- case IMAGE:
- }
- }
-
- @Override
- protected String enbold(String word) {
- return "<B COLOR='#7777DD'>" + word + "</B>";
- }
-
- @Override
- protected String italize(String word) {
- return "<I COLOR='GRAY'>" + word + "</I>";
- }
- };
- }
-
- /**
- * 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.getTraceHandler().error(e);
- }
- return builder.toString();
- }
-}