Version 1.2.0: better UI, some fixes
[fanfix.git] / src / be / nikiroo / fanfix / reader / LocalReaderFrame.java
1 package be.nikiroo.fanfix.reader;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Desktop;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.KeyEvent;
9 import java.awt.event.WindowEvent;
10 import java.io.File;
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.List;
14
15 import javax.swing.JFileChooser;
16 import javax.swing.JFrame;
17 import javax.swing.JMenu;
18 import javax.swing.JMenuBar;
19 import javax.swing.JMenuItem;
20 import javax.swing.JOptionPane;
21 import javax.swing.JPanel;
22 import javax.swing.JScrollPane;
23 import javax.swing.SwingUtilities;
24
25 import be.nikiroo.fanfix.Instance;
26 import be.nikiroo.fanfix.bundles.UiConfig;
27 import be.nikiroo.fanfix.data.MetaData;
28 import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListener;
29 import be.nikiroo.utils.Progress;
30 import be.nikiroo.utils.ui.ProgressBar;
31 import be.nikiroo.utils.ui.WrapLayout;
32
33 class LocalReaderFrame extends JFrame {
34 private static final long serialVersionUID = 1L;
35 private LocalReader reader;
36 private List<MetaData> stories;
37 private List<LocalReaderBook> books;
38 private JPanel bookPane;
39 private String type;
40 private Color color;
41 private ProgressBar pgBar;
42 private JMenuBar bar;
43 private LocalReaderBook selectedBook;
44
45 public LocalReaderFrame(LocalReader reader, String type) {
46 super("Fanfix Library");
47
48 this.reader = reader;
49
50 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
51 setSize(800, 600);
52 setLayout(new BorderLayout());
53
54 books = new ArrayList<LocalReaderBook>();
55 bookPane = new JPanel(new WrapLayout(WrapLayout.LEADING, 5, 5));
56
57 color = Instance.getUiConfig().getColor(UiConfig.BACKGROUND_COLOR);
58
59 if (color != null) {
60 setBackground(color);
61 bookPane.setBackground(color);
62 }
63
64 JScrollPane scroll = new JScrollPane(bookPane);
65 scroll.getVerticalScrollBar().setUnitIncrement(16);
66 add(scroll, BorderLayout.CENTER);
67
68 pgBar = new ProgressBar();
69 add(pgBar, BorderLayout.SOUTH);
70
71 refreshBooks(type);
72 setJMenuBar(createMenu());
73
74 setVisible(true);
75 }
76
77 private void refreshBooks(String type) {
78 this.type = type;
79 stories = Instance.getLibrary().getList(type);
80 books.clear();
81 bookPane.removeAll();
82 for (MetaData meta : stories) {
83 LocalReaderBook book = new LocalReaderBook(meta,
84 reader.isCached(meta.getLuid()));
85 if (color != null) {
86 book.setBackground(color);
87 }
88
89 books.add(book);
90 final String luid = meta.getLuid();
91 book.addActionListener(new BookActionListener() {
92 public void select(LocalReaderBook book) {
93 selectedBook = book;
94 for (LocalReaderBook abook : books) {
95 abook.setSelected(abook == book);
96 }
97 }
98
99 public void action(final LocalReaderBook book) {
100 final Progress pg = new Progress();
101 outOfUi(pg, new Runnable() {
102 public void run() {
103 try {
104 File target = LocalReaderFrame.this.reader
105 .getTarget(luid, pg);
106 book.setCached(true);
107 // TODO: allow custom programs, with
108 // Desktop/xdg-open fallback
109 try {
110 Desktop.getDesktop().browse(target.toURI());
111 } catch (UnsupportedOperationException e) {
112 String browsers[] = new String[] {
113 "xdg-open", "epiphany",
114 "konqueror", "firefox", "chrome",
115 "google-chrome", "mozilla" };
116
117 Runtime runtime = Runtime.getRuntime();
118 for (String browser : browsers) {
119 try {
120 runtime.exec(new String[] {
121 browser,
122 target.getAbsolutePath() });
123 runtime = null;
124 break;
125 } catch (IOException ioe) {
126 // continue, try next browser
127 }
128 }
129
130 if (runtime != null) {
131 throw new IOException(
132 "Cannot find a working GUI browser...");
133 }
134 }
135 } catch (IOException e) {
136 Instance.syserr(e);
137 }
138 }
139 });
140 }
141 });
142
143 bookPane.add(book);
144 }
145
146 bookPane.validate();
147 bookPane.repaint();
148 }
149
150 private JMenuBar createMenu() {
151 bar = new JMenuBar();
152
153 JMenu file = new JMenu("File");
154 file.setMnemonic(KeyEvent.VK_F);
155
156 JMenuItem imprt = new JMenuItem("Import URL", KeyEvent.VK_U);
157 imprt.addActionListener(new ActionListener() {
158 public void actionPerformed(ActionEvent e) {
159 imprt(true);
160 }
161 });
162 JMenuItem imprtF = new JMenuItem("Import File", KeyEvent.VK_F);
163 imprtF.addActionListener(new ActionListener() {
164 public void actionPerformed(ActionEvent e) {
165 imprt(false);
166 }
167 });
168 JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
169 exit.addActionListener(new ActionListener() {
170 public void actionPerformed(ActionEvent e) {
171 LocalReaderFrame.this.dispatchEvent(new WindowEvent(
172 LocalReaderFrame.this, WindowEvent.WINDOW_CLOSING));
173 }
174 });
175
176 file.add(imprt);
177 file.add(imprtF);
178 file.addSeparator();
179 file.add(exit);
180
181 bar.add(file);
182
183 JMenu edit = new JMenu("Edit");
184 edit.setMnemonic(KeyEvent.VK_E);
185
186 final String notYet = "[TODO] Show not ready yet, but you can do it on command line, see: fanfix --help";
187
188 JMenuItem export = new JMenuItem("Export", KeyEvent.VK_E);
189 export.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
191 JOptionPane.showMessageDialog(LocalReaderFrame.this, notYet);
192 }
193 });
194
195 JMenuItem refresh = new JMenuItem("Refresh", KeyEvent.VK_R);
196 refresh.addActionListener(new ActionListener() {
197 public void actionPerformed(ActionEvent e) {
198 if (selectedBook != null) {
199 outOfUi(null, new Runnable() {
200 public void run() {
201 reader.refresh(selectedBook.getLuid());
202 selectedBook.setCached(false);
203 SwingUtilities.invokeLater(new Runnable() {
204 public void run() {
205 selectedBook.repaint();
206 }
207 });
208 }
209 });
210 }
211 }
212 });
213
214 JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D);
215 delete.addActionListener(new ActionListener() {
216 public void actionPerformed(ActionEvent e) {
217 if (selectedBook != null) {
218 outOfUi(null, new Runnable() {
219 public void run() {
220 reader.delete(selectedBook.getLuid());
221 selectedBook = null;
222 SwingUtilities.invokeLater(new Runnable() {
223 public void run() {
224 refreshBooks(type);
225 }
226 });
227 }
228 });
229 }
230 }
231 });
232
233 edit.add(export);
234 edit.add(refresh);
235 edit.add(delete);
236
237 bar.add(edit);
238
239 JMenu view = new JMenu("View");
240 view.setMnemonic(KeyEvent.VK_V);
241
242 List<String> tt = Instance.getLibrary().getTypes();
243 tt.add(0, null);
244 for (final String type : tt) {
245
246 JMenuItem item = new JMenuItem(type == null ? "All books" : type);
247 item.addActionListener(new ActionListener() {
248 public void actionPerformed(ActionEvent e) {
249 refreshBooks(type);
250 }
251 });
252 view.add(item);
253
254 if (type == null) {
255 view.addSeparator();
256 }
257 }
258
259 bar.add(view);
260
261 return bar;
262 }
263
264 private void outOfUi(final Progress pg, final Runnable run) {
265 pgBar.setProgress(pg);
266
267 SwingUtilities.invokeLater(new Runnable() {
268 public void run() {
269 setAllEnabled(false);
270 pgBar.addActioListener(new ActionListener() {
271 public void actionPerformed(ActionEvent e) {
272 pgBar.setProgress(null);
273 setAllEnabled(true);
274 }
275 });
276 }
277 });
278
279 new Thread(new Runnable() {
280 public void run() {
281 run.run();
282 if (pg == null) {
283 SwingUtilities.invokeLater(new Runnable() {
284 public void run() {
285 setAllEnabled(true);
286 }
287 });
288 } else if (!pg.isDone()) {
289 pg.setProgress(pg.getMax());
290 }
291 }
292 }).start();
293 }
294
295 private void imprt(boolean askUrl) {
296 JFileChooser fc = new JFileChooser();
297
298 final String url;
299 if (askUrl) {
300 url = JOptionPane.showInputDialog(LocalReaderFrame.this,
301 "url of the story to import?", "Importing from URL",
302 JOptionPane.QUESTION_MESSAGE);
303 } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) {
304 url = fc.getSelectedFile().getAbsolutePath();
305 } else {
306 url = null;
307 }
308
309 if (url != null && !url.isEmpty()) {
310 final Progress pg = new Progress("Importing " + url);
311 outOfUi(pg, new Runnable() {
312 public void run() {
313 Exception ex = null;
314 try {
315 Instance.getLibrary()
316 .imprt(BasicReader.getUrl(url), pg);
317 } catch (IOException e) {
318 ex = e;
319 }
320
321 final Exception e = ex;
322
323 final boolean ok = (e == null);
324 SwingUtilities.invokeLater(new Runnable() {
325 public void run() {
326 if (!ok) {
327 JOptionPane.showMessageDialog(
328 LocalReaderFrame.this,
329 "Cannot import: " + url,
330 e.getMessage(),
331 JOptionPane.ERROR_MESSAGE);
332
333 setAllEnabled(true);
334 } else {
335 refreshBooks(type);
336 }
337 }
338 });
339 }
340 });
341 }
342 }
343
344 public void setAllEnabled(boolean enabled) {
345 for (LocalReaderBook book : books) {
346 book.setEnabled(enabled);
347 book.validate();
348 book.repaint();
349 }
350
351 bar.setEnabled(enabled);
352 bookPane.setEnabled(enabled);
353 bookPane.validate();
354 bookPane.repaint();
355
356 setEnabled(enabled);
357 validate();
358 repaint();
359 }
360 }