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