gui: change name, author
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReader.java
CommitLineData
16a81ef7 1package be.nikiroo.fanfix.reader.ui;
a6395bef 2
edd46289 3import java.awt.Desktop;
a6395bef 4import java.awt.EventQueue;
350bc060
NR
5import java.awt.event.WindowAdapter;
6import java.awt.event.WindowEvent;
a6395bef
NR
7import java.io.File;
8import java.io.IOException;
b42117f1
NR
9import java.net.URISyntaxException;
10
11import javax.swing.JEditorPane;
12import javax.swing.JLabel;
13import javax.swing.JOptionPane;
14import javax.swing.event.HyperlinkEvent;
15import javax.swing.event.HyperlinkListener;
a6395bef
NR
16
17import be.nikiroo.fanfix.Instance;
b42117f1 18import be.nikiroo.fanfix.VersionCheck;
bc2ea776 19import be.nikiroo.fanfix.data.MetaData;
a6395bef 20import be.nikiroo.fanfix.data.Story;
ff05b828
NR
21import be.nikiroo.fanfix.library.BasicLibrary;
22import be.nikiroo.fanfix.library.CacheLibrary;
16a81ef7 23import be.nikiroo.fanfix.reader.BasicReader;
c3b229a1 24import be.nikiroo.fanfix.reader.Reader;
3b2b638f 25import be.nikiroo.utils.Progress;
b42117f1 26import be.nikiroo.utils.Version;
b0e88ebd 27import be.nikiroo.utils.ui.UIUtils;
a6395bef 28
c3b229a1
NR
29/**
30 * This class implements a graphical {@link Reader} using the Swing library from
31 * Java.
32 * <p>
33 * It can thus be themed to look native-like, or metal-like, or use any other
34 * theme you may want to try.
35 * <p>
36 * We actually try to enable native look-alike mode on start.
37 *
38 * @author niki
39 */
5dd985cf 40class GuiReader extends BasicReader {
b0e88ebd
NR
41 static private boolean nativeLookLoaded;
42
ff05b828
NR
43 private CacheLibrary cacheLib;
44
45 private File cacheDir;
a6395bef 46
c3b229a1
NR
47 /**
48 * Create a new graphical {@link Reader}.
49 *
50 * @throws IOException
51 * in case of I/O errors
52 */
5dd985cf 53 public GuiReader() throws IOException {
c3b229a1 54 // TODO: allow different themes?
b0e88ebd
NR
55 if (!nativeLookLoaded) {
56 UIUtils.setLookAndFeel();
57 nativeLookLoaded = true;
58 }
59
ff05b828
NR
60 cacheDir = Instance.getReaderDir();
61 cacheDir.mkdirs();
62 if (!cacheDir.exists()) {
a6395bef 63 throw new IOException(
ff05b828
NR
64 "Cannote create cache directory for local reader: "
65 + cacheDir);
66 }
67 }
68
69 @Override
70 public synchronized BasicLibrary getLibrary() {
71 if (cacheLib == null) {
72 BasicLibrary lib = super.getLibrary();
73 if (lib instanceof CacheLibrary) {
74 cacheLib = (CacheLibrary) lib;
75 } else {
76 cacheLib = new CacheLibrary(cacheDir, lib);
77 }
a6395bef
NR
78 }
79
ff05b828 80 return cacheLib;
a6395bef
NR
81 }
82
211f7ddb 83 @Override
350bc060 84 public void read(boolean sync) throws IOException {
bc2ea776
NR
85 MetaData meta = getMeta();
86
87 if (meta == null) {
edd46289
NR
88 throw new IOException("No story to read");
89 }
90
350bc060 91 read(meta.getLuid(), sync, null);
a6395bef
NR
92 }
93
9843a5e5
NR
94 /**
95 * Check if the {@link Story} denoted by this Library UID is present in the
5dd985cf 96 * {@link GuiReader} cache.
9843a5e5
NR
97 *
98 * @param luid
99 * the Library UID
100 *
101 * @return TRUE if it is
102 */
10d558d2 103 public boolean isCached(String luid) {
ff05b828 104 return cacheLib.isCached(luid);
10d558d2
NR
105 }
106
211f7ddb 107 @Override
b0e88ebd 108 public void browse(String type) {
b42117f1
NR
109 // TODO: improve presentation of update message
110 final VersionCheck updates = VersionCheck.check();
111 StringBuilder builder = new StringBuilder();
350bc060 112 final Boolean[] done = new Boolean[] { false };
b42117f1
NR
113
114 final JEditorPane updateMessage = new JEditorPane("text/html", "");
115 if (updates.isNewVersionAvailable()) {
116 builder.append("A new version of the program is available at <span style='color: blue;'>https://github.com/nikiroo/fanfix/releases</span>");
117 builder.append("<br>");
118 builder.append("<br>");
119 for (Version v : updates.getNewer()) {
120 builder.append("\t<b>Version " + v + "</b>");
121 builder.append("<br>");
122 builder.append("<ul>");
123 for (String item : updates.getChanges().get(v)) {
124 builder.append("<li>" + item + "</li>");
125 }
126 builder.append("</ul>");
127 }
128
129 // html content
130 updateMessage.setText("<html><body>" //
131 + builder//
132 + "</body></html>");
133
134 // handle link events
135 updateMessage.addHyperlinkListener(new HyperlinkListener() {
211f7ddb 136 @Override
b42117f1
NR
137 public void hyperlinkUpdate(HyperlinkEvent e) {
138 if (e.getEventType().equals(
139 HyperlinkEvent.EventType.ACTIVATED))
140 try {
141 Desktop.getDesktop().browse(e.getURL().toURI());
142 } catch (IOException ee) {
62c63b07 143 Instance.getTraceHandler().error(ee);
b42117f1 144 } catch (URISyntaxException ee) {
62c63b07 145 Instance.getTraceHandler().error(ee);
b42117f1
NR
146 }
147 }
148 });
149 updateMessage.setEditable(false);
150 updateMessage.setBackground(new JLabel().getBackground());
151 }
152
333f0e7b 153 final String typeFinal = type;
a6395bef 154 EventQueue.invokeLater(new Runnable() {
211f7ddb 155 @Override
a6395bef 156 public void run() {
b42117f1
NR
157 if (updates.isNewVersionAvailable()) {
158 int rep = JOptionPane.showConfirmDialog(null,
159 updateMessage, "Updates available",
160 JOptionPane.OK_CANCEL_OPTION);
161 if (rep == JOptionPane.OK_OPTION) {
162 updates.ok();
163 } else {
164 updates.ignore();
165 }
166 }
167
350bc060
NR
168 try {
169 GuiReaderFrame gui = new GuiReaderFrame(GuiReader.this,
170 typeFinal);
171 gui.setVisible(true);
172 gui.addWindowListener(new WindowAdapter() {
173 @Override
174 public void windowClosed(WindowEvent e) {
175 super.windowClosed(e);
176 done[0] = true;
177 }
178 });
179 } catch (Exception e) {
180 Instance.getTraceHandler().error(e);
181 done[0] = true;
182 }
a6395bef
NR
183 }
184 });
350bc060
NR
185
186 // This action must be synchronous, so wait until the frame is closed
187 while (!done[0]) {
188 try {
189 Thread.sleep(100);
190 } catch (InterruptedException e) {
191 }
192 }
a6395bef 193 }
10d558d2 194
16a81ef7 195 @Override
350bc060
NR
196 public void start(File target, String program, boolean sync)
197 throws IOException {
198
199 boolean handled = false;
200 if (program == null && !sync) {
16a81ef7
NR
201 try {
202 Desktop.getDesktop().browse(target.toURI());
350bc060 203 handled = true;
16a81ef7 204 } catch (UnsupportedOperationException e) {
16a81ef7 205 }
350bc060
NR
206 }
207
208 if (!handled) {
209 super.start(target, program, sync);
16a81ef7
NR
210 }
211 }
212
c3b229a1
NR
213 /**
214 * Delete the {@link Story} from the cache if it is present, but <b>NOT</b>
215 * from the main library.
216 * <p>
217 * The next time we try to retrieve the {@link Story}, it may be required to
218 * cache it again.
219 *
220 * @param luid
221 * the luid of the {@link Story}
222 */
754a5bc2 223 void clearLocalReaderCache(String luid) {
68e2c6d2 224 try {
ff05b828 225 cacheLib.clearFromCache(luid);
68e2c6d2 226 } catch (IOException e) {
62c63b07 227 Instance.getTraceHandler().error(e);
68e2c6d2 228 }
10d558d2
NR
229 }
230
c3b229a1
NR
231 /**
232 * Forward the delete operation to the main library.
233 * <p>
234 * The {@link Story} will be deleted from the main library as well as the
235 * cache if present.
236 *
237 * @param luid
238 * the {@link Story} to delete
239 */
10d558d2 240 void delete(String luid) {
68e2c6d2 241 try {
ff05b828 242 cacheLib.delete(luid);
68e2c6d2 243 } catch (IOException e) {
62c63b07 244 Instance.getTraceHandler().error(e);
68e2c6d2 245 }
10d558d2 246 }
edd46289 247
c3b229a1
NR
248 /**
249 * "Open" the given {@link Story}. It usually involves starting an external
250 * program adapted to the given file type.
350bc060
NR
251 * <p>
252 * Asynchronous method.
c3b229a1
NR
253 *
254 * @param luid
255 * the luid of the {@link Story} to open
350bc060
NR
256 * @param sync
257 * execute the process synchronously (wait until it is terminated
258 * before returning)
c3b229a1
NR
259 * @param pg
260 * the optional progress (we may need to prepare the
261 * {@link Story} for reading
262 *
263 * @throws IOException
264 * in case of I/O errors
265 */
350bc060 266 void read(String luid, boolean sync, Progress pg) throws IOException {
ff05b828 267 File file = cacheLib.getFile(luid, pg);
edd46289 268
bc2ea776 269 // TODO: show a special page for the chapter?
c3b229a1
NR
270 // We could also implement an internal viewer, both for image and
271 // non-image documents
350bc060 272 openExternal(getLibrary().getInfo(luid), file, sync);
edd46289 273 }
70c9b112 274
c3b229a1
NR
275 /**
276 * Change the source of the given {@link Story} (the source is the main
277 * information used to group the stories together).
278 * <p>
279 * In other words, <b>move</b> the {@link Story} into other source.
280 * <p>
281 * The source can be a new one, it needs not exist before hand.
282 *
283 * @param luid
284 * the luid of the {@link Story} to move
285 * @param newSource
286 * the new source
287 */
288 void changeSource(String luid, String newSource) {
68e2c6d2 289 try {
ff05b828 290 cacheLib.changeSource(luid, newSource, null);
68e2c6d2 291 } catch (IOException e) {
62c63b07 292 Instance.getTraceHandler().error(e);
68e2c6d2 293 }
70c9b112 294 }
c8d48938
NR
295
296 /**
297 * Change the title of the given {@link Story}.
298 *
299 * @param luid
300 * the luid of the {@link Story} to change
301 * @param newTitle
302 * the new title
303 */
304 void changeTitle(String luid, String newTitle) {
305 try {
306 cacheLib.changeTitle(luid, newTitle, null);
307 } catch (IOException e) {
308 Instance.getTraceHandler().error(e);
309 }
310 }
311
312 /**
313 * Change the author of the given {@link Story}.
314 * <p>
315 * The author can be a new one, it needs not exist before hand.
316 *
317 * @param luid
318 * the luid of the {@link Story} to change
319 * @param newAuthor
320 * the new author
321 */
322 void changeAuthor(String luid, String newAuthor) {
323 try {
324 cacheLib.changeAuthor(luid, newAuthor, null);
325 } catch (IOException e) {
326 Instance.getTraceHandler().error(e);
327 }
328 }
a6395bef 329}