1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.Desktop
;
4 import java
.awt
.EventQueue
;
6 import java
.io
.IOException
;
7 import java
.net
.URISyntaxException
;
9 import javax
.swing
.JEditorPane
;
10 import javax
.swing
.JLabel
;
11 import javax
.swing
.JOptionPane
;
12 import javax
.swing
.event
.HyperlinkEvent
;
13 import javax
.swing
.event
.HyperlinkListener
;
15 import be
.nikiroo
.fanfix
.Instance
;
16 import be
.nikiroo
.fanfix
.VersionCheck
;
17 import be
.nikiroo
.fanfix
.data
.MetaData
;
18 import be
.nikiroo
.fanfix
.data
.Story
;
19 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
20 import be
.nikiroo
.fanfix
.library
.CacheLibrary
;
21 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
22 import be
.nikiroo
.fanfix
.reader
.Reader
;
23 import be
.nikiroo
.utils
.Progress
;
24 import be
.nikiroo
.utils
.Version
;
25 import be
.nikiroo
.utils
.ui
.UIUtils
;
28 * This class implements a graphical {@link Reader} using the Swing library from
31 * It can thus be themed to look native-like, or metal-like, or use any other
32 * theme you may want to try.
34 * We actually try to enable native look-alike mode on start.
38 class GuiReader
extends BasicReader
{
39 static private boolean nativeLookLoaded
;
41 private CacheLibrary cacheLib
;
43 private File cacheDir
;
46 * Create a new graphical {@link Reader}.
49 * in case of I/O errors
51 public GuiReader() throws IOException
{
52 // TODO: allow different themes?
53 if (!nativeLookLoaded
) {
54 UIUtils
.setLookAndFeel();
55 nativeLookLoaded
= true;
58 cacheDir
= Instance
.getReaderDir();
60 if (!cacheDir
.exists()) {
61 throw new IOException(
62 "Cannote create cache directory for local reader: "
68 public synchronized BasicLibrary
getLibrary() {
69 if (cacheLib
== null) {
70 BasicLibrary lib
= super.getLibrary();
71 if (lib
instanceof CacheLibrary
) {
72 cacheLib
= (CacheLibrary
) lib
;
74 cacheLib
= new CacheLibrary(cacheDir
, lib
);
82 public void read() throws IOException
{
83 MetaData meta
= getMeta();
86 throw new IOException("No story to read");
89 read(meta
.getLuid(), null);
93 * Check if the {@link Story} denoted by this Library UID is present in the
94 * {@link GuiReader} cache.
99 * @return TRUE if it is
101 public boolean isCached(String luid
) {
102 return cacheLib
.isCached(luid
);
106 public void browse(String type
) {
107 // TODO: improve presentation of update message
108 final VersionCheck updates
= VersionCheck
.check();
109 StringBuilder builder
= new StringBuilder();
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>");
123 builder
.append("</ul>");
127 updateMessage
.setText("<html><body>" //
131 // handle link events
132 updateMessage
.addHyperlinkListener(new HyperlinkListener() {
134 public void hyperlinkUpdate(HyperlinkEvent e
) {
135 if (e
.getEventType().equals(
136 HyperlinkEvent
.EventType
.ACTIVATED
))
138 Desktop
.getDesktop().browse(e
.getURL().toURI());
139 } catch (IOException ee
) {
140 Instance
.getTraceHandler().error(ee
);
141 } catch (URISyntaxException ee
) {
142 Instance
.getTraceHandler().error(ee
);
146 updateMessage
.setEditable(false);
147 updateMessage
.setBackground(new JLabel().getBackground());
150 final String typeFinal
= type
;
151 EventQueue
.invokeLater(new Runnable() {
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
) {
165 new GuiReaderFrame(GuiReader
.this, typeFinal
).setVisible(true);
171 public void start(File target
, String program
) throws IOException
{
172 if (program
== null) {
174 Desktop
.getDesktop().browse(target
.toURI());
175 } catch (UnsupportedOperationException e
) {
176 super.start(target
, program
);
179 super.start(target
, program
);
184 * Delete the {@link Story} from the cache if it is present, but <b>NOT</b>
185 * from the main library.
187 * The next time we try to retrieve the {@link Story}, it may be required to
191 * the luid of the {@link Story}
193 void clearLocalReaderCache(String luid
) {
195 cacheLib
.clearFromCache(luid
);
196 } catch (IOException e
) {
197 Instance
.getTraceHandler().error(e
);
202 * Forward the delete operation to the main library.
204 * The {@link Story} will be deleted from the main library as well as the
208 * the {@link Story} to delete
210 void delete(String luid
) {
212 cacheLib
.delete(luid
);
213 } catch (IOException e
) {
214 Instance
.getTraceHandler().error(e
);
219 * "Open" the given {@link Story}. It usually involves starting an external
220 * program adapted to the given file type.
223 * the luid of the {@link Story} to open
225 * the optional progress (we may need to prepare the
226 * {@link Story} for reading
228 * @throws IOException
229 * in case of I/O errors
231 void read(String luid
, Progress pg
) throws IOException
{
232 File file
= cacheLib
.getFile(luid
, pg
);
234 // TODO: show a special page for the chapter?
235 // We could also implement an internal viewer, both for image and
236 // non-image documents
237 openExternal(getLibrary().getInfo(luid
), file
);
241 * Change the source of the given {@link Story} (the source is the main
242 * information used to group the stories together).
244 * In other words, <b>move</b> the {@link Story} into other source.
246 * The source can be a new one, it needs not exist before hand.
249 * the luid of the {@link Story} to move
253 void changeSource(String luid
, String newSource
) {
255 cacheLib
.changeSource(luid
, newSource
, null);
256 } catch (IOException e
) {
257 Instance
.getTraceHandler().error(e
);