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