Fix some CBZ cover/fake cover issues
[fanfix.git] / src / be / nikiroo / fanfix / reader / GuiReaderFrame.java
CommitLineData
a6395bef
NR
1package be.nikiroo.fanfix.reader;
2
d3c84ac3 3import java.awt.BorderLayout;
b4dc6ab5 4import java.awt.Color;
4d205683 5import java.awt.Frame;
5130ce84
NR
6import java.awt.Toolkit;
7import java.awt.datatransfer.DataFlavor;
a6395bef
NR
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
333f0e7b 10import java.awt.event.KeyEvent;
9843a5e5 11import java.awt.event.MouseEvent;
333f0e7b
NR
12import java.awt.event.WindowEvent;
13import java.io.File;
a6395bef 14import java.io.IOException;
4d205683 15import java.net.URL;
fd1d31c2 16import java.net.UnknownHostException;
70c9b112 17import java.util.ArrayList;
4d205683 18import java.util.HashMap;
a6395bef 19import java.util.List;
4d205683
NR
20import java.util.Map;
21import java.util.Map.Entry;
a6395bef 22
4310bae9 23import javax.swing.BoxLayout;
10d558d2 24import javax.swing.JFileChooser;
a6395bef 25import javax.swing.JFrame;
99ccbdf6 26import javax.swing.JLabel;
333f0e7b
NR
27import javax.swing.JMenu;
28import javax.swing.JMenuBar;
29import javax.swing.JMenuItem;
30import javax.swing.JOptionPane;
31import javax.swing.JPanel;
9843a5e5 32import javax.swing.JPopupMenu;
d3c84ac3 33import javax.swing.JScrollPane;
99ccbdf6 34import javax.swing.SwingConstants;
3b2b638f 35import javax.swing.SwingUtilities;
4d205683
NR
36import javax.swing.filechooser.FileFilter;
37import javax.swing.filechooser.FileNameExtensionFilter;
a6395bef
NR
38
39import be.nikiroo.fanfix.Instance;
e8eeea0a 40import be.nikiroo.fanfix.bundles.Config;
b4dc6ab5 41import be.nikiroo.fanfix.bundles.UiConfig;
a6395bef 42import be.nikiroo.fanfix.data.MetaData;
4d205683 43import be.nikiroo.fanfix.data.Story;
e6249b0f
NR
44import be.nikiroo.fanfix.library.BasicLibrary;
45import be.nikiroo.fanfix.library.BasicLibrary.Status;
e42573a0 46import be.nikiroo.fanfix.library.LocalLibrary;
4d205683 47import be.nikiroo.fanfix.output.BasicOutput.OutputType;
5dd985cf 48import be.nikiroo.fanfix.reader.GuiReaderBook.BookActionListener;
3b2b638f 49import be.nikiroo.utils.Progress;
39c3c689 50import be.nikiroo.utils.Version;
e8eeea0a 51import be.nikiroo.utils.ui.ConfigEditor;
3b2b638f 52import be.nikiroo.utils.ui.ProgressBar;
a6395bef 53
4d205683 54/**
5dd985cf 55 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
4d205683 56 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
5dd985cf
NR
57 * way to copy them to the {@link GuiReader} cache (
58 * {@link BasicReader#getLibrary()}), read them, delete them...
4d205683
NR
59 *
60 * @author niki
61 */
5dd985cf 62class GuiReaderFrame extends JFrame {
a6395bef 63 private static final long serialVersionUID = 1L;
5dd985cf
NR
64 private GuiReader reader;
65 private Map<GuiReaderGroup, String> booksByType;
66 private Map<GuiReaderGroup, String> booksByAuthor;
4310bae9 67 private JPanel pane;
b4dc6ab5 68 private Color color;
3b2b638f
NR
69 private ProgressBar pgBar;
70 private JMenuBar bar;
5dd985cf 71 private GuiReaderBook selectedBook;
793f1071 72 private boolean words; // words or authors (secondary info on books)
a6395bef 73
c8faa52a
NR
74 /**
75 * A {@link Runnable} with a {@link Story} parameter.
76 *
77 * @author niki
78 */
79 private interface StoryRunnable {
80 /**
81 * Run the action.
82 *
83 * @param story
84 * the story
85 */
86 public void run(Story story);
87 }
88
4d205683 89 /**
5dd985cf 90 * Create a new {@link GuiReaderFrame}.
4d205683
NR
91 *
92 * @param reader
5dd985cf
NR
93 * the associated {@link GuiReader} to forward some commands and
94 * access its {@link LocalLibrary}
4d205683
NR
95 * @param type
96 * the type of {@link Story} to load, or NULL for all types
97 */
5dd985cf 98 public GuiReaderFrame(GuiReader reader, String type) {
39c3c689 99 super(String.format("Fanfix %s Library", Version.getCurrentVersion()));
a6395bef
NR
100
101 this.reader = reader;
102
103 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
104 setSize(800, 600);
d3c84ac3 105 setLayout(new BorderLayout());
a6395bef 106
4310bae9
NR
107 pane = new JPanel();
108 pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
b4dc6ab5 109
3b2b638f 110 color = Instance.getUiConfig().getColor(UiConfig.BACKGROUND_COLOR);
b4dc6ab5
NR
111 if (color != null) {
112 setBackground(color);
4310bae9 113 pane.setBackground(color);
b4dc6ab5 114 }
d3c84ac3 115
4310bae9 116 JScrollPane scroll = new JScrollPane(pane);
b4dc6ab5
NR
117 scroll.getVerticalScrollBar().setUnitIncrement(16);
118 add(scroll, BorderLayout.CENTER);
a6395bef 119
99ccbdf6
NR
120 String message = reader.getLibrary().getLibraryName();
121 if (!message.isEmpty()) {
122 JLabel name = new JLabel(message, SwingConstants.CENTER);
123 add(name, BorderLayout.NORTH);
124 }
125
3b2b638f
NR
126 pgBar = new ProgressBar();
127 add(pgBar, BorderLayout.SOUTH);
128
754a5bc2 129 pgBar.addActionListener(new ActionListener() {
211f7ddb 130 @Override
754a5bc2
NR
131 public void actionPerformed(ActionEvent e) {
132 invalidate();
133 pgBar.setProgress(null);
134 validate();
135 setEnabled(true);
136 }
137 });
138
139 pgBar.addUpdateListener(new ActionListener() {
211f7ddb 140 @Override
754a5bc2 141 public void actionPerformed(ActionEvent e) {
80791ae8
NR
142 invalidate();
143 validate();
754a5bc2
NR
144 repaint();
145 }
146 });
147
5dd985cf
NR
148 booksByType = new HashMap<GuiReaderGroup, String>();
149 booksByAuthor = new HashMap<GuiReaderGroup, String>();
4310bae9 150
4f661b2b
NR
151 pane.setVisible(false);
152 final Progress pg = new Progress();
153 final String typeF = type;
154 outOfUi(pg, new Runnable() {
211f7ddb 155 @Override
4f661b2b 156 public void run() {
e6249b0f
NR
157 BasicLibrary lib = GuiReaderFrame.this.reader.getLibrary();
158 Status status = lib.getStatus();
159
160 if (status == Status.READY) {
161 lib.refresh(pg);
162 invalidate();
163 setJMenuBar(createMenu(true));
164 addBookPane(typeF, true);
165 refreshBooks();
166 validate();
167 pane.setVisible(true);
168 } else {
169 invalidate();
170 setJMenuBar(createMenu(false));
171 validate();
172
173 String err = lib.getLibraryName() + "\n";
174 switch (status) {
175 case INVALID:
176 err += "Library not valid";
177 break;
178
179 case UNAUTORIZED:
180 err += "You are not allowed to access this library";
181 break;
182
183 case UNAVAILABLE:
184 err += "Library currently unavilable";
185 break;
186
187 default:
188 err += "An error occured when contacting the library";
189 break;
190 }
191
192 error(err, "Library error", null);
193 }
4f661b2b
NR
194 }
195 });
4310bae9 196
333f0e7b
NR
197 setVisible(true);
198 }
199
14b57448
NR
200 private void addSourcePanes() {
201 // Sources -> i18n
202 GuiReaderGroup bookPane = new GuiReaderGroup(reader, "Sources", color);
203
204 List<MetaData> sources = new ArrayList<MetaData>();
205 for (String source : reader.getLibrary().getSources()) {
206 MetaData mSource = new MetaData();
207 mSource.setLuid(null);
208 mSource.setTitle(source);
209 mSource.setSource(source);
210 sources.add(mSource);
211 }
212
213 bookPane.refreshBooks(sources, false);
214
215 this.invalidate();
216 pane.invalidate();
217 pane.add(bookPane);
218 pane.validate();
219 this.validate();
220
221 bookPane.setActionListener(new BookActionListener() {
222 @Override
223 public void select(GuiReaderBook book) {
224 selectedBook = book;
225 }
226
227 @Override
228 public void popupRequested(GuiReaderBook book, MouseEvent e) {
229 JPopupMenu popup = new JPopupMenu();
230 popup.add(createMenuItemOpenBook());
231 popup.show(e.getComponent(), e.getX(), e.getY());
232 }
233
234 @Override
235 public void action(final GuiReaderBook book) {
236 removeBookPanes();
237 addBookPane(book.getMeta().getSource(), true);
238 refreshBooks();
239 }
240 });
241 }
242
4d205683 243 /**
5dd985cf
NR
244 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
245 * selected type or author.
4d205683 246 *
4310bae9 247 * @param value
70c9b112
NR
248 * the author or the type, or NULL to get all the
249 * authors-or-types
4d205683 250 * @param type
4310bae9 251 * TRUE for type, FALSE for author
4d205683 252 */
4310bae9
NR
253 private void addBookPane(String value, boolean type) {
254 if (value == null) {
255 if (type) {
14b57448
NR
256 if (Instance.getUiConfig().getBoolean(UiConfig.SOURCE_PAGE,
257 false)) {
258 addSourcePanes();
259 } else {
260 for (String tt : reader.getLibrary().getSources()) {
261 if (tt != null) {
262 addBookPane(tt, type);
263 }
4310bae9
NR
264 }
265 }
266 } else {
e42573a0 267 for (String tt : reader.getLibrary().getAuthors()) {
4310bae9
NR
268 if (tt != null) {
269 addBookPane(tt, type);
270 }
271 }
b4dc6ab5
NR
272 }
273
4310bae9
NR
274 return;
275 }
4d205683 276
5dd985cf 277 GuiReaderGroup bookPane = new GuiReaderGroup(reader, value, color);
4310bae9
NR
278 if (type) {
279 booksByType.put(bookPane, value);
280 } else {
281 booksByAuthor.put(bookPane, value);
282 }
333f0e7b 283
4310bae9
NR
284 this.invalidate();
285 pane.invalidate();
286 pane.add(bookPane);
287 pane.validate();
288 this.validate();
9843a5e5 289
4310bae9 290 bookPane.setActionListener(new BookActionListener() {
211f7ddb 291 @Override
5dd985cf 292 public void select(GuiReaderBook book) {
4310bae9
NR
293 selectedBook = book;
294 }
295
211f7ddb 296 @Override
5dd985cf 297 public void popupRequested(GuiReaderBook book, MouseEvent e) {
4310bae9
NR
298 JPopupMenu popup = new JPopupMenu();
299 popup.add(createMenuItemOpenBook());
300 popup.addSeparator();
301 popup.add(createMenuItemExport());
e6249b0f 302 popup.add(createMenuItemMove(true));
14b57448 303 popup.add(createMenuItemSetCover());
4310bae9
NR
304 popup.add(createMenuItemClearCache());
305 popup.add(createMenuItemRedownload());
306 popup.addSeparator();
307 popup.add(createMenuItemDelete());
308 popup.show(e.getComponent(), e.getX(), e.getY());
309 }
310
211f7ddb 311 @Override
5dd985cf 312 public void action(final GuiReaderBook book) {
4310bae9
NR
313 openBook(book);
314 }
315 });
316 }
a6395bef 317
4310bae9
NR
318 private void removeBookPanes() {
319 booksByType.clear();
320 booksByAuthor.clear();
321 pane.invalidate();
322 this.invalidate();
323 pane.removeAll();
324 pane.validate();
325 this.validate();
326 }
327
328 /**
5dd985cf 329 * Refresh the list of {@link GuiReaderBook}s from disk.
4310bae9 330 *
4310bae9
NR
331 */
332 private void refreshBooks() {
5dd985cf 333 for (GuiReaderGroup group : booksByType.keySet()) {
e42573a0 334 List<MetaData> stories = reader.getLibrary().getListBySource(
4310bae9 335 booksByType.get(group));
793f1071 336 group.refreshBooks(stories, words);
4310bae9
NR
337 }
338
5dd985cf 339 for (GuiReaderGroup group : booksByAuthor.keySet()) {
e42573a0 340 List<MetaData> stories = reader.getLibrary().getListByAuthor(
4310bae9 341 booksByAuthor.get(group));
793f1071 342 group.refreshBooks(stories, words);
a6395bef
NR
343 }
344
4310bae9
NR
345 pane.repaint();
346 this.repaint();
333f0e7b
NR
347 }
348
4d205683
NR
349 /**
350 * Create the main menu bar.
351 *
e6249b0f
NR
352 * @param libOk
353 * the library can be queried
354 *
4d205683
NR
355 * @return the bar
356 */
e6249b0f 357 private JMenuBar createMenu(boolean libOk) {
3b2b638f 358 bar = new JMenuBar();
333f0e7b
NR
359
360 JMenu file = new JMenu("File");
10d558d2 361 file.setMnemonic(KeyEvent.VK_F);
333f0e7b 362
22848428 363 JMenuItem imprt = new JMenuItem("Import URL...", KeyEvent.VK_U);
333f0e7b 364 imprt.addActionListener(new ActionListener() {
211f7ddb 365 @Override
333f0e7b 366 public void actionPerformed(ActionEvent e) {
10d558d2
NR
367 imprt(true);
368 }
369 });
22848428 370 JMenuItem imprtF = new JMenuItem("Import File...", KeyEvent.VK_F);
10d558d2 371 imprtF.addActionListener(new ActionListener() {
211f7ddb 372 @Override
10d558d2
NR
373 public void actionPerformed(ActionEvent e) {
374 imprt(false);
375 }
376 });
377 JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
378 exit.addActionListener(new ActionListener() {
211f7ddb 379 @Override
10d558d2 380 public void actionPerformed(ActionEvent e) {
5dd985cf
NR
381 GuiReaderFrame.this.dispatchEvent(new WindowEvent(
382 GuiReaderFrame.this, WindowEvent.WINDOW_CLOSING));
10d558d2
NR
383 }
384 });
3b2b638f 385
9843a5e5
NR
386 file.add(createMenuItemOpenBook());
387 file.add(createMenuItemExport());
e6249b0f 388 file.add(createMenuItemMove(libOk));
9843a5e5 389 file.addSeparator();
10d558d2
NR
390 file.add(imprt);
391 file.add(imprtF);
392 file.addSeparator();
393 file.add(exit);
3b2b638f 394
10d558d2
NR
395 bar.add(file);
396
397 JMenu edit = new JMenu("Edit");
398 edit.setMnemonic(KeyEvent.VK_E);
399
22848428
NR
400 edit.add(createMenuItemClearCache());
401 edit.add(createMenuItemRedownload());
9843a5e5
NR
402 edit.addSeparator();
403 edit.add(createMenuItemDelete());
404
405 bar.add(edit);
406
793f1071
NR
407 JMenu view = new JMenu("View");
408 view.setMnemonic(KeyEvent.VK_V);
409 JMenuItem vauthors = new JMenuItem("Author");
410 vauthors.setMnemonic(KeyEvent.VK_A);
411 vauthors.addActionListener(new ActionListener() {
211f7ddb 412 @Override
793f1071
NR
413 public void actionPerformed(ActionEvent e) {
414 words = false;
415 refreshBooks();
416 }
417 });
418 view.add(vauthors);
419 JMenuItem vwords = new JMenuItem("Word count");
420 vwords.setMnemonic(KeyEvent.VK_W);
421 vwords.addActionListener(new ActionListener() {
211f7ddb 422 @Override
793f1071
NR
423 public void actionPerformed(ActionEvent e) {
424 words = true;
425 refreshBooks();
426 }
427 });
428 view.add(vwords);
429 bar.add(view);
430
4310bae9
NR
431 JMenu sources = new JMenu("Sources");
432 sources.setMnemonic(KeyEvent.VK_S);
9843a5e5 433
e6249b0f
NR
434 List<String> tt = new ArrayList<String>();
435 if (libOk) {
436 tt.addAll(reader.getLibrary().getSources());
437 }
9843a5e5 438 tt.add(0, null);
e6249b0f 439
9843a5e5 440 for (final String type : tt) {
4310bae9 441 JMenuItem item = new JMenuItem(type == null ? "All" : type);
9843a5e5 442 item.addActionListener(new ActionListener() {
211f7ddb 443 @Override
9843a5e5 444 public void actionPerformed(ActionEvent e) {
4310bae9
NR
445 removeBookPanes();
446 addBookPane(type, true);
447 refreshBooks();
9843a5e5
NR
448 }
449 });
4310bae9 450 sources.add(item);
9843a5e5
NR
451
452 if (type == null) {
4310bae9 453 sources.addSeparator();
9843a5e5
NR
454 }
455 }
10d558d2 456
4310bae9
NR
457 bar.add(sources);
458
459 JMenu authors = new JMenu("Authors");
460 authors.setMnemonic(KeyEvent.VK_A);
461
e6249b0f
NR
462 List<String> aa = new ArrayList<String>();
463 if (libOk) {
464 aa.addAll(reader.getLibrary().getAuthors());
465 }
4310bae9
NR
466 aa.add(0, null);
467 for (final String author : aa) {
468 JMenuItem item = new JMenuItem(author == null ? "All"
469 : author.isEmpty() ? "[unknown]" : author);
470 item.addActionListener(new ActionListener() {
211f7ddb 471 @Override
4310bae9
NR
472 public void actionPerformed(ActionEvent e) {
473 removeBookPanes();
474 addBookPane(author, false);
475 refreshBooks();
476 }
477 });
478 authors.add(item);
479
480 if (author == null || author.isEmpty()) {
481 authors.addSeparator();
482 }
483 }
484
485 bar.add(authors);
9843a5e5 486
e8eeea0a
NR
487 JMenu options = new JMenu("Options");
488 options.setMnemonic(KeyEvent.VK_O);
489 options.add(createMenuItemConfig());
490 options.add(createMenuItemUiConfig());
491 bar.add(options);
492
9843a5e5
NR
493 return bar;
494 }
495
e8eeea0a
NR
496 /**
497 * Create the Fanfix Configuration menu item.
498 *
499 * @return the item
500 */
501 private JMenuItem createMenuItemConfig() {
502 final String title = "Fanfix Configuration";
503 JMenuItem item = new JMenuItem(title);
504 item.setMnemonic(KeyEvent.VK_F);
505
506 item.addActionListener(new ActionListener() {
211f7ddb 507 @Override
e8eeea0a
NR
508 public void actionPerformed(ActionEvent e) {
509 ConfigEditor<Config> ed = new ConfigEditor<Config>(
510 Config.class, Instance.getConfig(),
511 "This is where you configure the options of the program.");
512 JFrame frame = new JFrame(title);
513 frame.add(ed);
514 frame.setSize(800, 600);
515 frame.setVisible(true);
516 }
517 });
518
519 return item;
520 }
521
522 /**
523 * Create the UI Configuration menu item.
524 *
525 * @return the item
526 */
527 private JMenuItem createMenuItemUiConfig() {
528 final String title = "UI Configuration";
529 JMenuItem item = new JMenuItem(title);
530 item.setMnemonic(KeyEvent.VK_U);
531
532 item.addActionListener(new ActionListener() {
211f7ddb 533 @Override
e8eeea0a
NR
534 public void actionPerformed(ActionEvent e) {
535 ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(
536 UiConfig.class, Instance.getUiConfig(),
537 "This is where you configure the graphical appearence of the program.");
538 JFrame frame = new JFrame(title);
539 frame.add(ed);
540 frame.setSize(800, 600);
541 frame.setVisible(true);
542 }
543 });
544
545 return item;
546 }
547
4d205683
NR
548 /**
549 * Create the export menu item.
550 *
551 * @return the item
552 */
9843a5e5 553 private JMenuItem createMenuItemExport() {
4d205683
NR
554 final JFileChooser fc = new JFileChooser();
555 fc.setAcceptAllFileFilterUsed(false);
556
557 final Map<FileFilter, OutputType> filters = new HashMap<FileFilter, OutputType>();
558 for (OutputType type : OutputType.values()) {
559 String ext = type.getDefaultExtension(false);
560 String desc = type.getDesc(false);
b2612f9d 561
4d205683
NR
562 if (ext == null || ext.isEmpty()) {
563 filters.put(createAllFilter(desc), type);
564 } else {
565 filters.put(new FileNameExtensionFilter(desc, ext), type);
566 }
567 }
568
569 // First the "ALL" filters, then, the extension filters
570 for (Entry<FileFilter, OutputType> entry : filters.entrySet()) {
571 if (!(entry.getKey() instanceof FileNameExtensionFilter)) {
572 fc.addChoosableFileFilter(entry.getKey());
573 }
574 }
575 for (Entry<FileFilter, OutputType> entry : filters.entrySet()) {
576 if (entry.getKey() instanceof FileNameExtensionFilter) {
577 fc.addChoosableFileFilter(entry.getKey());
578 }
579 }
580 //
9843a5e5 581
4d205683 582 JMenuItem export = new JMenuItem("Save as...", KeyEvent.VK_S);
10d558d2 583 export.addActionListener(new ActionListener() {
211f7ddb 584 @Override
10d558d2 585 public void actionPerformed(ActionEvent e) {
4d205683 586 if (selectedBook != null) {
5dd985cf 587 fc.showDialog(GuiReaderFrame.this, "Save");
b2612f9d
NR
588 if (fc.getSelectedFile() != null) {
589 final OutputType type = filters.get(fc.getFileFilter());
590 final String path = fc.getSelectedFile()
591 .getAbsolutePath()
592 + type.getDefaultExtension(false);
593 final Progress pg = new Progress();
594 outOfUi(pg, new Runnable() {
211f7ddb 595 @Override
b2612f9d
NR
596 public void run() {
597 try {
e42573a0 598 reader.getLibrary().export(
b2612f9d
NR
599 selectedBook.getMeta().getLuid(),
600 type, path, pg);
601 } catch (IOException e) {
62c63b07 602 Instance.getTraceHandler().error(e);
b2612f9d 603 }
4d205683 604 }
b2612f9d
NR
605 });
606 }
4d205683 607 }
10d558d2
NR
608 }
609 });
610
9843a5e5
NR
611 return export;
612 }
613
edd46289
NR
614 /**
615 * Create a {@link FileFilter} that accepts all files and return the given
616 * description.
617 *
618 * @param desc
619 * the description
620 *
621 * @return the filter
622 */
4d205683
NR
623 private FileFilter createAllFilter(final String desc) {
624 return new FileFilter() {
625 @Override
626 public String getDescription() {
627 return desc;
628 }
629
630 @Override
631 public boolean accept(File f) {
632 return true;
633 }
634 };
635 }
636
637 /**
638 * Create the refresh (delete cache) menu item.
639 *
640 * @return the item
641 */
22848428 642 private JMenuItem createMenuItemClearCache() {
4d205683 643 JMenuItem refresh = new JMenuItem("Clear cache", KeyEvent.VK_C);
10d558d2 644 refresh.addActionListener(new ActionListener() {
211f7ddb 645 @Override
10d558d2
NR
646 public void actionPerformed(ActionEvent e) {
647 if (selectedBook != null) {
648 outOfUi(null, new Runnable() {
211f7ddb 649 @Override
10d558d2 650 public void run() {
754a5bc2
NR
651 reader.clearLocalReaderCache(selectedBook.getMeta()
652 .getLuid());
10d558d2 653 selectedBook.setCached(false);
085a2f9a 654 GuiReaderBook.clearIcon(selectedBook.getMeta());
3b2b638f 655 SwingUtilities.invokeLater(new Runnable() {
211f7ddb 656 @Override
3b2b638f 657 public void run() {
10d558d2 658 selectedBook.repaint();
3b2b638f
NR
659 }
660 });
661 }
662 });
333f0e7b
NR
663 }
664 }
665 });
10d558d2 666
9843a5e5
NR
667 return refresh;
668 }
669
70c9b112
NR
670 /**
671 * Create the delete menu item.
672 *
e6249b0f
NR
673 * @param libOk
674 * the library can be queried
675 *
70c9b112
NR
676 * @return the item
677 */
e6249b0f 678 private JMenuItem createMenuItemMove(boolean libOk) {
70c9b112
NR
679 JMenu moveTo = new JMenu("Move to...");
680 moveTo.setMnemonic(KeyEvent.VK_M);
681
682 List<String> types = new ArrayList<String>();
683 types.add(null);
e6249b0f
NR
684 if (libOk) {
685 types.addAll(reader.getLibrary().getSources());
686 }
70c9b112
NR
687
688 for (String type : types) {
689 JMenuItem item = new JMenuItem(type == null ? "New type..." : type);
690
691 moveTo.add(item);
692 if (type == null) {
693 moveTo.addSeparator();
694 }
695
696 final String ftype = type;
697 item.addActionListener(new ActionListener() {
211f7ddb 698 @Override
70c9b112
NR
699 public void actionPerformed(ActionEvent e) {
700 if (selectedBook != null) {
701 String type = ftype;
702 if (type == null) {
703 Object rep = JOptionPane.showInputDialog(
5dd985cf 704 GuiReaderFrame.this, "Move to:",
70c9b112
NR
705 "Moving story",
706 JOptionPane.QUESTION_MESSAGE, null, null,
707 selectedBook.getMeta().getSource());
14b57448 708
70c9b112
NR
709 if (rep == null) {
710 return;
70c9b112 711 }
211f7ddb
NR
712
713 type = rep.toString();
70c9b112
NR
714 }
715
716 final String ftype = type;
717 outOfUi(null, new Runnable() {
211f7ddb 718 @Override
70c9b112
NR
719 public void run() {
720 reader.changeType(selectedBook.getMeta()
721 .getLuid(), ftype);
722
723 selectedBook = null;
724
725 SwingUtilities.invokeLater(new Runnable() {
211f7ddb 726 @Override
70c9b112 727 public void run() {
e6249b0f 728 setJMenuBar(createMenu(true));
70c9b112
NR
729 }
730 });
731 }
732 });
733 }
734 }
735 });
736 }
737
738 return moveTo;
739 }
740
22848428
NR
741 /**
742 * Create the redownload (then delete original) menu item.
743 *
744 * @return the item
745 */
746 private JMenuItem createMenuItemRedownload() {
747 JMenuItem refresh = new JMenuItem("Redownload", KeyEvent.VK_R);
748 refresh.addActionListener(new ActionListener() {
211f7ddb 749 @Override
22848428
NR
750 public void actionPerformed(ActionEvent e) {
751 if (selectedBook != null) {
754a5bc2 752 final MetaData meta = selectedBook.getMeta();
c8faa52a 753 imprt(meta.getUrl(), new StoryRunnable() {
211f7ddb 754 @Override
c8faa52a 755 public void run(Story story) {
754a5bc2 756 reader.delete(meta.getLuid());
5dd985cf 757 GuiReaderFrame.this.selectedBook = null;
c8faa52a
NR
758 MetaData newMeta = story.getMeta();
759 if (!newMeta.getSource().equals(meta.getSource())) {
760 reader.changeType(newMeta.getLuid(),
761 meta.getSource());
762 }
22848428 763 }
754a5bc2 764 }, "Removing old copy");
22848428
NR
765 }
766 }
767 });
768
769 return refresh;
770 }
771
4d205683
NR
772 /**
773 * Create the delete menu item.
774 *
775 * @return the item
776 */
9843a5e5 777 private JMenuItem createMenuItemDelete() {
10d558d2
NR
778 JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D);
779 delete.addActionListener(new ActionListener() {
211f7ddb 780 @Override
10d558d2
NR
781 public void actionPerformed(ActionEvent e) {
782 if (selectedBook != null) {
783 outOfUi(null, new Runnable() {
211f7ddb 784 @Override
10d558d2 785 public void run() {
22848428 786 reader.delete(selectedBook.getMeta().getLuid());
10d558d2 787 selectedBook = null;
10d558d2
NR
788 }
789 });
790 }
791 }
792 });
793
9843a5e5
NR
794 return delete;
795 }
10d558d2 796
4d205683 797 /**
14b57448 798 * Create the open menu item for a book or a source (no LUID).
4d205683
NR
799 *
800 * @return the item
801 */
9843a5e5
NR
802 private JMenuItem createMenuItemOpenBook() {
803 JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O);
804 open.addActionListener(new ActionListener() {
211f7ddb 805 @Override
9843a5e5
NR
806 public void actionPerformed(ActionEvent e) {
807 if (selectedBook != null) {
14b57448
NR
808 if (selectedBook.getMeta().getLuid() == null) {
809 removeBookPanes();
810 addBookPane(selectedBook.getMeta().getSource(), true);
811 refreshBooks();
812 } else {
813 openBook(selectedBook);
814 }
815 }
816 }
817 });
818
819 return open;
820 }
821
822 /**
823 * Create the SetCover menu item for a book to change the linked source
824 * cover.
825 *
826 * @return the item
827 */
828 private JMenuItem createMenuItemSetCover() {
829 JMenuItem open = new JMenuItem("Set as cover for source", KeyEvent.VK_C);
830 open.addActionListener(new ActionListener() {
831 @Override
832 public void actionPerformed(ActionEvent e) {
833 if (selectedBook != null) {
834 reader.getLibrary().setSourceCover(
835 selectedBook.getMeta().getSource(),
836 selectedBook.getMeta().getLuid());
085a2f9a
NR
837 MetaData source = selectedBook.getMeta().clone();
838 source.setLuid(null);
839 GuiReaderBook.clearIcon(source);
9843a5e5
NR
840 }
841 }
842 });
10d558d2 843
9843a5e5
NR
844 return open;
845 }
10d558d2 846
4d205683 847 /**
5dd985cf 848 * Open a {@link GuiReaderBook} item.
4d205683
NR
849 *
850 * @param book
5dd985cf 851 * the {@link GuiReaderBook} to open
4d205683 852 */
5dd985cf 853 private void openBook(final GuiReaderBook book) {
9843a5e5
NR
854 final Progress pg = new Progress();
855 outOfUi(pg, new Runnable() {
211f7ddb 856 @Override
9843a5e5
NR
857 public void run() {
858 try {
bc2ea776 859 reader.read(book.getMeta().getLuid(), pg);
edd46289 860 SwingUtilities.invokeLater(new Runnable() {
211f7ddb 861 @Override
edd46289
NR
862 public void run() {
863 book.setCached(true);
9843a5e5 864 }
edd46289 865 });
9843a5e5 866 } catch (IOException e) {
edd46289 867 // TODO: error message?
62c63b07 868 Instance.getTraceHandler().error(e);
333f0e7b 869 }
10d558d2 870 }
9843a5e5 871 });
a6395bef 872 }
3b2b638f 873
4d205683
NR
874 /**
875 * Process the given action out of the Swing UI thread and link the given
876 * {@link ProgressBar} to the action.
877 * <p>
878 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
879 * to done when the action is done.
880 *
5dd985cf 881 * @param progress
4d205683
NR
882 * the {@link ProgressBar} or NULL
883 * @param run
884 * the action to run
885 */
754a5bc2
NR
886 private void outOfUi(Progress progress, final Runnable run) {
887 final Progress pg = new Progress();
d5a7153c 888 final Progress reload = new Progress("Reload books");
754a5bc2
NR
889 if (progress == null) {
890 progress = new Progress();
891 }
3b2b638f 892
754a5bc2
NR
893 pg.addProgress(progress, 90);
894 pg.addProgress(reload, 10);
895
896 invalidate();
897 pgBar.setProgress(pg);
898 validate();
a4143cd7 899 setEnabled(false);
3b2b638f
NR
900
901 new Thread(new Runnable() {
211f7ddb 902 @Override
3b2b638f 903 public void run() {
925298fd
NR
904 try {
905 run.run();
906 refreshBooks();
907 } finally {
908 reload.done();
909 if (!pg.isDone()) {
910 // will trigger pgBar ActionListener:
911 pg.done();
912 }
3b2b638f
NR
913 }
914 }
754a5bc2 915 }, "outOfUi thread").start();
3b2b638f
NR
916 }
917
4d205683 918 /**
68e2c6d2 919 * Import a {@link Story} into the main {@link LocalLibrary}.
22848428
NR
920 * <p>
921 * Should be called inside the UI thread.
4d205683
NR
922 *
923 * @param askUrl
924 * TRUE for an {@link URL}, false for a {@link File}
925 */
10d558d2
NR
926 private void imprt(boolean askUrl) {
927 JFileChooser fc = new JFileChooser();
928
5130ce84 929 Object url;
10d558d2 930 if (askUrl) {
5130ce84
NR
931 String clipboard = "";
932 try {
933 clipboard = ("" + Toolkit.getDefaultToolkit()
934 .getSystemClipboard().getData(DataFlavor.stringFlavor))
935 .trim();
936 } catch (Exception e) {
937 // No data will be handled
938 }
939
940 if (clipboard == null || !clipboard.startsWith("http")) {
941 clipboard = "";
942 }
943
5dd985cf 944 url = JOptionPane.showInputDialog(GuiReaderFrame.this,
10d558d2 945 "url of the story to import?", "Importing from URL",
5130ce84 946 JOptionPane.QUESTION_MESSAGE, null, null, clipboard);
10d558d2
NR
947 } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) {
948 url = fc.getSelectedFile().getAbsolutePath();
949 } else {
950 url = null;
951 }
952
5130ce84 953 if (url != null && !url.toString().isEmpty()) {
754a5bc2 954 imprt(url.toString(), null, null);
22848428
NR
955 }
956 }
10d558d2 957
22848428 958 /**
68e2c6d2 959 * Actually import the {@link Story} into the main {@link LocalLibrary}.
22848428
NR
960 * <p>
961 * Should be called inside the UI thread.
962 *
963 * @param url
964 * the {@link Story} to import by {@link URL}
965 * @param onSuccess
966 * Action to execute on success
967 */
c8faa52a 968 private void imprt(final String url, final StoryRunnable onSuccess,
754a5bc2
NR
969 String onSuccessPgName) {
970 final Progress pg = new Progress();
971 final Progress pgImprt = new Progress();
972 final Progress pgOnSuccess = new Progress(onSuccessPgName);
973 pg.addProgress(pgImprt, 95);
974 pg.addProgress(pgOnSuccess, 5);
975
22848428 976 outOfUi(pg, new Runnable() {
211f7ddb 977 @Override
22848428
NR
978 public void run() {
979 Exception ex = null;
c8faa52a 980 Story story = null;
22848428 981 try {
c8faa52a
NR
982 story = reader.getLibrary().imprt(BasicReader.getUrl(url),
983 pgImprt);
22848428
NR
984 } catch (IOException e) {
985 ex = e;
986 }
10d558d2 987
22848428
NR
988 final Exception e = ex;
989
990 final boolean ok = (e == null);
754a5bc2
NR
991
992 pgOnSuccess.setProgress(0);
993 if (!ok) {
fd1d31c2 994 if (e instanceof UnknownHostException) {
925298fd 995 error("URL not supported: " + url, "Cannot import URL",
fd1d31c2
NR
996 null);
997 } else {
998 error("Failed to import " + url + ": \n"
999 + e.getMessage(), "Cannot import URL", e);
1000 }
754a5bc2
NR
1001 } else {
1002 if (onSuccess != null) {
c8faa52a 1003 onSuccess.run(story);
22848428 1004 }
754a5bc2 1005 }
5ce869b8 1006 pgOnSuccess.done();
22848428
NR
1007 }
1008 });
10d558d2
NR
1009 }
1010
4d205683
NR
1011 /**
1012 * Enables or disables this component, depending on the value of the
1013 * parameter <code>b</code>. An enabled component can respond to user input
1014 * and generate events. Components are enabled initially by default.
1015 * <p>
1016 * Disabling this component will also affect its children.
1017 *
1018 * @param b
1019 * If <code>true</code>, this component is enabled; otherwise
1020 * this component is disabled
1021 */
1022 @Override
1023 public void setEnabled(boolean b) {
4f661b2b
NR
1024 if (bar != null) {
1025 bar.setEnabled(b);
1026 }
1027
5dd985cf 1028 for (GuiReaderGroup group : booksByType.keySet()) {
4310bae9
NR
1029 group.setEnabled(b);
1030 }
5dd985cf 1031 for (GuiReaderGroup group : booksByAuthor.keySet()) {
4310bae9
NR
1032 group.setEnabled(b);
1033 }
4d205683 1034 super.setEnabled(b);
3b2b638f
NR
1035 repaint();
1036 }
e6249b0f
NR
1037
1038 /**
1039 * Display an error message and log the linked {@link Exception}.
1040 *
1041 * @param message
1042 * the message
1043 * @param title
1044 * the title of the error message
1045 * @param e
1046 * the exception to log if any
1047 */
1048 private void error(final String message, final String title, Exception e) {
1049 Instance.getTraceHandler().error(title + ": " + message);
1050 if (e != null) {
1051 Instance.getTraceHandler().error(e);
1052 }
1053
1054 SwingUtilities.invokeLater(new Runnable() {
1055 @Override
1056 public void run() {
1057 JOptionPane.showMessageDialog(GuiReaderFrame.this, message,
1058 title, JOptionPane.ERROR_MESSAGE);
1059 }
1060 });
1061 }
a6395bef 1062}