X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Freader%2Fui%2FGuiReaderFrame.java;h=77cd7e019ef4363113e7605330c7ae82386fea7a;hb=0b39fb9f4fc11d0880158a3b182900b9fe82da42;hp=39187b968b39a970849294f4fe1ac1f046f0199e;hpb=c3b229a10b147a2ca104a13ad0b43e49549b4ed9;p=fanfix.git diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java index 39187b9..77cd7e0 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java @@ -1,56 +1,46 @@ package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; -import java.awt.Color; 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.KeyEvent; -import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.swing.BoxLayout; import javax.swing.JFileChooser; import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; 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 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.GuiReaderBook.BookActionListener; +import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper; +import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.StoryRunnable; +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; -import be.nikiroo.utils.ui.ProgressBar; /** * A {@link Frame} that will show a {@link GuiReaderBook} item for each @@ -60,31 +50,23 @@ import be.nikiroo.utils.ui.ProgressBar; * * @author niki */ -class GuiReaderFrame extends JFrame { +class GuiReaderFrame extends JFrame implements FrameHelper { private static final long serialVersionUID = 1L; private GuiReader reader; - private Map booksByType; - private Map booksByAuthor; - 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 GuiReaderMainPanel mainPanel; /** - * A {@link Runnable} with a {@link Story} parameter. + * The different modification actions you can use on {@link Story} items. * * @author niki */ - private interface StoryRunnable { - /** - * Run the action. - * - * @param story - * the story - */ - public void run(Story story); + private enum ChangeAction { + /** Change the source/type, that is, move it to another source. */ + SOURCE, + /** Change its name. */ + TITLE, + /** Change its author. */ + AUTHOR } /** @@ -97,286 +79,74 @@ class GuiReaderFrame extends JFrame { * the type of {@link Story} to load, or NULL for all types */ public GuiReaderFrame(GuiReader reader, String type) { - super(String.format("Fanfix %s Library", Version.getCurrentVersion())); + super(getAppTitle(reader.getLibrary().getLibraryName())); this.reader = reader; - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + mainPanel = new GuiReaderMainPanel(this, type); + setSize(800, 600); setLayout(new BorderLayout()); - - pane = new JPanel(); - pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); - - Integer icolor = Instance.getUiConfig().getColor( - UiConfig.BACKGROUND_COLOR); - if (icolor != null) { - color = new Color(icolor); - setBackground(color); - pane.setBackground(color); - } - - JScrollPane scroll = new JScrollPane(pane); - scroll.getVerticalScrollBar().setUnitIncrement(16); - add(scroll, BorderLayout.CENTER); - - String message = reader.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) { - invalidate(); - pgBar.setProgress(null); - validate(); - setEnabled(true); - } - }); - - pgBar.addUpdateListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - invalidate(); - validate(); - repaint(); - } - }); - - booksByType = new HashMap(); - booksByAuthor = new HashMap(); - - pane.setVisible(false); - final Progress pg = new Progress(); - final String typeF = type; - outOfUi(pg, new Runnable() { - @Override - public void run() { - BasicLibrary lib = GuiReaderFrame.this.reader.getLibrary(); - Status status = lib.getStatus(); - - if (status == Status.READY) { - lib.refresh(pg); - invalidate(); - setJMenuBar(createMenu(true)); - addBookPane(typeF, true); - refreshBooks(); - validate(); - pane.setVisible(true); - } else { - invalidate(); - setJMenuBar(createMenu(false)); - validate(); - - String err = lib.getLibraryName() + "\n"; - switch (status) { - case INVALID: - err += "Library not valid"; - break; - - case UNAUTORIZED: - err += "You are not allowed to access this library"; - break; - - case UNAVAILABLE: - err += "Library currently unavailable"; - break; - - default: - err += "An error occured when contacting the library"; - break; - } - - error(err, "Library error", null); - } - } - }); - - setVisible(true); + add(mainPanel, BorderLayout.CENTER); } - private void addSourcePanes() { - // Sources -> i18n - GuiReaderGroup bookPane = new GuiReaderGroup(reader, "Sources", color); - - List sources = new ArrayList(); - for (String source : reader.getLibrary().getSources()) { - MetaData mSource = new MetaData(); - mSource.setLuid(null); - mSource.setTitle(source); - mSource.setSource(source); - sources.add(mSource); - } - - bookPane.refreshBooks(sources, false); - - 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, MouseEvent e) { - JPopupMenu popup = new JPopupMenu(); - popup.add(createMenuItemOpenBook()); - popup.show(e.getComponent(), e.getX(), e.getY()); - } - - @Override - public void action(final GuiReaderBook book) { - removeBookPanes(); - addBookPane(book.getMeta().getSource(), true); - refreshBooks(); - } - }); - } - - /** - * Add a new {@link GuiReaderGroup} on the frame to display the books of the - * selected type or author. - * - * @param value - * the author or the type, or NULL to get all the - * authors-or-types - * @param type - * TRUE for type, FALSE for author - */ - private void addBookPane(String value, boolean type) { - if (value == null) { - if (type) { - if (Instance.getUiConfig().getBoolean(UiConfig.SOURCE_PAGE, - false)) { - addSourcePanes(); - } else { - for (String tt : reader.getLibrary().getSources()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - } else { - for (String tt : reader.getLibrary().getAuthors()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - - return; - } - - GuiReaderGroup bookPane = new GuiReaderGroup(reader, value, color); - if (type) { - booksByType.put(bookPane, value); - } else { - booksByAuthor.put(bookPane, value); - } - - 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, MouseEvent e) { - JPopupMenu popup = new JPopupMenu(); - popup.add(createMenuItemOpenBook()); - popup.addSeparator(); - popup.add(createMenuItemExport()); - popup.add(createMenuItemMove(true)); - popup.add(createMenuItemSetCover()); - popup.add(createMenuItemClearCache()); - popup.add(createMenuItemRedownload()); - popup.addSeparator(); - popup.add(createMenuItemDelete()); - popup.show(e.getComponent(), e.getX(), e.getY()); - } - - @Override - public void action(final GuiReaderBook book) { - openBook(book); - } - }); + @Override + public JPopupMenu createBookPopup() { + JPopupMenu popup = new JPopupMenu(); + popup.add(createMenuItemOpenBook()); + popup.addSeparator(); + popup.add(createMenuItemExport()); + popup.add(createMenuItemMoveTo(true)); + popup.add(createMenuItemSetCoverForSource()); + popup.add(createMenuItemSetCoverForAuthor()); + popup.add(createMenuItemClearCache()); + popup.add(createMenuItemRedownload()); + popup.addSeparator(); + popup.add(createMenuItemRename(true)); + popup.add(createMenuItemSetAuthor(true)); + popup.addSeparator(); + popup.add(createMenuItemDelete()); + popup.addSeparator(); + popup.add(createMenuItemProperties()); + return popup; } - private void removeBookPanes() { - booksByType.clear(); - booksByAuthor.clear(); - pane.invalidate(); - this.invalidate(); - pane.removeAll(); - pane.validate(); - this.validate(); + @Override + public JPopupMenu createSourceAuthorPopup() { + JPopupMenu popup = new JPopupMenu(); + popup.add(createMenuItemOpenBook()); + return popup; } - /** - * Refresh the list of {@link GuiReaderBook}s from disk. - */ - private void refreshBooks() { - for (GuiReaderGroup group : booksByType.keySet()) { - List stories = reader.getLibrary().getListBySource( - booksByType.get(group)); - group.refreshBooks(stories, words); - } - - for (GuiReaderGroup group : booksByAuthor.keySet()) { - List stories = reader.getLibrary().getListByAuthor( - booksByAuthor.get(group)); - group.refreshBooks(stories, words); - } + @Override + public void createMenu(boolean libOk) { + invalidate(); - pane.repaint(); - this.repaint(); - } + JMenuBar bar = new JMenuBar(); - /** - * Create the main menu bar. - * - * @param libOk - * the library can be queried - * - * @return the bar - */ - private JMenuBar createMenu(boolean libOk) { - bar = new JMenuBar(); - - JMenu file = new JMenu("File"); + JMenu file = new JMenu(GuiReader.trans(StringIdGui.MENU_FILE)); file.setMnemonic(KeyEvent.VK_F); - JMenuItem imprt = new JMenuItem("Import URL...", KeyEvent.VK_U); + JMenuItem imprt = new JMenuItem( + GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_URL), + KeyEvent.VK_U); imprt.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - imprt(true); + mainPanel.imprt(true); } }); - JMenuItem imprtF = new JMenuItem("Import File...", KeyEvent.VK_F); + JMenuItem imprtF = new JMenuItem( + GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_FILE), + KeyEvent.VK_F); imprtF.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - imprt(false); + mainPanel.imprt(false); } }); - JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X); + JMenuItem exit = new JMenuItem( + GuiReader.trans(StringIdGui.MENU_FILE_EXIT), KeyEvent.VK_X); exit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -387,18 +157,25 @@ class GuiReaderFrame extends JFrame { file.add(createMenuItemOpenBook()); file.add(createMenuItemExport()); - file.add(createMenuItemMove(libOk)); + file.add(createMenuItemMoveTo(libOk)); file.addSeparator(); file.add(imprt); file.add(imprtF); file.addSeparator(); + file.add(createMenuItemRename(libOk)); + file.add(createMenuItemSetAuthor(libOk)); + file.addSeparator(); + file.add(createMenuItemProperties()); + file.addSeparator(); file.add(exit); bar.add(file); - JMenu edit = new JMenu("Edit"); + JMenu edit = new JMenu(GuiReader.trans(StringIdGui.MENU_EDIT)); edit.setMnemonic(KeyEvent.VK_E); + edit.add(createMenuItemSetCoverForSource()); + edit.add(createMenuItemSetCoverForAuthor()); edit.add(createMenuItemClearCache()); edit.add(createMenuItemRedownload()); edit.addSeparator(); @@ -406,93 +183,173 @@ class GuiReaderFrame extends JFrame { bar.add(edit); - JMenu view = new JMenu("View"); + 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, "", 1, 0); + } + }); + search.add(searchItem); + } + } + + bar.add(search); + + JMenu view = new JMenu(GuiReader.trans(StringIdGui.MENU_VIEW)); view.setMnemonic(KeyEvent.VK_V); - JMenuItem vauthors = new JMenuItem("Author"); + 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) { - words = false; - refreshBooks(); + mainPanel.setWords(false); + mainPanel.refreshBooks(); } }); view.add(vauthors); - JMenuItem vwords = new JMenuItem("Word count"); + 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) { - words = true; - refreshBooks(); + mainPanel.setWords(true); + mainPanel.refreshBooks(); } }); view.add(vwords); bar.add(view); - JMenu sources = new JMenu("Sources"); + Map> groupedSources = new HashMap>(); + if (libOk) { + groupedSources = reader.getLibrary().getSourcesGrouped(); + } + JMenu sources = new JMenu(GuiReader.trans(StringIdGui.MENU_SOURCES)); sources.setMnemonic(KeyEvent.VK_S); + populateMenuSA(sources, groupedSources, true); + bar.add(sources); - List tt = new ArrayList(); + Map> goupedAuthors = new HashMap>(); if (libOk) { - tt.addAll(reader.getLibrary().getSources()); + goupedAuthors = reader.getLibrary().getAuthorsGrouped(); } - tt.add(0, null); - - for (final String type : tt) { - JMenuItem item = new JMenuItem(type == null ? "All" : type); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeBookPanes(); - addBookPane(type, true); - refreshBooks(); - } - }); - sources.add(item); + JMenu authors = new JMenu(GuiReader.trans(StringIdGui.MENU_AUTHORS)); + authors.setMnemonic(KeyEvent.VK_A); + populateMenuSA(authors, goupedAuthors, false); + bar.add(authors); - if (type == null) { - sources.addSeparator(); - } - } + JMenu options = new JMenu(GuiReader.trans(StringIdGui.MENU_OPTIONS)); + options.setMnemonic(KeyEvent.VK_O); + options.add(createMenuItemConfig()); + options.add(createMenuItemUiConfig()); + bar.add(options); - bar.add(sources); + setJMenuBar(bar); + } - JMenu authors = new JMenu("Authors"); - authors.setMnemonic(KeyEvent.VK_A); + // "" = [unknown] + private void populateMenuSA(JMenu menu, + Map> groupedValues, boolean type) { + + // "All" and "Listing" special items first + JMenuItem item = new JMenuItem( + GuiReader.trans(StringIdGui.MENU_XXX_ALL_GROUPED)); + item.addActionListener(getActionOpenList(type, false)); + menu.add(item); + item = new JMenuItem(GuiReader.trans(StringIdGui.MENU_XXX_ALL_LISTING)); + item.addActionListener(getActionOpenList(type, true)); + menu.add(item); + + menu.addSeparator(); + + for (final String value : groupedValues.keySet()) { + List list = groupedValues.get(value); + if (type && list.size() == 1 && list.get(0).isEmpty()) { + // leaf item source/type + item = new JMenuItem( + value.isEmpty() ? GuiReader + .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) + : value); + item.addActionListener(getActionOpen(value, type)); + menu.add(item); + } else { + JMenu dir; + if (!type && groupedValues.size() == 1) { + // only one group of authors + dir = menu; + } else { + dir = new JMenu( + value.isEmpty() ? GuiReader + .trans(StringIdGui.MENU_AUTHORS_UNKNOWN) + : value); + } - List aa = new ArrayList(); - if (libOk) { - aa.addAll(reader.getLibrary().getAuthors()); - } - aa.add(0, null); - for (final String author : aa) { - JMenuItem item = new JMenuItem(author == null ? "All" - : author.isEmpty() ? "[unknown]" : author); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeBookPanes(); - addBookPane(author, false); - refreshBooks(); + 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); } - }); - authors.add(item); - if (author == null || author.isEmpty()) { - authors.addSeparator(); + if (menu != dir) { + menu.add(dir); + } } } + } - bar.add(authors); - - JMenu options = new JMenu("Options"); - options.setMnemonic(KeyEvent.VK_O); - options.add(createMenuItemConfig()); - options.add(createMenuItemUiConfig()); - bar.add(options); + /** + * 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(); + } + }; + } - return bar; + private ActionListener getActionOpenList(final boolean type, + final boolean listMode) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mainPanel.removeBookPanes(); + mainPanel.addBookPane(type, listMode); + mainPanel.refreshBooks(); + } + }; } /** @@ -501,7 +358,7 @@ class GuiReaderFrame extends JFrame { * @return the item */ private JMenuItem createMenuItemConfig() { - final String title = "Fanfix Configuration"; + final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG); JMenuItem item = new JMenuItem(title); item.setMnemonic(KeyEvent.VK_F); @@ -509,8 +366,8 @@ class GuiReaderFrame extends JFrame { @Override public void actionPerformed(ActionEvent e) { ConfigEditor ed = new ConfigEditor( - Config.class, Instance.getConfig(), - "This is where you configure the options of the program."); + Config.class, Instance.getConfig(), GuiReader + .trans(StringIdGui.SUBTITLE_CONFIG)); JFrame frame = new JFrame(title); frame.add(ed); frame.setSize(800, 600); @@ -527,7 +384,7 @@ class GuiReaderFrame extends JFrame { * @return the item */ private JMenuItem createMenuItemUiConfig() { - final String title = "UI Configuration"; + final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG_UI); JMenuItem item = new JMenuItem(title); item.setMnemonic(KeyEvent.VK_U); @@ -535,8 +392,8 @@ class GuiReaderFrame extends JFrame { @Override public void actionPerformed(ActionEvent e) { ConfigEditor ed = new ConfigEditor( - UiConfig.class, Instance.getUiConfig(), - "This is where you configure the graphical appearence of the program."); + UiConfig.class, Instance.getUiConfig(), GuiReader + .trans(StringIdGui.SUBTITLE_CONFIG_UI)); JFrame frame = new JFrame(title); frame.add(ed); frame.setSize(800, 600); @@ -556,50 +413,47 @@ class GuiReaderFrame extends JFrame { final JFileChooser fc = new JFileChooser(); fc.setAcceptAllFileFilterUsed(false); - final Map filters = new HashMap(); + // Add the "ALL" filters first, then the others + final Map otherFilters = new HashMap(); for (OutputType type : OutputType.values()) { String ext = type.getDefaultExtension(false); String desc = type.getDesc(false); if (ext == null || ext.isEmpty()) { - filters.put(createAllFilter(desc), type); + fc.addChoosableFileFilter(createAllFilter(desc)); } else { - filters.put(new FileNameExtensionFilter(desc, ext), type); + otherFilters.put(new FileNameExtensionFilter(desc, ext), type); } } - // First the "ALL" filters, then, the extension filters - for (Entry entry : filters.entrySet()) { - if (!(entry.getKey() instanceof FileNameExtensionFilter)) { - fc.addChoosableFileFilter(entry.getKey()); - } - } - for (Entry entry : filters.entrySet()) { - if (entry.getKey() instanceof FileNameExtensionFilter) { - fc.addChoosableFileFilter(entry.getKey()); - } + for (Entry entry : otherFilters.entrySet()) { + fc.addChoosableFileFilter(entry.getKey()); } // - JMenuItem export = new JMenuItem("Save as...", KeyEvent.VK_S); + 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, "Save"); + fc.showDialog(GuiReaderFrame.this, + GuiReader.trans(StringIdGui.TITLE_SAVE)); if (fc.getSelectedFile() != null) { - final OutputType type = filters.get(fc.getFileFilter()); + final OutputType type = otherFilters.get(fc + .getFileFilter()); final String path = fc.getSelectedFile() .getAbsolutePath() + type.getDefaultExtension(false); final Progress pg = new Progress(); - outOfUi(pg, new Runnable() { + mainPanel.outOfUi(pg, false, new Runnable() { @Override public void run() { try { reader.getLibrary().export( - selectedBook.getMeta().getLuid(), - type, path, pg); + selectedBook.getInfo().getMeta() + .getLuid(), type, path, pg); } catch (IOException e) { Instance.getTraceHandler().error(e); } @@ -642,18 +496,22 @@ class GuiReaderFrame extends JFrame { * @return the item */ private JMenuItem createMenuItemClearCache() { - JMenuItem refresh = new JMenuItem("Clear cache", KeyEvent.VK_C); + 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) { - outOfUi(null, new Runnable() { + mainPanel.outOfUi(null, false, new Runnable() { @Override public void run() { - reader.clearLocalReaderCache(selectedBook.getMeta() - .getLuid()); + reader.clearLocalReaderCache(selectedBook.getInfo() + .getMeta().getLuid()); selectedBook.setCached(false); - GuiReaderBook.clearIcon(selectedBook.getMeta()); + GuiReaderCoverImager.clearIcon(selectedBook + .getInfo()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -670,100 +528,227 @@ class GuiReaderFrame extends JFrame { } /** - * Create the delete menu item. + * Create the "move to" menu item. * * @param libOk * the library can be queried * * @return the item */ - private JMenuItem createMenuItemMove(boolean libOk) { - JMenu moveTo = new JMenu("Move to..."); - moveTo.setMnemonic(KeyEvent.VK_M); + private JMenuItem createMenuItemMoveTo(boolean libOk) { + JMenu changeTo = new JMenu( + GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO)); + changeTo.setMnemonic(KeyEvent.VK_M); - List types = new ArrayList(); - types.add(null); + Map> groupedSources = new HashMap>(); if (libOk) { - types.addAll(reader.getLibrary().getSources()); + groupedSources = reader.getLibrary().getSourcesGrouped(); } - for (String type : types) { - JMenuItem item = new JMenuItem(type == null ? "New type..." : type); + JMenuItem item = new JMenuItem( + GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE)); + item.addActionListener(createMoveAction(ChangeAction.SOURCE, null)); + changeTo.add(item); + changeTo.addSeparator(); + + for (final String type : groupedSources.keySet()) { + List list = groupedSources.get(type); + if (list.size() == 1 && list.get(0).isEmpty()) { + item = new JMenuItem(type); + item.addActionListener(createMoveAction(ChangeAction.SOURCE, + type)); + changeTo.add(item); + } else { + JMenu dir = new JMenu(type); + for (String sub : list) { + // " " instead of "" for the visual height + String itemName = sub.isEmpty() ? " " : sub; + String actualType = type; + if (!sub.isEmpty()) { + actualType += "/" + sub; + } - moveTo.add(item); - if (type == null) { - moveTo.addSeparator(); + item = new JMenuItem(itemName); + item.addActionListener(createMoveAction( + ChangeAction.SOURCE, actualType)); + dir.add(item); + } + changeTo.add(dir); } + } - final String ftype = type; - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (selectedBook != null) { - String type = ftype; - if (type == null) { - Object rep = JOptionPane.showInputDialog( - GuiReaderFrame.this, "Move to:", - "Moving story", - JOptionPane.QUESTION_MESSAGE, null, null, - selectedBook.getMeta().getSource()); - - if (rep == null) { - return; - } + return changeTo; + } + + /** + * Create the "set author" menu item. + * + * @param libOk + * the library can be queried + * + * @return the item + */ + private JMenuItem createMenuItemSetAuthor(boolean libOk) { + 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 + if (libOk) { + Map> groupedAuthors = reader.getLibrary() + .getAuthorsGrouped(); + + 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; + } - type = rep.toString(); + /** + * Create the "rename" menu item. + * + * @param libOk + * the library can be queried + * + * @return the item + */ + private JMenuItem createMenuItemRename( + @SuppressWarnings("unused") boolean libOk) { + 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(); } - final String ftype = type; - outOfUi(null, new Runnable() { - @Override - public void run() { - reader.changeSource(selectedBook.getMeta() - .getLuid(), ftype); + Object rep = JOptionPane.showInputDialog( + GuiReaderFrame.this, + GuiReader.trans(StringIdGui.SUBTITLE_MOVE_TO), + GuiReader.trans(StringIdGui.TITLE_MOVE_TO), + JOptionPane.QUESTION_MESSAGE, null, null, init); - selectedBook = null; + if (rep == null) { + return; + } - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - setJMenuBar(createMenu(true)); - } - }); - } - }); + changeTo = rep.toString(); } - } - }); - } - return moveTo; + 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(true); + } + }); + } + }); + } + } + }; } /** - * Create the redownload (then delete original) menu item. + * Create the re-download (then delete original) menu item. * * @return the item */ private JMenuItem createMenuItemRedownload() { - JMenuItem refresh = new JMenuItem("Redownload", KeyEvent.VK_R); + 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.getMeta(); - imprt(meta.getUrl(), new StoryRunnable() { + final MetaData meta = selectedBook.getInfo().getMeta(); + mainPanel.imprt(meta.getUrl(), new StoryRunnable() { @Override public void run(Story story) { - reader.delete(meta.getLuid()); - GuiReaderFrame.this.selectedBook = null; MetaData newMeta = story.getMeta(); if (!newMeta.getSource().equals(meta.getSource())) { reader.changeSource(newMeta.getLuid(), meta.getSource()); } } - }, "Removing old copy"); + }, GuiReader.trans(StringIdGui.PROGRESS_CHANGE_SOURCE)); } } }); @@ -777,16 +762,59 @@ class GuiReaderFrame extends JFrame { * @return the item */ private JMenuItem createMenuItemDelete() { - JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D); + 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) { - outOfUi(null, new Runnable() { + mainPanel.outOfUi(null, false, new Runnable() { @Override public void run() { - reader.delete(selectedBook.getMeta().getLuid()); - selectedBook = null; + new GuiReaderPropertiesFrame(reader.getLibrary(), + selectedBook.getInfo().getMeta()) + .setVisible(true); } }); } @@ -797,22 +825,25 @@ class GuiReaderFrame extends JFrame { } /** - * Create the open menu item for a book or a source (no LUID). + * Create the open menu item for a book, a source/type or an author. * * @return the item */ - private JMenuItem createMenuItemOpenBook() { - JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O); + 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.getMeta().getLuid() == null) { - removeBookPanes(); - addBookPane(selectedBook.getMeta().getSource(), true); - refreshBooks(); + if (selectedBook.getInfo().getMeta() == null) { + mainPanel.removeBookPanes(); + mainPanel.addBookPane(selectedBook.getInfo() + .getMainInfo(), mainPanel.getCurrentType()); + mainPanel.refreshBooks(); } else { - openBook(selectedBook); + mainPanel.openBook(selectedBook); } } } @@ -827,214 +858,62 @@ class GuiReaderFrame extends JFrame { * * @return the item */ - private JMenuItem createMenuItemSetCover() { - JMenuItem open = new JMenuItem("Set as cover for source", KeyEvent.VK_C); + 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 e) { + final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); if (selectedBook != null) { - reader.getLibrary().setSourceCover( - selectedBook.getMeta().getSource(), - selectedBook.getMeta().getLuid()); - MetaData source = selectedBook.getMeta().clone(); - source.setLuid(null); - GuiReaderBook.clearIcon(source); - } - } - }); + BasicLibrary lib = reader.getLibrary(); + String luid = selectedBook.getInfo().getMeta().getLuid(); + String source = selectedBook.getInfo().getMeta() + .getSource(); - return open; - } + lib.setSourceCover(source, luid); - /** - * Open a {@link GuiReaderBook} item. - * - * @param book - * the {@link GuiReaderBook} to open - */ - private void openBook(final GuiReaderBook book) { - final Progress pg = new Progress(); - outOfUi(pg, new Runnable() { - @Override - public void run() { - try { - reader.read(book.getMeta().getLuid(), pg); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - book.setCached(true); - } - }); - } catch (IOException e) { - // TODO: error message? - Instance.getTraceHandler().error(e); + GuiReaderBookInfo sourceInfo = GuiReaderBookInfo + .fromSource(lib, source); + GuiReaderCoverImager.clearIcon(sourceInfo); } } }); - } - - /** - * Process the given action out of the Swing UI thread and link the given - * {@link ProgressBar} to the action. - *

- * The code will make sure that the {@link ProgressBar} (if not NULL) is set - * to done when the action is done. - * - * @param progress - * the {@link ProgressBar} or NULL - * @param run - * the action to run - */ - private void outOfUi(Progress progress, final Runnable run) { - final Progress pg = new Progress(); - final Progress reload = new Progress("Reload books"); - if (progress == null) { - progress = new Progress(); - } - - 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(); - refreshBooks(); - } finally { - reload.done(); - if (!pg.isDone()) { - // will trigger pgBar ActionListener: - pg.done(); - } - } - } - }, "outOfUi thread").start(); - } - - /** - * Import a {@link Story} into the main {@link LocalLibrary}. - *

- * Should be called inside the UI thread. - * - * @param askUrl - * TRUE for an {@link URL}, false for a {@link File} - */ - private 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 = ""; - } - - url = JOptionPane.showInputDialog(GuiReaderFrame.this, - "url of the story to import?", "Importing from 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); - } + return open; } /** - * Actually import the {@link Story} into the main {@link LocalLibrary}. - *

- * Should be called inside the UI thread. + * Create the SetCover menu item for a book to change the linked source + * cover. * - * @param url - * the {@link Story} to import by {@link URL} - * @param onSuccess - * Action to execute on success + * @return the item */ - private void imprt(final String url, final StoryRunnable 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, new Runnable() { + 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 run() { - Exception ex = null; - Story story = null; - try { - story = reader.getLibrary().imprt(BasicReader.getUrl(url), - pgImprt); - } catch (IOException e) { - ex = e; - } - - final Exception e = ex; + public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = mainPanel.getSelectedBook(); + if (selectedBook != null) { + BasicLibrary lib = reader.getLibrary(); + String luid = selectedBook.getInfo().getMeta().getLuid(); + String author = selectedBook.getInfo().getMeta() + .getAuthor(); - final boolean ok = (e == null); + lib.setAuthorCover(author, luid); - pgOnSuccess.setProgress(0); - if (!ok) { - if (e instanceof UnknownHostException) { - error("URL not supported: " + url, "Cannot import URL", - null); - } else { - error("Failed to import " + url + ": \n" - + e.getMessage(), "Cannot import URL", e); - } - } else { - if (onSuccess != null) { - onSuccess.run(story); - } + GuiReaderBookInfo authorInfo = GuiReaderBookInfo + .fromAuthor(lib, author); + GuiReaderCoverImager.clearIcon(authorInfo); } - pgOnSuccess.done(); } }); - } - /** - * Enables or disables this component, depending on the value of the - * parameter b. An enabled component can respond to user input - * and generate events. Components are enabled initially by default. - *

- * Disabling this component will also affect its children. - * - * @param b - * If true, this component is enabled; otherwise - * this component is disabled - */ - @Override - public void setEnabled(boolean b) { - if (bar != null) { - bar.setEnabled(b); - } - - for (GuiReaderGroup group : booksByType.keySet()) { - group.setEnabled(b); - } - for (GuiReaderGroup group : booksByAuthor.keySet()) { - group.setEnabled(b); - } - super.setEnabled(b); - repaint(); + return open; } /** @@ -1047,7 +926,7 @@ class GuiReaderFrame extends JFrame { * @param e * the exception to log if any */ - private void error(final String message, final String title, Exception e) { + public void error(final String message, final String title, Exception e) { Instance.getTraceHandler().error(title + ": " + message); if (e != null) { Instance.getTraceHandler().error(e); @@ -1061,4 +940,28 @@ class GuiReaderFrame extends JFrame { } }); } + + @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()); + } }