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