/
HTMLEditorKit.java
2556 lines (2378 loc) · 97.3 KB
/
HTMLEditorKit.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text.html;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.Writer;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleAction;
import javax.accessibility.AccessibleContext;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JViewport;
import javax.swing.SizeRequirements;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.plaf.TextUI;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.Highlighter;
import javax.swing.text.IconView;
import javax.swing.text.JTextComponent;
import javax.swing.text.LabelView;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.TextAction;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.parser.ParserDelegator;
import sun.awt.AppContext;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
/**
* The Swing JEditorPane text component supports different kinds
* of content via a plug-in mechanism called an EditorKit. Because
* HTML is a very popular format of content, some support is provided
* by default. The default support is provided by this class, which
* supports HTML version 3.2 (with some extensions), and is migrating
* toward version 4.0.
* The <applet> tag is not supported, but some support is provided
* for the <object> tag.
* <p>
* There are several goals of the HTML EditorKit provided, that have
* an effect upon the way that HTML is modeled. These
* have influenced its design in a substantial way.
* <dl>
* <dt>
* Support editing
* <dd>
* It might seem fairly obvious that a plug-in for JEditorPane
* should provide editing support, but that fact has several
* design considerations. There are a substantial number of HTML
* documents that don't properly conform to an HTML specification.
* These must be normalized somewhat into a correct form if one
* is to edit them. Additionally, users don't like to be presented
* with an excessive amount of structure editing, so using traditional
* text editing gestures is preferred over using the HTML structure
* exactly as defined in the HTML document.
* <p>
* The modeling of HTML is provided by the class <code>HTMLDocument</code>.
* Its documentation describes the details of how the HTML is modeled.
* The editing support leverages heavily off of the text package.
*
* <dt>
* Extendable/Scalable
* <dd>
* To maximize the usefulness of this kit, a great deal of effort
* has gone into making it extendable. These are some of the
* features.
* <ol>
* <li>
* The parser is replaceable. The default parser is the Hot Java
* parser which is DTD based. A different DTD can be used, or an
* entirely different parser can be used. To change the parser,
* reimplement the getParser method. The default parser is
* dynamically loaded when first asked for, so the class files
* will never be loaded if an alternative parser is used. The
* default parser is in a separate package called parser below
* this package.
* <li>
* The parser drives the ParserCallback, which is provided by
* HTMLDocument. To change the callback, subclass HTMLDocument
* and reimplement the createDefaultDocument method to return
* document that produces a different reader. The reader controls
* how the document is structured. Although the Document provides
* HTML support by default, there is nothing preventing support of
* non-HTML tags that result in alternative element structures.
* <li>
* The default view of the models are provided as a hierarchy of
* View implementations, so one can easily customize how a particular
* element is displayed or add capabilities for new kinds of elements
* by providing new View implementations. The default set of views
* are provided by the <code>HTMLFactory</code> class. This can
* be easily changed by subclassing or replacing the HTMLFactory
* and reimplementing the getViewFactory method to return the alternative
* factory.
* <li>
* The View implementations work primarily off of CSS attributes,
* which are kept in the views. This makes it possible to have
* multiple views mapped over the same model that appear substantially
* different. This can be especially useful for printing. For
* most HTML attributes, the HTML attributes are converted to CSS
* attributes for display. This helps make the View implementations
* more general purpose
* </ol>
*
* <dt>
* Asynchronous Loading
* <dd>
* Larger documents involve a lot of parsing and take some time
* to load. By default, this kit produces documents that will be
* loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
* This is controlled by a property on the document. The method
* {@link #createDefaultDocument createDefaultDocument} can
* be overriden to change this. The batching of work is done
* by the <code>HTMLDocument.HTMLReader</code> class. The actual
* work is done by the <code>DefaultStyledDocument</code> and
* <code>AbstractDocument</code> classes in the text package.
*
* <dt>
* Customization from current LAF
* <dd>
* HTML provides a well known set of features without exactly
* specifying the display characteristics. Swing has a theme
* mechanism for its look-and-feel implementations. It is desirable
* for the look-and-feel to feed display characteristics into the
* HTML views. An user with poor vision for example would want
* high contrast and larger than typical fonts.
* <p>
* The support for this is provided by the <code>StyleSheet</code>
* class. The presentation of the HTML can be heavily influenced
* by the setting of the StyleSheet property on the EditorKit.
*
* <dt>
* Not lossy
* <dd>
* An EditorKit has the ability to be read and save documents.
* It is generally the most pleasing to users if there is no loss
* of data between the two operation. The policy of the HTMLEditorKit
* will be to store things not recognized or not necessarily visible
* so they can be subsequently written out. The model of the HTML document
* should therefore contain all information discovered while reading the
* document. This is constrained in some ways by the need to support
* editing (i.e. incorrect documents sometimes must be normalized).
* The guiding principle is that information shouldn't be lost, but
* some might be synthesized to produce a more correct model or it might
* be rearranged.
* </dl>
*
* @author Timothy Prinzing
*/
@SuppressWarnings("serial") // Same-version serialization only
public class HTMLEditorKit extends StyledEditorKit implements Accessible {
private JEditorPane theEditor;
/**
* Constructs an HTMLEditorKit, creates a StyleContext,
* and loads the style sheet.
*/
public HTMLEditorKit() {
}
/**
* Get the MIME type of the data that this
* kit represents support for. This kit supports
* the type <code>text/html</code>.
*
* @return the type
*/
public String getContentType() {
return "text/html";
}
/**
* Fetch a factory that is suitable for producing
* views of any models that are produced by this
* kit.
*
* @return the factory
*/
public ViewFactory getViewFactory() {
return defaultFactory;
}
/**
* Create an uninitialized text storage model
* that is appropriate for this type of editor.
*
* @return the model
*/
public Document createDefaultDocument() {
StyleSheet styles = getStyleSheet();
StyleSheet ss = new StyleSheet();
ss.addStyleSheet(styles);
HTMLDocument doc = new HTMLDocument(ss);
doc.setParser(getParser());
doc.setAsynchronousLoadPriority(4);
doc.setTokenThreshold(100);
return doc;
}
/**
* Try to get an HTML parser from the document. If no parser is set for
* the document, return the editor kit's default parser. It is an error
* if no parser could be obtained from the editor kit.
*/
private Parser ensureParser(HTMLDocument doc) throws IOException {
Parser p = doc.getParser();
if (p == null) {
p = getParser();
}
if (p == null) {
throw new IOException("Can't load parser");
}
return p;
}
/**
* Inserts content from the given stream. If <code>doc</code> is
* an instance of HTMLDocument, this will read
* HTML 3.2 text. Inserting HTML into a non-empty document must be inside
* the body Element, if you do not insert into the body an exception will
* be thrown. When inserting into a non-empty document all tags outside
* of the body (head, title) will be dropped.
*
* @param in the stream to read from
* @param doc the destination for the insertion
* @param pos the location in the document to place the
* content
* @exception IOException on any I/O error
* @exception BadLocationException if pos represents an invalid
* location within the document
* @exception RuntimeException (will eventually be a BadLocationException)
* if pos is invalid
*/
public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
if (doc instanceof HTMLDocument) {
HTMLDocument hdoc = (HTMLDocument) doc;
if (pos > doc.getLength()) {
throw new BadLocationException("Invalid location", pos);
}
Parser p = ensureParser(hdoc);
ParserCallback receiver = hdoc.getReader(pos);
Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
receiver.flush();
} else {
super.read(in, doc, pos);
}
}
/**
* Inserts HTML into an existing document.
*
* @param doc the document to insert into
* @param offset the offset to insert HTML at
* @param popDepth the number of ElementSpec.EndTagTypes to generate
* before inserting
* @param html the HTML string
* @param pushDepth the number of ElementSpec.StartTagTypes with a direction
* of ElementSpec.JoinNextDirection that should be generated
* before inserting, but after the end tags have been generated
* @param insertTag the first tag to start inserting into document
*
* @throws BadLocationException if {@code offset} is invalid
* @throws IOException on I/O error
* @exception RuntimeException (will eventually be a BadLocationException)
* if pos is invalid
*/
public void insertHTML(HTMLDocument doc, int offset, String html,
int popDepth, int pushDepth,
HTML.Tag insertTag) throws
BadLocationException, IOException {
if (offset > doc.getLength()) {
throw new BadLocationException("Invalid location", offset);
}
Parser p = ensureParser(doc);
ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
insertTag);
Boolean ignoreCharset = (Boolean)doc.getProperty
("IgnoreCharsetDirective");
p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
false : ignoreCharset.booleanValue());
receiver.flush();
}
/**
* Write content from a document to the given stream
* in a format appropriate for this kind of content handler.
*
* @param out the stream to write to
* @param doc the source for the write
* @param pos the location in the document to fetch the
* content
* @param len the amount to write out
* @exception IOException on any I/O error
* @exception BadLocationException if {@code pos} represents an invalid
* location within the document
*/
public void write(Writer out, Document doc, int pos, int len)
throws IOException, BadLocationException {
if (doc instanceof HTMLDocument) {
HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
w.write();
} else if (doc instanceof StyledDocument) {
MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
w.write();
} else {
super.write(out, doc, pos, len);
}
}
/**
* Called when the kit is being installed into the
* a JEditorPane.
*
* @param c the JEditorPane
*/
public void install(JEditorPane c) {
c.addMouseListener(linkHandler);
c.addMouseMotionListener(linkHandler);
c.addCaretListener(nextLinkAction);
super.install(c);
theEditor = c;
}
/**
* Called when the kit is being removed from the
* JEditorPane. This is used to unregister any
* listeners that were attached.
*
* @param c the JEditorPane
*/
public void deinstall(JEditorPane c) {
c.removeMouseListener(linkHandler);
c.removeMouseMotionListener(linkHandler);
c.removeCaretListener(nextLinkAction);
super.deinstall(c);
theEditor = null;
}
/**
* Default Cascading Style Sheet file that sets
* up the tag views.
*/
public static final String DEFAULT_CSS = "default.css";
/**
* Set the set of styles to be used to render the various
* HTML elements. These styles are specified in terms of
* CSS specifications. Each document produced by the kit
* will have a copy of the sheet which it can add the
* document specific styles to. By default, the StyleSheet
* specified is shared by all HTMLEditorKit instances.
* This should be reimplemented to provide a finer granularity
* if desired.
*
* @param s a StyleSheet
*/
public void setStyleSheet(StyleSheet s) {
if (s == null) {
AppContext.getAppContext().remove(DEFAULT_STYLES_KEY);
} else {
AppContext.getAppContext().put(DEFAULT_STYLES_KEY, s);
}
}
/**
* Get the set of styles currently being used to render the
* HTML elements. By default the resource specified by
* DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
* instances.
*
* @return the StyleSheet
*/
public StyleSheet getStyleSheet() {
AppContext appContext = AppContext.getAppContext();
StyleSheet defaultStyles = (StyleSheet) appContext.get(DEFAULT_STYLES_KEY);
if (defaultStyles == null) {
defaultStyles = new StyleSheet();
appContext.put(DEFAULT_STYLES_KEY, defaultStyles);
try {
InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
Reader r = new BufferedReader(
new InputStreamReader(is, ISO_8859_1));
defaultStyles.loadRules(r, null);
r.close();
} catch (Throwable e) {
// on error we simply have no styles... the html
// will look mighty wrong but still function.
}
}
return defaultStyles;
}
/**
* Fetch a resource relative to the HTMLEditorKit classfile.
* If this is called on 1.2 the loading will occur under the
* protection of a doPrivileged call to allow the HTMLEditorKit
* to function when used in an applet.
*
* @param name the name of the resource, relative to the
* HTMLEditorKit class
* @return a stream representing the resource
*/
@SuppressWarnings("removal")
static InputStream getResourceAsStream(final String name) {
return AccessController.doPrivileged(
new PrivilegedAction<InputStream>() {
public InputStream run() {
return HTMLEditorKit.class.getResourceAsStream(name);
}
});
}
/**
* Fetches the command list for the editor. This is
* the list of commands supported by the superclass
* augmented by the collection of commands defined
* locally for style operations.
*
* @return the command list
*/
public Action[] getActions() {
return TextAction.augmentList(super.getActions(), defaultActions);
}
/**
* Copies the key/values in <code>element</code>s AttributeSet into
* <code>set</code>. This does not copy component, icon, or element
* names attributes. Subclasses may wish to refine what is and what
* isn't copied here. But be sure to first remove all the attributes that
* are in <code>set</code>.<p>
* This is called anytime the caret moves over a different location.
*
*/
protected void createInputAttributes(Element element,
MutableAttributeSet set) {
set.removeAttributes(set);
set.addAttributes(element.getAttributes());
set.removeAttribute(StyleConstants.ComposedTextAttribute);
Object o = set.getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag tag = (HTML.Tag)o;
// PENDING: we need a better way to express what shouldn't be
// copied when editing...
if(tag == HTML.Tag.IMG) {
// Remove the related image attributes, src, width, height
set.removeAttribute(HTML.Attribute.SRC);
set.removeAttribute(HTML.Attribute.HEIGHT);
set.removeAttribute(HTML.Attribute.WIDTH);
set.addAttribute(StyleConstants.NameAttribute,
HTML.Tag.CONTENT);
}
else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
// Don't copy HRs or BRs either.
set.addAttribute(StyleConstants.NameAttribute,
HTML.Tag.CONTENT);
}
else if (tag == HTML.Tag.COMMENT) {
// Don't copy COMMENTs either
set.addAttribute(StyleConstants.NameAttribute,
HTML.Tag.CONTENT);
set.removeAttribute(HTML.Attribute.COMMENT);
}
else if (tag == HTML.Tag.INPUT) {
// or INPUT either
set.addAttribute(StyleConstants.NameAttribute,
HTML.Tag.CONTENT);
set.removeAttribute(HTML.Tag.INPUT);
}
else if (tag instanceof HTML.UnknownTag) {
// Don't copy unknowns either:(
set.addAttribute(StyleConstants.NameAttribute,
HTML.Tag.CONTENT);
set.removeAttribute(HTML.Attribute.ENDTAG);
}
}
}
/**
* Gets the input attributes used for the styled
* editing actions.
*
* @return the attribute set
*/
public MutableAttributeSet getInputAttributes() {
if (input == null) {
input = getStyleSheet().addStyle(null, null);
}
return input;
}
/**
* Sets the default cursor.
*
* @param cursor a cursor
*
* @since 1.3
*/
public void setDefaultCursor(Cursor cursor) {
defaultCursor = cursor;
}
/**
* Returns the default cursor.
*
* @return the cursor
*
* @since 1.3
*/
public Cursor getDefaultCursor() {
return defaultCursor;
}
/**
* Sets the cursor to use over links.
*
* @param cursor a cursor
*
* @since 1.3
*/
public void setLinkCursor(Cursor cursor) {
linkCursor = cursor;
}
/**
* Returns the cursor to use over hyper links.
*
* @return the cursor
*
* @since 1.3
*/
public Cursor getLinkCursor() {
return linkCursor;
}
/**
* Indicates whether an html form submission is processed automatically
* or only <code>FormSubmitEvent</code> is fired.
*
* @return true if html form submission is processed automatically,
* false otherwise.
*
* @see #setAutoFormSubmission
* @since 1.5
*/
public boolean isAutoFormSubmission() {
return isAutoFormSubmission;
}
/**
* Specifies if an html form submission is processed
* automatically or only <code>FormSubmitEvent</code> is fired.
* By default it is set to true.
*
* @param isAuto if {@code true}, html form submission is processed automatically.
*
* @see #isAutoFormSubmission()
* @see FormSubmitEvent
* @since 1.5
*/
public void setAutoFormSubmission(boolean isAuto) {
isAutoFormSubmission = isAuto;
}
/**
* Creates a copy of the editor kit.
*
* @return the copy
*/
public Object clone() {
HTMLEditorKit o = (HTMLEditorKit)super.clone();
if (o != null) {
o.input = null;
o.linkHandler = new LinkController();
}
return o;
}
/**
* Fetch the parser to use for reading HTML streams.
* This can be reimplemented to provide a different
* parser. The default implementation is loaded dynamically
* to avoid the overhead of loading the default parser if
* it's not used. The default parser is the HotJava parser
* using an HTML 3.2 DTD.
*
* @return the parser
*/
protected Parser getParser() {
if (defaultParser == null) {
defaultParser = new ParserDelegator();
}
return defaultParser;
}
// ----- Accessibility support -----
private AccessibleContext accessibleContext;
/**
* returns the AccessibleContext associated with this editor kit
*
* @return the AccessibleContext associated with this editor kit
* @since 1.4
*/
public AccessibleContext getAccessibleContext() {
if (theEditor == null) {
return null;
}
if (accessibleContext == null) {
AccessibleHTML a = new AccessibleHTML(theEditor);
accessibleContext = a.getAccessibleContext();
}
return accessibleContext;
}
// --- variables ------------------------------------------
private static final Cursor MoveCursor = Cursor.getPredefinedCursor
(Cursor.HAND_CURSOR);
private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
(Cursor.DEFAULT_CURSOR);
/** Shared factory for creating HTML Views. */
private static final ViewFactory defaultFactory = new HTMLFactory();
MutableAttributeSet input;
private static final Object DEFAULT_STYLES_KEY = new Object();
private LinkController linkHandler = new LinkController();
private static Parser defaultParser = null;
private Cursor defaultCursor = DefaultCursor;
private Cursor linkCursor = MoveCursor;
private boolean isAutoFormSubmission = true;
/**
* Class to watch the associated component and fire
* hyperlink events on it when appropriate.
*/
@SuppressWarnings("serial") // Same-version serialization only
public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
private Element curElem = null;
/**
* If true, the current element (curElem) represents an image.
*/
private boolean curElemImage = false;
private String href = null;
/** This is used by viewToModel to avoid allocing a new array each
* time. */
private transient Position.Bias[] bias = new Position.Bias[1];
/**
* Current offset.
*/
private int curOffset;
/**
* Constructs a {@code LinkController}.
*/
public LinkController() {}
/**
* Called for a mouse click event.
* If the component is read-only (ie a browser) then
* the clicked event is used to drive an attempt to
* follow the reference specified by a link.
*
* @param e the mouse event
* @see MouseListener#mouseClicked
*/
@SuppressWarnings("deprecation")
public void mouseClicked(MouseEvent e) {
JEditorPane editor = (JEditorPane) e.getSource();
if (! editor.isEditable() && editor.isEnabled() &&
SwingUtilities.isLeftMouseButton(e)) {
Point pt = new Point(e.getX(), e.getY());
int pos = editor.viewToModel(pt);
if (pos >= 0) {
activateLink(pos, editor, e);
}
}
}
// ignore the drags
public void mouseDragged(MouseEvent e) {
}
// track the moving of the mouse.
@SuppressWarnings("deprecation")
public void mouseMoved(MouseEvent e) {
JEditorPane editor = (JEditorPane) e.getSource();
if (!editor.isEnabled()) {
return;
}
HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit();
boolean adjustCursor = true;
Cursor newCursor = kit.getDefaultCursor();
if (!editor.isEditable()) {
Point pt = new Point(e.getX(), e.getY());
int pos = editor.getUI().viewToModel(editor, pt, bias);
if (bias[0] == Position.Bias.Backward && pos > 0) {
pos--;
}
if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument)){
HTMLDocument hdoc = (HTMLDocument)editor.getDocument();
Element elem = hdoc.getCharacterElement(pos);
if (!doesElementContainLocation(editor, elem, pos,
e.getX(), e.getY())) {
elem = null;
}
if (curElem != elem || curElemImage) {
Element lastElem = curElem;
curElem = elem;
String href = null;
curElemImage = false;
if (elem != null) {
AttributeSet a = elem.getAttributes();
AttributeSet anchor = (AttributeSet)a.
getAttribute(HTML.Tag.A);
if (anchor == null) {
curElemImage = (a.getAttribute(StyleConstants.
NameAttribute) == HTML.Tag.IMG);
if (curElemImage) {
href = getMapHREF(editor, hdoc, elem, a,
pos, e.getX(), e.getY());
}
}
else {
href = (String)anchor.getAttribute
(HTML.Attribute.HREF);
}
}
if (href != this.href) {
// reference changed, fire event(s)
fireEvents(editor, hdoc, href, lastElem, e);
this.href = href;
if (href != null) {
newCursor = kit.getLinkCursor();
}
}
else {
adjustCursor = false;
}
}
else {
adjustCursor = false;
}
curOffset = pos;
}
}
if (adjustCursor && editor.getCursor() != newCursor) {
editor.setCursor(newCursor);
}
}
/**
* Returns a string anchor if the passed in element has a
* USEMAP that contains the passed in location.
*/
@SuppressWarnings("deprecation")
private String getMapHREF(JEditorPane html, HTMLDocument hdoc,
Element elem, AttributeSet attr, int offset,
int x, int y) {
Object useMap = attr.getAttribute(HTML.Attribute.USEMAP);
if (useMap != null && (useMap instanceof String)) {
Map m = hdoc.getMap((String)useMap);
if (m != null && offset < hdoc.getLength()) {
Rectangle bounds;
TextUI ui = html.getUI();
try {
Rectangle lBounds = ui.modelToView(html, offset,
Position.Bias.Forward);
Rectangle rBounds = ui.modelToView(html, offset + 1,
Position.Bias.Backward);
bounds = lBounds;
bounds.add(rBounds);
} catch (BadLocationException ble) {
bounds = null;
}
if (bounds != null) {
AttributeSet area = m.getArea(x - bounds.x,
y - bounds.y,
bounds.width,
bounds.height);
if (area != null) {
return (String)area.getAttribute(HTML.Attribute.
HREF);
}
}
}
}
return null;
}
/**
* Returns true if the View representing <code>e</code> contains
* the location <code>x</code>, <code>y</code>. <code>offset</code>
* gives the offset into the Document to check for.
*/
@SuppressWarnings("deprecation")
private boolean doesElementContainLocation(JEditorPane editor,
Element e, int offset,
int x, int y) {
if (e != null && offset > 0 && e.getStartOffset() == offset) {
try {
TextUI ui = editor.getUI();
Rectangle r1 = ui.modelToView(editor, offset,
Position.Bias.Forward);
if (r1 == null) {
return false;
}
Rectangle r2 = ui.modelToView(editor, e.getEndOffset(),
Position.Bias.Backward);
if (r2 != null) {
r1.add(r2);
}
return r1.contains(x, y);
} catch (BadLocationException ble) {
}
}
return true;
}
/**
* Calls linkActivated on the associated JEditorPane
* if the given position represents a link.<p>This is implemented
* to forward to the method with the same name, but with the following
* args both == -1.
*
* @param pos the position
* @param editor the editor pane
*/
protected void activateLink(int pos, JEditorPane editor) {
activateLink(pos, editor, null);
}
/**
* Calls linkActivated on the associated JEditorPane
* if the given position represents a link. If this was the result
* of a mouse click, <code>x</code> and
* <code>y</code> will give the location of the mouse, otherwise
* they will be {@literal <} 0.
*
* @param pos the position
* @param html the editor pane
*/
void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
Document doc = html.getDocument();
if (doc instanceof HTMLDocument) {
HTMLDocument hdoc = (HTMLDocument) doc;
Element e = hdoc.getCharacterElement(pos);
AttributeSet a = e.getAttributes();
AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
HyperlinkEvent linkEvent = null;
String description;
int x = -1;
int y = -1;
if (mouseEvent != null) {
x = mouseEvent.getX();
y = mouseEvent.getY();
}
if (anchor == null) {
href = getMapHREF(html, hdoc, e, a, pos, x, y);
}
else {
href = (String)anchor.getAttribute(HTML.Attribute.HREF);
}
if (href != null) {
linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
e, mouseEvent);
}
if (linkEvent != null) {
html.fireHyperlinkUpdate(linkEvent);
}
}
}
/**
* Creates and returns a new instance of HyperlinkEvent. If
* <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
* will be created.
*/
HyperlinkEvent createHyperlinkEvent(JEditorPane html,
HTMLDocument hdoc, String href,
AttributeSet anchor,
Element element,
MouseEvent mouseEvent) {
URL u;
try {
URL base = hdoc.getBase();
u = new URL(base, href);
// Following is a workaround for 1.2, in which
// new URL("file://...", "#...") causes the filename to
// be lost.
if (href != null && "file".equals(u.getProtocol()) &&
href.startsWith("#")) {
String baseFile = base.getFile();
String newFile = u.getFile();
if (baseFile != null && newFile != null &&
!newFile.startsWith(baseFile)) {
u = new URL(base, baseFile + href);
}
}
} catch (MalformedURLException m) {
u = null;
}
HyperlinkEvent linkEvent;
if (!hdoc.isFrameDocument()) {
linkEvent = new HyperlinkEvent(
html, HyperlinkEvent.EventType.ACTIVATED, u, href,
element, mouseEvent);
} else {
String target = (anchor != null) ?
(String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
if (target == null || target.isEmpty()) {
target = hdoc.getBaseTarget();
}