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