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