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