1
22 package org.crosswire.bibledesktop.display.basic;
23
24 import java.awt.Component;
25 import java.awt.Dimension;
26 import java.awt.event.MouseListener;
27 import java.beans.PropertyChangeEvent;
28 import java.net.MalformedURLException;
29 import java.net.URI;
30 import java.text.MessageFormat;
31 import java.util.Arrays;
32 import java.util.Locale;
33
34 import javax.swing.JTextPane;
35 import javax.swing.event.EventListenerList;
36 import javax.swing.event.HyperlinkEvent;
37 import javax.swing.event.HyperlinkListener;
38 import javax.swing.text.Style;
39 import javax.swing.text.StyleConstants;
40 import javax.swing.text.StyledDocument;
41 import javax.xml.transform.TransformerException;
42
43 import org.crosswire.bibledesktop.BDMsg;
44 import org.crosswire.bibledesktop.book.install.BookFont;
45 import org.crosswire.bibledesktop.desktop.XSLTProperty;
46 import org.crosswire.bibledesktop.display.BookDataDisplay;
47 import org.crosswire.bibledesktop.display.URIEvent;
48 import org.crosswire.bibledesktop.display.URIEventListener;
49 import org.crosswire.bibledesktop.passage.KeyChangeListener;
50 import org.crosswire.common.swing.AntiAliasedTextPane;
51 import org.crosswire.common.swing.GuiConvert;
52 import org.crosswire.common.swing.GuiUtil;
53 import org.crosswire.common.util.Logger;
54 import org.crosswire.common.util.Reporter;
55 import org.crosswire.common.xml.Converter;
56 import org.crosswire.common.xml.SAXEventProvider;
57 import org.crosswire.common.xml.TransformingSAXEventProvider;
58 import org.crosswire.common.xml.XMLUtil;
59 import org.crosswire.jsword.book.Book;
60 import org.crosswire.jsword.book.BookCategory;
61 import org.crosswire.jsword.book.BookData;
62 import org.crosswire.jsword.book.BookException;
63 import org.crosswire.jsword.book.BookMetaData;
64 import org.crosswire.jsword.passage.Key;
65 import org.crosswire.jsword.util.ConverterFactory;
66 import org.xml.sax.SAXException;
67
68
76 public class TextPaneBookDataDisplay implements BookDataDisplay, HyperlinkListener {
77
78
79
80
83 public TextPaneBookDataDisplay() {
84 converter = ConverterFactory.getConverter();
85 txtView = new AntiAliasedTextPane();
86 txtView.setEditable(false);
87 txtView.setEditorKit(new LazyHTMLEditorKit());
88 txtView.addHyperlinkListener(this);
89 style = txtView.addStyle(HYPERLINK_STYLE, null);
90 styledDoc = txtView.getStyledDocument();
91 lastStart = -1;
92 lastLength = -1;
93
94 this.addURIEventListener(
95 new ActiveURITip(txtView,
96 new Dimension(400, 300)));
97 }
98
99
102 public void clearBookData() {
103 setBookData(null, null);
104 }
105
106
109 public void setBookData(Book[] books, Key key) {
110 if (books == null || books.length == 0 || books[0] == null || key == null) {
111 bdata = null;
112 } else if (bdata == null || !Arrays.equals(books, bdata.getBooks()) || !key.equals(bdata.getKey())) {
113 bdata = new BookData(books, key, compareBooks);
114 }
115
116 refresh();
117 }
118
119
122 public void setCompareBooks(boolean compare) {
123 compareBooks = compare;
124 if (bdata != null) {
125 bdata = new BookData(bdata.getBooks(), bdata.getKey(), compareBooks);
126 refresh();
127 }
128 }
129
130
133 public void refresh() {
134 if (bdata == null) {
135 txtView.setText("");
136 return;
137 }
138
139 BookMetaData bmd = getFirstBook().getBookMetaData();
141 if (bmd == null) {
142 txtView.setText("");
143 return;
144 }
145
146 boolean direction = bmd.isLeftToRight();
150 GuiUtil.applyOrientation(txtView, direction);
151 txtView.setLocale(new Locale(bmd.getLanguage().getCode()));
153
154 String fontSpec = GuiConvert.font2String(BookFont.instance().getFont(getFirstBook()));
155 try {
156 SAXEventProvider osissep = bdata.getSAXEventProvider();
157 TransformingSAXEventProvider htmlsep = (TransformingSAXEventProvider) converter.convert(osissep);
158
159 XSLTProperty.DIRECTION.setState(direction ? "ltr" : "rtl");
160
161 URI loc = bmd.getLocation();
162 XSLTProperty.BASE_URL.setState(loc == null ? "" : loc.getPath());
163
164 if (bmd.getBookCategory() == BookCategory.BIBLE) {
165 XSLTProperty.setProperties(htmlsep);
166 } else {
167 XSLTProperty.CSS.setProperty(htmlsep);
168 XSLTProperty.BASE_URL.setProperty(htmlsep);
169 XSLTProperty.DIRECTION.setProperty(htmlsep);
170 }
171 htmlsep.setParameter(XSLTProperty.FONT.getName(), fontSpec);
173
174 String text = XMLUtil.writeToString(htmlsep);
175 txtView.setText(text);
176 txtView.select(0, 0);
177 } catch (SAXException e) {
178 Reporter.informUser(this, e);
179 } catch (BookException e) {
180 Reporter.informUser(this, e);
181 } catch (TransformerException e) {
182 Reporter.informUser(this, e);
183 }
184 }
185
186
189 public void hyperlinkUpdate(HyperlinkEvent ev) {
190 try {
192 HyperlinkEvent.EventType type = ev.getEventType();
193 JTextPane pane = (JTextPane) ev.getSource();
194
195 String uri = ev.getDescription();
196 String[] parts = getParts(uri);
197 if (type == HyperlinkEvent.EventType.ACTIVATED) {
198 if (parts[1].length() > 0) {
200 if (parts[1].charAt(0) == '#') {
201 log.debug(MessageFormat.format(SCROLL_TO_URI, uri));
202 uri = uri.substring(1);
210 pane.scrollToReference(uri);
211 } else {
212 fireActivateURI(new URIEvent(this, parts[0], parts[1]));
214 }
215 }
216 } else {
217
221 boolean isEnter = type == HyperlinkEvent.EventType.ENTERED;
222
223 int start = lastStart;
224 int length = lastLength;
225 if (isEnter) {
226 javax.swing.text.Element textElement = ev.getSourceElement();
227 start = textElement.getStartOffset();
228 length = textElement.getEndOffset() - start;
229 lastStart = start;
230 lastLength = length;
231 }
232
233 StyleConstants.setUnderline(style, isEnter);
234 styledDoc.setCharacterAttributes(start, length, style, false);
235
236 if (isEnter) {
237 fireEnterURI(new URIEvent(this, parts[0], parts[1]));
238 } else {
239 fireLeaveURI(new URIEvent(this, parts[0], parts[1]));
240 }
241 }
242 } catch (MalformedURLException ex) {
243 Reporter.informUser(this, ex);
244 }
245 }
246
247
250 public void propertyChange(PropertyChangeEvent evt) {
251 if (evt.getPropertyName().equals(BookDataDisplay.COMPARE_BOOKS)) {
252 setCompareBooks(Boolean.valueOf(evt.getNewValue().toString()).booleanValue());
253 }
254 }
255
256 private String[] getParts(String reference) throws MalformedURLException {
257 String protocol = RELATIVE_URI_PROTOCOL;
258 String data = reference;
259 int match = data.indexOf(':');
260 if (match == -1) {
261 if (data.charAt(0) != '#') {
266 throw new MalformedURLException(BDMsg.gettext("Missing : in {0}", data));
269 }
270 } else {
271 protocol = data.substring(0, match);
272 data = data.substring(match + 1);
273 }
274
275 if (data.startsWith(DOUBLE_SLASH)) {
276 data = data.substring(2);
277 }
278
279 return new String[] {
280 protocol, data
281 };
282 }
283
284
287 public Component getComponent() {
288 return txtView;
289 }
290
291
294 public void copy() {
295 txtView.copy();
296 }
297
298
301 public synchronized void addKeyChangeListener(KeyChangeListener listener) {
302 listenerList.add(KeyChangeListener.class, listener);
303 }
304
305
308 public synchronized void removeKeyChangeListener(KeyChangeListener listener) {
309 listenerList.remove(KeyChangeListener.class, listener);
310 }
311
312
315 public synchronized void addURIEventListener(URIEventListener listener) {
316 listenerList.add(URIEventListener.class, listener);
317 }
318
319
322 public synchronized void removeURIEventListener(URIEventListener listener) {
323 listenerList.remove(URIEventListener.class, listener);
324 }
325
326
333 public void fireActivateURI(URIEvent e) {
334 Object[] listeners = listenerList.getListenerList();
336 for (int i = listeners.length - 2; i >= 0; i -= 2) {
339 if (listeners[i] == URIEventListener.class) {
340 ((URIEventListener) listeners[i + 1]).activateURI(e);
341 }
342 }
343 }
344
345
352 public void fireEnterURI(URIEvent e) {
353 Object[] listeners = listenerList.getListenerList();
355 for (int i = listeners.length - 2; i >= 0; i -= 2) {
358 if (listeners[i] == URIEventListener.class) {
359 ((URIEventListener) listeners[i + 1]).enterURI(e);
360 }
361 }
362 }
363
364
371 public void fireLeaveURI(URIEvent e) {
372 Object[] listeners = listenerList.getListenerList();
374 for (int i = listeners.length - 2; i >= 0; i -= 2) {
377 if (listeners[i] == URIEventListener.class) {
378 ((URIEventListener) listeners[i + 1]).leaveURI(e);
379 }
380 }
381 }
382
383
386 public void removeMouseListener(MouseListener li) {
387 txtView.removeMouseListener(li);
388 }
389
390
393 public void addMouseListener(MouseListener li) {
394 txtView.addMouseListener(li);
395 }
396
397
400 public Key getKey() {
401 return bdata == null ? null : bdata.getKey();
402 }
403
404
407 public Book[] getBooks() {
408 return bdata.getBooks();
409 }
410
411
414 public Book getFirstBook() {
415 return bdata.getFirstBook();
416 }
417
418 private static final String HYPERLINK_STYLE = "Hyperlink";
420 private static final String DOUBLE_SLASH = "//";
421 private static final String SCROLL_TO_URI = "scrolling to: {0}";
422 private static final String RELATIVE_URI_PROTOCOL = "";
423
424
427 protected static final Logger log = Logger.getLogger(TextPaneBookDataDisplay.class);
428
429
432 private BookData bdata;
433
434
437 private boolean compareBooks;
438
439
442 private Converter converter;
443
444
447 private JTextPane txtView;
448
449
452 private Style style;
453
454
457 private int lastStart;
458
459
462 private int lastLength;
463
464
467 private StyledDocument styledDoc;
468
469
472 private EventListenerList listenerList = new EventListenerList();
473
474 }
475