001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package javax.swing;
018:
019: import java.awt.Container;
020: import java.awt.Dimension;
021: import java.awt.Rectangle;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025: import java.io.StringReader;
026: import java.io.StringWriter;
027: import java.net.URL;
028: import java.net.URLConnection;
029: import java.nio.charset.Charset;
030: import java.util.Map;
031: import java.util.Hashtable;
032:
033: import javax.accessibility.AccessibleContext;
034: import javax.accessibility.AccessibleHyperlink;
035: import javax.accessibility.AccessibleHypertext;
036: import javax.accessibility.AccessibleState;
037: import javax.accessibility.AccessibleStateSet;
038: import javax.accessibility.AccessibleText;
039: import javax.swing.event.HyperlinkEvent;
040: import javax.swing.event.HyperlinkListener;
041: import javax.swing.text.AbstractDocument;
042: import javax.swing.text.AttributeSet;
043: import javax.swing.text.BadLocationException;
044: import javax.swing.text.ChangedCharSetException;
045: import javax.swing.text.DefaultEditorKit;
046: import javax.swing.text.Document;
047: import javax.swing.text.EditorKit;
048: import javax.swing.text.Element;
049: import javax.swing.text.JTextComponent;
050: import javax.swing.text.StyledEditorKit;
051: import javax.swing.text.View;
052: import javax.swing.text.ViewFactory;
053: import javax.swing.text.WrappedPlainView;
054: import javax.swing.text.html.HTML;
055: import javax.swing.text.html.HTMLDocument;
056: import javax.swing.text.html.HTMLEditorKit;
057:
058: import org.apache.harmony.luni.util.NotImplementedException;
059: import org.apache.harmony.x.swing.StringConstants;
060:
061: import org.apache.harmony.x.swing.internal.nls.Messages;
062:
063: /**
064: * <p>
065: * <i>JEditorPane</i>
066: * </p>
067: * <h3>Implementation Notes:</h3>
068: * <ul>
069: * <li>The <code>serialVersionUID</code> fields are explicitly declared as a performance
070: * optimization, not as a guarantee of serialization compatibility.</li>
071: * </ul>
072: */
073: public class JEditorPane extends JTextComponent {
074: private static final long serialVersionUID = -767121239635831550L;
075:
076: protected class AccessibleJEditorPane extends
077: JTextComponent.AccessibleJTextComponent {
078: private static final long serialVersionUID = -6869835326921704467L;
079:
080: @Override
081: public String getAccessibleDescription() {
082: return getContentType();
083: }
084:
085: @Override
086: public AccessibleStateSet getAccessibleStateSet() {
087: AccessibleStateSet set = super .getAccessibleStateSet();
088: set.add(AccessibleState.MULTI_LINE);
089: return set;
090: }
091: }
092:
093: protected class AccessibleJEditorPaneHTML extends
094: AccessibleJEditorPane {
095: private static final long serialVersionUID = -5072331196784098614L;
096:
097: AccessibleText text;
098:
099: @Override
100: public AccessibleText getAccessibleText() {
101: if (text == null) {
102: text = new JEditorPaneAccessibleHypertextSupport();
103: }
104: return text;
105: }
106: }
107:
108: protected class JEditorPaneAccessibleHypertextSupport extends
109: AccessibleJEditorPane implements AccessibleHypertext {
110: private static final long serialVersionUID = -1462897229238717575L;
111:
112: //Not implemented
113: public class HTMLLink extends AccessibleHyperlink {
114: public HTMLLink(Element e) throws NotImplementedException {
115: super ();
116: throw new NotImplementedException();
117: }
118:
119: @Override
120: public boolean doAccessibleAction(int i)
121: throws NotImplementedException {
122: throw new NotImplementedException();
123: }
124:
125: @Override
126: public Object getAccessibleActionAnchor(int i)
127: throws NotImplementedException {
128: throw new NotImplementedException();
129: }
130:
131: @Override
132: public int getAccessibleActionCount()
133: throws NotImplementedException {
134: throw new NotImplementedException();
135: }
136:
137: @Override
138: public String getAccessibleActionDescription(int i)
139: throws NotImplementedException {
140: throw new NotImplementedException();
141: }
142:
143: @Override
144: public Object getAccessibleActionObject(int i)
145: throws NotImplementedException {
146: throw new NotImplementedException();
147: }
148:
149: @Override
150: public int getEndIndex() throws NotImplementedException {
151: throw new NotImplementedException();
152: }
153:
154: @Override
155: public int getStartIndex() throws NotImplementedException {
156: throw new NotImplementedException();
157: }
158:
159: @Override
160: public boolean isValid() throws NotImplementedException {
161: throw new NotImplementedException();
162: }
163: }
164:
165: public JEditorPaneAccessibleHypertextSupport()
166: throws NotImplementedException {
167: super ();
168: throw new NotImplementedException();
169: }
170:
171: public AccessibleHyperlink getLink(int linkIndex)
172: throws NotImplementedException {
173: throw new NotImplementedException();
174: }
175:
176: public int getLinkCount() throws NotImplementedException {
177: throw new NotImplementedException();
178: }
179:
180: public int getLinkIndex(int charIndex)
181: throws NotImplementedException {
182: throw new NotImplementedException();
183: }
184:
185: public String getLinkText(int linkIndex)
186: throws NotImplementedException {
187: throw new NotImplementedException();
188: }
189: }
190:
191: public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
192:
193: public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
194:
195: private static final String uiClassID = "EditorPaneUI";
196:
197: static final class PlainEditorKit extends DefaultEditorKit
198: implements ViewFactory {
199: private static final long serialVersionUID = 1L;
200:
201: public View create(Element elem) {
202: return new WrappedPlainView(elem);
203: }
204:
205: @Override
206: public ViewFactory getViewFactory() {
207: return this ;
208: }
209: }
210:
211: private static final String PLAIN_CONTENT_TYPE = "text/plain";
212:
213: private static final String HTML_CONTENT_TYPE = "text/html";
214:
215: private static final String RTF_CONTENT_TYPE = "text/rtf";
216:
217: private static final String RTF2_CONTENT_TYPE = "application/rtf";
218:
219: private static final String REFERENCE_TAIL_PATTERN = "#.*";
220:
221: private static final String RTF_HEADER = "{\\rtf";
222:
223: private static final String HTML_HEADER = "<html";
224:
225: private static Map<String, ContentTypeRegistration> contentTypes = new Hashtable<String, ContentTypeRegistration>();
226:
227: private static Map<String, EditorKit> localContentTypes = new Hashtable<String, EditorKit>();
228:
229: private EditorKit editorKit;
230:
231: private URL currentPage;
232:
233: private AccessibleContext accessible;
234:
235: private AccessibleContext accessibleHTML;
236:
237: static {
238: contentTypes
239: .put(PLAIN_CONTENT_TYPE, new ContentTypeRegistration(
240: "javax.swing.JEditorPane$PlainEditorKit", null));
241: contentTypes.put(HTML_CONTENT_TYPE,
242: new ContentTypeRegistration(
243: "javax.swing.text.html.HTMLEditorKit", null));
244: contentTypes.put(RTF_CONTENT_TYPE, new ContentTypeRegistration(
245: "javax.swing.text.rtf.RTFEditorKit", null));
246: contentTypes.put(RTF2_CONTENT_TYPE,
247: new ContentTypeRegistration(
248: "javax.swing.text.rtf.RTFEditorKit", null));
249: }
250:
251: public static EditorKit createEditorKitForContentType(
252: final String contentType) {
253: ContentTypeRegistration registration = contentTypes
254: .get(contentType);
255:
256: if (registration != null) {
257: try {
258: if (registration.editorKit == null) {
259: registration.editorKit = (EditorKit) Class.forName(
260: registration.className, true,
261: registration.classLoader).newInstance();
262: }
263: return (EditorKit) registration.editorKit.clone();
264: } catch (Throwable e) {
265: // Ignore.
266:
267: /*
268: * This is rather dangerous, but is being done so for
269: * compatibility with RI that seems to do the same.
270: * See HARMONY-3454 for details.
271: * This could be tweaked in the future and changed to
272: * only catch Exception and LinkageError, for example.
273: */
274: }
275: }
276: return null;
277: }
278:
279: public static String getEditorKitClassNameForContentType(
280: final String type) {
281: if (type == null) {
282: throw new NullPointerException(Messages.getString(
283: "swing.03", "Content type")); //$NON-NLS-1$ //$NON-NLS-2$
284: }
285: ContentTypeRegistration registration = contentTypes.get(type);
286:
287: return ((registration != null) ? registration.className : null);
288: }
289:
290: public static void registerEditorKitForContentType(
291: final String type, final String editorKitName) {
292: registerEditorKitForContentType(type, editorKitName, null);
293: }
294:
295: public static void registerEditorKitForContentType(
296: final String type, final String editorKitName,
297: final ClassLoader loader) {
298: if (type == null) {
299: throw new NullPointerException(Messages.getString(
300: "swing.03", "Content type")); //$NON-NLS-1$ //$NON-NLS-2$
301: }
302:
303: if (editorKitName == null) {
304: throw new NullPointerException(Messages.getString(
305: "swing.03", "Class name")); //$NON-NLS-1$ //$NON-NLS-2$
306: }
307: contentTypes.put(type, new ContentTypeRegistration(
308: editorKitName, ((loader != null) ? loader : Thread
309: .currentThread().getContextClassLoader())));
310: }
311:
312: public JEditorPane() {
313: setFocusCycleRoot(true);
314: }
315:
316: public JEditorPane(final String page) throws IOException {
317: this ();
318: setPage(page);
319: }
320:
321: public JEditorPane(final String type, final String text) {
322: this ();
323:
324: if (type == null) {
325: throw new NullPointerException(Messages.getString(
326: "swing.03", "Content type")); //$NON-NLS-1$ //$NON-NLS-2$
327: }
328: setContentType(type);
329: setText(text);
330: }
331:
332: public JEditorPane(final URL page) throws IOException {
333: this ();
334: setPage(page);
335: }
336:
337: public synchronized void addHyperlinkListener(
338: final HyperlinkListener listener) {
339: listenerList.add(HyperlinkListener.class, listener);
340: }
341:
342: protected EditorKit createDefaultEditorKit() {
343: return new PlainEditorKit();
344: }
345:
346: public void fireHyperlinkUpdate(final HyperlinkEvent event) {
347: HyperlinkListener[] listeners = getHyperlinkListeners();
348: for (int i = 0; i < listeners.length; i++) {
349: listeners[i].hyperlinkUpdate(event);
350: }
351: }
352:
353: @Override
354: public AccessibleContext getAccessibleContext() {
355: if (HTML_CONTENT_TYPE.equals(getContentType())) {
356: if (accessibleHTML == null) {
357: accessibleHTML = new AccessibleJEditorPaneHTML();
358: }
359: return accessibleHTML;
360: }
361: if (accessible == null) {
362: accessible = new AccessibleJEditorPane();
363: }
364: return accessible;
365: }
366:
367: public final String getContentType() {
368: return ((editorKit != null) ? editorKit.getContentType() : null);
369: }
370:
371: public EditorKit getEditorKit() {
372: if (editorKit == null) {
373: editorKit = createDefaultEditorKit();
374: }
375: return editorKit;
376: }
377:
378: public EditorKit getEditorKitForContentType(final String type) {
379: EditorKit kit = localContentTypes.get(type);
380:
381: if (kit == null) {
382: kit = createEditorKitForContentType(type);
383:
384: if (kit == null) {
385: kit = createDefaultEditorKit();
386: }
387: }
388: return kit;
389: }
390:
391: public synchronized HyperlinkListener[] getHyperlinkListeners() {
392: return getListeners(HyperlinkListener.class);
393: }
394:
395: public URL getPage() {
396: return currentPage;
397: }
398:
399: @Override
400: public Dimension getPreferredSize() {
401: getUI().getRootView(this ).setSize(0, 0);
402: Dimension d = super .getPreferredSize();
403: Container parent = getParent();
404: if (parent instanceof JViewport) {
405: Dimension min = getMinimumSize();
406: if (!getScrollableTracksViewportWidth()) {
407: int width = parent.getWidth();
408: if (width < min.width) {
409: d.width = min.width;
410: }
411: }
412: if (!getScrollableTracksViewportHeight()) {
413: int height = parent.getHeight();
414: if (height < min.height) {
415: d.height = min.height;
416: }
417: }
418: }
419: return d;
420: }
421:
422: @Override
423: public boolean getScrollableTracksViewportHeight() {
424: Container parent = getParent();
425: if (parent instanceof JViewport) {
426: int height = parent.getHeight();
427: Dimension min = getMinimumSize();
428: Dimension max = getMaximumSize();
429: return height >= min.height && height <= max.height;
430: }
431: return false;
432: }
433:
434: @Override
435: public boolean getScrollableTracksViewportWidth() {
436: Container parent = getParent();
437: if (parent instanceof JViewport) {
438: int width = parent.getWidth();
439: Dimension min = getMinimumSize();
440: Dimension max = getMaximumSize();
441: return width >= min.width && width <= max.width;
442: }
443: return false;
444: }
445:
446: private String getBaseURL(final String url) {
447: return (url == null) ? null : url.replaceAll(
448: REFERENCE_TAIL_PATTERN, "");
449: }
450:
451: protected InputStream getStream(final URL url) throws IOException {
452: if (url.getProtocol() == "http") {
453: getDocument().putProperty(
454: Document.StreamDescriptionProperty,
455: getBaseURL(url.toString()));
456: }
457: URLConnection connection = url.openConnection();
458: String contentType = connection.getContentType();
459: setContentType((contentType != null) ? contentType
460: : PLAIN_CONTENT_TYPE);
461: return connection.getInputStream();
462: }
463:
464: @Override
465: public String getText() {
466: StringWriter writer = new StringWriter();
467: try {
468: super .write(writer);
469: } catch (IOException e) {
470: }
471: return writer.toString();
472: }
473:
474: @Override
475: public String getUIClassID() {
476: return uiClassID;
477: }
478:
479: @Override
480: protected String paramString() {
481: return (super .paramString() + "," + "contentType="
482: + getContentType() + "," + "editorKit=" + editorKit
483: + "," + "document=" + getDocument() + ","
484: + "currentPage=" + currentPage);
485: }
486:
487: public void read(final InputStream stream, final Object type)
488: throws IOException {
489: if (type instanceof String) {
490: setContentType((String) type);
491: }
492: try {
493: Document doc = getDocument();
494: doc.putProperty(StringConstants.IGNORE_CHARSET_DIRECTIVE,
495: Boolean.TRUE);
496: editorKit.read(new InputStreamReader(stream), doc, 0);
497: } catch (BadLocationException e) {
498: }
499: }
500:
501: public synchronized void removeHyperlinkListener(
502: final HyperlinkListener listener) {
503: listenerList.remove(HyperlinkListener.class, listener);
504: }
505:
506: @Override
507: public synchronized void replaceSelection(final String s) {
508: if (!isEditable()) {
509: new DefaultEditorKit.BeepAction().actionPerformed(null);
510: return;
511: }
512: int start = getSelectionStart();
513: int end = getSelectionEnd();
514: Document doc = getDocument();
515: try {
516: if (start != end) {
517: doc.remove(start, end - start);
518: }
519: //May be these attributes placed in Document ????
520: AttributeSet as = (editorKit instanceof StyledEditorKit) ? ((StyledEditorKit) editorKit)
521: .getInputAttributes()
522: : null;
523: if (s != null) {
524: doc.insertString(start, s, as);
525: }
526: } catch (BadLocationException e) {
527: }
528: }
529:
530: public void scrollToReference(final String ref) {
531: Document doc = getDocument();
532: if (ref == null || !(doc instanceof HTMLDocument)) {
533: return;
534: }
535: HTMLDocument.Iterator it = ((HTMLDocument) doc)
536: .getIterator(HTML.Tag.A);
537: int offset = 0;
538: while (it.isValid()) {
539: AttributeSet set = it.getAttributes();
540: Object name = set.getAttribute(HTML.Attribute.NAME);
541: if (ref.equals(name)) {
542: offset = it.getStartOffset();
543: break;
544: }
545: it.next();
546: }
547: Rectangle rect = null;
548: try {
549: rect = modelToView(offset);
550: } catch (BadLocationException e) {
551: }
552: Rectangle visibleRect = getVisibleRect();
553: if (visibleRect != null) {
554: rect.height = visibleRect.height;
555: }
556: scrollRectToVisible(rect);
557: }
558:
559: private boolean changeEditorKit(final String contentType) {
560: return !(/*(RTF_CONTENT_TYPE.equals(contentType) && editorKit instanceof RTFEditorKit)
561: ||*/(HTML_CONTENT_TYPE.equals(contentType) && editorKit instanceof HTMLEditorKit) || (PLAIN_CONTENT_TYPE
562: .equals(contentType) && editorKit instanceof PlainEditorKit));
563: }
564:
565: public final void setContentType(String type) {
566: if (type == null) {
567: throw new NullPointerException(Messages.getString(
568: "swing.03", "Content type")); //$NON-NLS-1$ //$NON-NLS-2$
569: }
570: int comma = type.indexOf(';');
571:
572: if (comma >= 0) {
573: type = type.substring(0, comma);
574: }
575: type = type.trim().toLowerCase();
576:
577: if (!contentTypes.containsKey(type)) {
578: type = PLAIN_CONTENT_TYPE;
579: }
580:
581: if (changeEditorKit(type)) {
582: EditorKit kit = getEditorKitForContentType(type);
583: updateEditorKit((kit != null) ? kit : new PlainEditorKit());
584: updateDocument(editorKit);
585: }
586: }
587:
588: private void updateEditorKit(final EditorKit kit) {
589: if (editorKit != null) {
590: editorKit.deinstall(this );
591: }
592: EditorKit oldEditorKit = editorKit;
593: if (kit != null) {
594: kit.install(this );
595: }
596: editorKit = kit;
597: firePropertyChange("editorKit", oldEditorKit, kit);
598: }
599:
600: private void updateDocument(final EditorKit kit) {
601: if (kit != null) {
602: setDocument(kit.createDefaultDocument());
603: }
604: }
605:
606: public void setEditorKit(final EditorKit kit) {
607: updateEditorKit(kit);
608: updateDocument(kit);
609: }
610:
611: public void setEditorKitForContentType(final String type,
612: final EditorKit kit) {
613: if (type == null) {
614: throw new NullPointerException(Messages.getString(
615: "swing.03", "Content type")); //$NON-NLS-1$ //$NON-NLS-2$
616: }
617:
618: if (kit == null) {
619: throw new NullPointerException(Messages.getString(
620: "swing.03", "Editor kit")); //$NON-NLS-1$ //$NON-NLS-2$
621: }
622: localContentTypes.put(type, kit);
623: }
624:
625: public void setPage(final String page) throws IOException {
626: setPage(new URL(page));
627: }
628:
629: private void documentLoading(final InputStream str,
630: final Document doc, final URL url) throws IOException {
631: try {
632: editorKit.read(str, doc, 0);
633: } catch (ChangedCharSetException e) {
634: try {
635: doc.putProperty(
636: StringConstants.IGNORE_CHARSET_DIRECTIVE,
637: Boolean.TRUE);
638: doc.remove(0, doc.getLength());
639: final String htmlAttribute = e.getCharSetSpec();
640: final int charSetIndex = htmlAttribute
641: .lastIndexOf("charset=");
642: if (charSetIndex >= 0) {
643: String charSet = htmlAttribute
644: .substring(charSetIndex + 8);
645: InputStreamReader reader = new InputStreamReader(
646: url.openStream(), Charset.forName(charSet));
647: editorKit.read(reader, doc, 0);
648: }
649: } catch (BadLocationException e1) {
650: }
651: } catch (BadLocationException e) {
652: }
653: }
654:
655: private static class ContentTypeRegistration {
656: String className;
657: ClassLoader classLoader;
658: EditorKit editorKit;
659:
660: ContentTypeRegistration(String className,
661: ClassLoader classLoader) {
662: this .className = className;
663: this .classLoader = classLoader;
664: }
665: }
666:
667: private class AsynchLoad extends Thread {
668: InputStream inputStream;
669: boolean successfulLoading = true;
670: URL url;
671:
672: public AsynchLoad(final int priority, final InputStream stream,
673: final URL url) {
674: super ();
675: setPriority(priority);
676: inputStream = stream;
677: this .url = url;
678: }
679:
680: @Override
681: public void run() {
682: try {
683: documentLoading(inputStream, getDocument(), url);
684: } catch (IOException e) {
685: successfulLoading = false;
686: }
687: }
688: }
689:
690: public void setPage(final URL page) throws IOException {
691: if (page == null) {
692: throw new IOException(Messages
693: .getString("swing.03", "Page")); //$NON-NLS-1$ //$NON-NLS-2$
694: }
695:
696: String url = page.toString();
697: String baseUrl = getBaseURL(url);
698: Document oldDoc = getDocument();
699: if (baseUrl != null
700: && oldDoc != null
701: && baseUrl
702: .equals(oldDoc
703: .getProperty(Document.StreamDescriptionProperty))) {
704:
705: scrollToReference(page.getRef());
706: return;
707: }
708: InputStream stream = getStream(page);
709: if (stream == null) {
710: return;
711: }
712: Document newDoc = editorKit.createDefaultDocument();
713: // Perhaps, it is reasonable only for HTMLDocument...
714: if (newDoc instanceof HTMLDocument) {
715: newDoc.putProperty(Document.StreamDescriptionProperty,
716: baseUrl);
717: newDoc.putProperty(
718: StringConstants.IGNORE_CHARSET_DIRECTIVE,
719: new Boolean(false));
720: try {
721: ((HTMLDocument) newDoc).setBase(new URL(baseUrl));
722: } catch (IOException e) {
723: }
724: }
725: // TODO Asynch loading doesn't work with completely.
726: // Also page property change event is written incorrectly now
727: // (at the asynchrounous loading), because loading may not be
728: // completed.
729: // int asynchronousLoadPriority = getAsynchronousLoadPriority(newDoc);
730: int asynchronousLoadPriority = -1;
731: if (asynchronousLoadPriority >= 0) {
732: setDocument(newDoc);
733: AsynchLoad newThread = new AsynchLoad(
734: asynchronousLoadPriority, stream, page);
735: newThread.start();
736: if (newThread.successfulLoading) {
737: changePage(page);
738: }
739: } else {
740: try {
741: documentLoading(stream, newDoc, page);
742: stream.close();
743: setDocument(newDoc);
744: changePage(page);
745: } catch (IOException e) {
746: }
747: }
748: }
749:
750: private void changePage(final URL newPage) {
751: URL oldPage = currentPage;
752: currentPage = newPage;
753: firePropertyChange("page", oldPage, currentPage);
754: }
755:
756: private int getAsynchronousLoadPriority(final Document doc) {
757: if (doc instanceof AbstractDocument) {
758: return ((AbstractDocument) doc)
759: .getAsynchronousLoadPriority();
760: }
761: return -1;
762: }
763:
764: @Override
765: public synchronized void setText(final String content) {
766: StringReader reader = new StringReader(content == null ? ""
767: : content);
768:
769: try {
770: read(reader, getContentType());
771: } catch (IOException e) {
772: }
773: }
774: }
|