KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > lobobrowser > html > gui > HtmlPanel


1 /*
2     GNU LESSER GENERAL PUBLIC LICENSE
3     Copyright (C) 2006 The Lobo Project
4
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Lesser General Public
7     License as published by the Free Software Foundation; either
8     version 2.1 of the License, or (at your option) any later version.
9
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13     Lesser General Public License for more details.
14
15     You should have received a copy of the GNU Lesser General Public
16     License along with this library; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19     Contact info: xamjadmin@users.sourceforge.net
20 */

21 /*
22  * Created on Nov 19, 2005
23  */

24 package org.lobobrowser.html.gui;
25
26 import java.awt.*;
27 import java.awt.event.ActionEvent JavaDoc;
28 import java.util.*;
29 import java.io.*;
30 import javax.swing.*;
31
32 import org.lobobrowser.html.*;
33 import org.lobobrowser.html.domimpl.*;
34 import org.lobobrowser.html.parser.*;
35 import org.lobobrowser.html.renderer.*;
36 import org.lobobrowser.util.EventDispatch2;
37 import org.lobobrowser.util.gui.WrapperLayout;
38 import org.w3c.dom.Document JavaDoc;
39 import org.w3c.dom.Text JavaDoc;
40 import org.w3c.dom.html2.*;
41
42 /**
43  * The <code>HtmlPanel</code> class is a Swing
44  * component that can render a HTML DOM.
45  * @author J. H. S.
46  */

47 public class HtmlPanel extends JComponent implements FrameContext {
48     private final EventDispatch2 selectionDispatch = new SelectionDispatch();
49     private final javax.swing.Timer JavaDoc notificationTimer;
50     private final DocumentNotificationListener notificationListener;
51     private static final int NOTIF_TIMER_DELAY = 300;
52
53     private volatile boolean isFrameSet = false;
54     private volatile NodeRenderer nodeRenderer = null;
55     private volatile NodeImpl rootNode;
56     private volatile HtmlBlockPanel htmlBlock;
57     private volatile FrameSetPanel frameSetPanel;
58     private volatile int preferredWidth = -1;
59     
60     /**
61      * Constructs an <code>HtmlPanel</code>.
62      */

63     public HtmlPanel() {
64         super();
65         this.setLayout(WrapperLayout.getInstance());
66         this.setOpaque(false);
67         this.notificationTimer = new javax.swing.Timer JavaDoc(NOTIF_TIMER_DELAY, new NotificationTimerAction());
68         this.notificationTimer.setRepeats(false);
69         this.notificationListener = new LocalDocumentNotificationListener();
70     }
71
72     /**
73      * Sets a preferred width that serves as a hint in calculating
74      * the preferred size of the <code>HtmlPanel</code>. Note that
75      * the preferred size can only be calculated when a document is
76      * available, and it will vary during incremental rendering.
77      * <p>
78      * This method currently does not have any effect when the
79      * document is a FRAMESET.
80      * <p>
81      * Note also that setting the preferred width to a value other
82      * than <code>-1</code> (the default) will have an effect in renderer performance.
83      *
84      * @param width The preferred width, or <code>-1</code> to unset.
85      */

86     public void setPreferredWidth(int width) {
87         this.preferredWidth = width;
88         HtmlBlockPanel htmlBlock = this.htmlBlock;
89         if(htmlBlock != null) {
90             htmlBlock.setPreferredWidth(width);
91         }
92     }
93     
94     /**
95      * Gets the root <code>Renderable</code> of
96      * the HTML block. It returns <code>null</code>
97      * for FRAMESETs.
98      */

99     public BoundableRenderable getBlockRenderable() {
100         HtmlBlockPanel htmlBlock = this.htmlBlock;
101         return htmlBlock == null ? null : htmlBlock.getRootRenderable();
102     }
103     
104     /**
105      * Gets an instance of {@link FrameSetPanel} in case
106      * the currently rendered page is a FRAMESET.
107      * <p>
108      * Note: This method should be invoked in the GUI thread.
109      * @return A <code>FrameSetPanel</code> instance or <code>null</code>
110      * if the document currently rendered is not a FRAMESET.
111      */

112     public FrameSetPanel getFrameSetPanel() {
113         int componentCount = this.getComponentCount();
114         if(componentCount == 0) {
115             return null;
116         }
117         Object JavaDoc c = this.getComponent(0);
118         if(c instanceof FrameSetPanel) {
119             return (FrameSetPanel) c;
120         }
121         return null;
122     }
123     
124     private void setUpAsBlock(UserAgentContext ucontext, HtmlRendererContext rcontext) {
125         HtmlBlockPanel shp = this.createHtmlBlockPanel(ucontext, rcontext);
126         shp.setPreferredWidth(this.preferredWidth);
127         this.htmlBlock = shp;
128         this.frameSetPanel = null;
129         shp.setDefaultPaddingInsets(new Insets(8, 8, 8, 8));
130         this.removeAll();
131         this.add(shp);
132         this.nodeRenderer = shp;
133     }
134
135     private void setUpFrameSet(NodeImpl fsrn) {
136         this.isFrameSet = true;
137         this.htmlBlock = null;
138         FrameSetPanel fsp = this.createFrameSetPanel();
139         this.frameSetPanel = fsp;
140         this.nodeRenderer = fsp;
141         this.removeAll();
142         this.add(fsp);
143         fsp.setRootNode(fsrn);
144     }
145     
146     /**
147      * Method invoked internally to create a {@link HtmlBlockPanel}.
148      * It is made available so it can be overridden.
149      */

150     protected HtmlBlockPanel createHtmlBlockPanel(UserAgentContext ucontext, HtmlRendererContext rcontext) {
151         return new HtmlBlockPanel(0, java.awt.Color.WHITE, true, ucontext, rcontext, this);
152     }
153
154     /**
155      * Method invoked internally to create a {@link FrameSetPanel}.
156      * It is made available so it can be overridden.
157      */

158     protected FrameSetPanel createFrameSetPanel() {
159         return new FrameSetPanel();
160     }
161     
162     /**
163      * Scrolls the document such that x and y coordinates
164      * are placed in the upper-left corner of the panel.
165      * @param x The x coordinate.
166      * @param y The y coordinate.
167      */

168     public void scroll(int x, int y) {
169         //TODO
170
}
171
172     /**
173      * Clears the current document if any.
174      * If called outside the GUI thread, the operation
175      * will be scheduled to be performed in the GUI
176      * thread.
177      */

178     public void clearDocument() {
179         if(java.awt.EventQueue.isDispatchThread()) {
180             this.clearDocumentImpl();
181         }
182         else {
183             java.awt.EventQueue.invokeLater(new Runnable JavaDoc() {
184                 public void run() {
185                     HtmlPanel.this.clearDocumentImpl();
186                 }
187             });
188         }
189     }
190     
191     private void clearDocumentImpl() {
192         HTMLDocumentImpl prevDocument = (HTMLDocumentImpl) this.rootNode;
193         if(prevDocument != null) {
194             prevDocument.removeDocumentNotificationListener(this.notificationListener);
195         }
196         NodeRenderer nr = this.nodeRenderer;
197         if(nr != null) {
198             nr.setRootNode(null);
199         }
200         this.rootNode = null;
201         this.htmlBlock = null;
202         this.nodeRenderer = null;
203         this.isFrameSet = false;
204         this.removeAll();
205         this.revalidate();
206         this.repaint();
207     }
208     
209     /**
210      * Sets an HTML DOM node and invalidates the component so it is
211      * rendered immediately in the GUI thread.
212      * @param node This should
213      * normally be a Document instance obtained with
214      * {@link org.lobobrowser.html.parser.DocumentBuilderImpl}.
215      * <p>
216      * Note: It is safe to call this method outside of the GUI thread.
217      * @param rcontext A renderer context.
218      * @param pcontext A parser context.
219      * @deprecated HtmlParserContext is no longer used here.
220      */

221     public void setDocument(final Document JavaDoc node, final HtmlRendererContext rcontext, final HtmlParserContext pcontext) {
222         this.setDocument(node, rcontext);
223     }
224     
225     /**
226      * Sets an HTML DOM node and invalidates the component so it is
227      * rendered as soon as possible in the GUI thread.
228      * <p>
229      * If this method is called from a thread that is not the GUI
230      * dispatch thread, the document is scheduled to be set later.
231      * Note that {@link #setPreferredWidth(int) preferred size}
232      * calculations should be done in the GUI dispatch thread for
233      * this reason.
234      * @param node This should
235      * normally be a Document instance obtained with
236      * {@link org.lobobrowser.html.parser.DocumentBuilderImpl}.
237      * <p>
238      * @param rcontext A renderer context.
239      */

240     public void setDocument(final Document JavaDoc node, final HtmlRendererContext rcontext) {
241         if(java.awt.EventQueue.isDispatchThread()) {
242             this.setDocumentImpl(node, rcontext);
243         }
244         else {
245             java.awt.EventQueue.invokeLater(new Runnable JavaDoc() {
246                 public void run() {
247                     HtmlPanel.this.setDocumentImpl(node, rcontext);
248                 }
249             });
250         }
251     }
252
253     private void setDocumentImpl(Document JavaDoc node, HtmlRendererContext rcontext) {
254         // Expected to be called in the GUI thread.
255
if(!(node instanceof HTMLDocumentImpl)) {
256             throw new IllegalArgumentException JavaDoc("Only nodes of type HTMLDocumentImpl are currently supported. Use DocumentBuilderImpl.");
257         }
258         HTMLDocumentImpl prevDocument = (HTMLDocumentImpl) this.rootNode;
259         if(prevDocument != null) {
260             prevDocument.removeDocumentNotificationListener(this.notificationListener);
261         }
262         HTMLDocumentImpl nodeImpl = (HTMLDocumentImpl) node;
263         nodeImpl.addDocumentNotificationListener(this.notificationListener);
264         this.rootNode = nodeImpl;
265         NodeImpl fsrn = this.getFrameSetRootNode(nodeImpl);
266         boolean newIfs = fsrn != null;
267         if(newIfs != this.isFrameSet || this.getComponentCount() == 0) {
268             this.isFrameSet = newIfs;
269             if(newIfs) {
270                 this.setUpFrameSet(fsrn);
271             }
272             else {
273                 this.setUpAsBlock(rcontext.getUserAgentContext(), rcontext);
274             }
275         }
276         NodeRenderer nr = this.nodeRenderer;
277         if(nr != null) {
278             // These subcomponents should take care
279
// of revalidation.
280
if(newIfs) {
281                 nr.setRootNode(fsrn);
282             }
283             else {
284                 nr.setRootNode(nodeImpl);
285             }
286         }
287         else {
288             this.invalidate();
289             this.validate();
290             this.repaint();
291         }
292     }
293
294     /**
295      * Renders HTML given as a string.
296      */

297     public void setHtml(String JavaDoc htmlSource, String JavaDoc uri, HtmlRendererContext rcontext) {
298         try {
299             DocumentBuilderImpl builder = new DocumentBuilderImpl(rcontext.getUserAgentContext(), rcontext);
300             Reader reader = new StringReader(htmlSource);
301             try {
302                 InputSourceImpl is = new InputSourceImpl(reader, uri);
303                 Document JavaDoc document = builder.parse(is);
304                 this.setDocument(document, rcontext);
305             } finally {
306                 reader.close();
307             }
308         } catch(java.io.IOException JavaDoc ioe) {
309             throw new IllegalStateException JavaDoc("Unexpected condition.", ioe);
310         } catch(org.xml.sax.SAXException JavaDoc se) {
311             throw new IllegalStateException JavaDoc("Unexpected condition.", se);
312         }
313     }
314     
315     /**
316      * Gets the HTML DOM node currently rendered if any.
317      */

318     public NodeImpl getRootNode() {
319         return this.rootNode;
320     }
321
322     private boolean resetIfFrameSet() {
323         NodeImpl nodeImpl = this.rootNode;
324         NodeImpl fsrn = this.getFrameSetRootNode(nodeImpl);
325         boolean newIfs = fsrn != null;
326         if(newIfs != this.isFrameSet || this.getComponentCount() == 0) {
327             this.isFrameSet = newIfs;
328             if(newIfs) {
329                 this.setUpFrameSet(fsrn);
330                 NodeRenderer nr = this.nodeRenderer;
331                 nr.setRootNode(fsrn);
332                 // Set proper bounds and repaint.
333
this.validate();
334                 this.repaint();
335                 return true;
336             }
337         }
338         return false;
339     }
340     
341     private NodeImpl getFrameSetRootNode(NodeImpl node) {
342         if(node instanceof Document JavaDoc) {
343             ElementImpl element = (ElementImpl) ((Document JavaDoc) node).getDocumentElement();
344             if(element != null && "HTML".equalsIgnoreCase(element.getTagName())) {
345                 return this.getFrameSet(element);
346             }
347             else {
348                 return this.getFrameSet(node);
349             }
350         }
351         else {
352             return null;
353         }
354     }
355     
356     private NodeImpl getFrameSet(NodeImpl node) {
357         NodeImpl[] children = node.getChildrenArray();
358         if(children == null) {
359             return null;
360         }
361         int length = children.length;
362         NodeImpl frameSet = null;
363         for(int i = 0; i < length; i++) {
364             NodeImpl child = children[i];
365             if(child instanceof Text JavaDoc) {
366                 // Ignore
367
}
368             else if(child instanceof ElementImpl) {
369                 String JavaDoc tagName = child.getNodeName();
370                 if("HEAD".equalsIgnoreCase(tagName) ||
371                    "NOFRAMES".equalsIgnoreCase(tagName) ||
372                    "TITLE".equalsIgnoreCase(tagName) ||
373                    "META".equalsIgnoreCase(tagName) ||
374                    "SCRIPT".equalsIgnoreCase(tagName) ||
375                    "NOSCRIPT".equalsIgnoreCase(tagName)) {
376                     // ignore it
377
}
378                 else if("FRAMESET".equalsIgnoreCase(tagName)) {
379                     frameSet = child;
380                     break;
381                 }
382                 else {
383                     if(this.hasSomeHtml((ElementImpl) child)) {
384                         return null;
385                     }
386                 }
387             }
388         }
389         return frameSet;
390     }
391     
392     private boolean hasSomeHtml(ElementImpl element) {
393         String JavaDoc tagName = element.getTagName();
394         if("HEAD".equalsIgnoreCase(tagName) || "TITLE".equalsIgnoreCase(tagName) || "META".equalsIgnoreCase(tagName)) {
395             return false;
396         }
397         NodeImpl[] children = element.getChildrenArray();
398         if(children != null) {
399             int length = children.length;
400             for(int i = 0; i < length; i++) {
401                 NodeImpl child = children[i];
402                 if(child instanceof Text JavaDoc) {
403                     String JavaDoc textContent = ((Text JavaDoc) child).getTextContent();
404                     if(textContent != null && !"".equals(textContent.trim())) {
405                         return false;
406                     }
407                 }
408                 else if(child instanceof ElementImpl) {
409                     if(this.hasSomeHtml((ElementImpl) child)) {
410                         return false;
411                     }
412                 }
413             }
414         }
415         return true;
416     }
417
418     /**
419      * Internal method used to expand the selection to the given point.
420      * <p>
421      * Note: This method should be invoked in the GUI thread.
422      */

423     public void expandSelection(RenderableSpot rpoint) {
424         HtmlBlockPanel block = this.htmlBlock;
425         if(block != null) {
426             block.setSelectionEnd(rpoint);
427             block.repaint();
428             this.selectionDispatch.fireEvent(new SelectionChangeEvent(this, block.isSelectionAvailable()));
429         }
430     }
431
432     /**
433      * Internal method used to reset the selection so that
434      * it is empty at the given point. This is what is called
435      * when the user clicks on a point in the document.
436      * <p>
437      * Note: This method should be invoked in the GUI thread.
438      */

439     public void resetSelection(RenderableSpot rpoint) {
440         HtmlBlockPanel block = this.htmlBlock;
441         if(block != null) {
442             block.setSelectionStart(rpoint);
443             block.setSelectionEnd(rpoint);
444             block.repaint();
445         }
446         this.selectionDispatch.fireEvent(new SelectionChangeEvent(this, false));
447     }
448     
449     /**
450      * Gets the selection text.
451      * <p>
452      * Note: This method should be invoked in the GUI thread.
453      */

454     public String JavaDoc getSelectionText() {
455         HtmlBlockPanel block = this.htmlBlock;
456         if(block == null) {
457             return null;
458         }
459         else {
460             return block.getSelectionText();
461         }
462     }
463     
464     /**
465      * Gets a DOM node enclosing the selection. The node returned should
466      * be the inner-most node that encloses both selection start and end
467      * points. Note that the selection end point may be just outside of
468      * the selection.
469      * <p>
470      * Note: This method should be invoked in the GUI thread.
471      */

472     public org.w3c.dom.Node JavaDoc getSelectionNode() {
473         HtmlBlockPanel block = this.htmlBlock;
474         if(block == null) {
475             return null;
476         }
477         else {
478             return block.getSelectionNode();
479         }
480     }
481
482     /**
483      * Returns true only if the current block has a selection.
484      * This method has no effect in FRAMESETs at the moment.
485      */

486     public boolean hasSelection() {
487         HtmlBlockPanel block = this.htmlBlock;
488         if(block == null) {
489             return false;
490         }
491         else {
492             return block.hasSelection();
493         }
494     }
495
496     /**
497      * Copies the current selection, if any, into the clipboard.
498      * This method has no effect in FRAMESETs at the moment.
499      */

500     public boolean copy() {
501         HtmlBlockPanel block = this.htmlBlock;
502         if(block != null) {
503             return block.copy();
504         }
505         else {
506             return false;
507         }
508     }
509
510     /**
511      * Adds listener of selection changes. Note that it does
512      * not have any effect on FRAMESETs.
513      * @param listener An instance of {@link SelectionChangeListener}.
514      */

515     public void addSelectionChangeListener(SelectionChangeListener listener) {
516         this.selectionDispatch.addListener(listener);
517     }
518
519     /**
520      * Removes a listener of selection changes that was
521      * previously added.
522      */

523     public void removeSelectionChangeListener(SelectionChangeListener listener) {
524         this.selectionDispatch.removeListener(listener);
525     }
526     
527     private ArrayList notifications = new ArrayList(1);
528     
529     private void addNotification(DocumentNotification notification) {
530         // This can be called in a random thread.
531
ArrayList notifs = this.notifications;
532         synchronized(notifs) {
533             notifs.add(notification);
534         }
535         if(EventQueue.isDispatchThread()) {
536             // Process imediately
537
this.processNotifications();
538         }
539         else {
540             this.notificationTimer.restart();
541         }
542     }
543     
544     /**
545      * Invalidates the layout of the given node and schedules it
546      * to be layed out later. Multiple invalidations may be
547      * processed in a single document layout.
548      */

549     public void delayedRelayout(NodeImpl node) {
550         ArrayList notifs = this.notifications;
551         synchronized(notifs) {
552             notifs.add(new DocumentNotification(DocumentNotification.SIZE, node));
553         }
554         this.notificationTimer.restart();
555     }
556
557     private void processNotifications() {
558         // This is called in the GUI thread.
559
ArrayList notifs = this.notifications;
560         DocumentNotification[] notifsArray;
561         synchronized(notifs) {
562             int size = notifs.size();
563             if(size == 0) {
564                 return;
565             }
566             notifsArray = new DocumentNotification[size];
567             notifsArray = (DocumentNotification[]) notifs.toArray(notifsArray);
568             notifs.clear();
569         }
570         int length = notifsArray.length;
571         for(int i = 0; i < length; i++) {
572             DocumentNotification dn = notifsArray[i];
573             if(dn.node instanceof HTMLFrameSetElement && this.htmlBlock != null) {
574                 if(this.resetIfFrameSet()) {
575                     // Revalidation already taken care of.
576
return;
577                 }
578             }
579         }
580         HtmlBlockPanel blockPanel = this.htmlBlock;
581         if(blockPanel != null) {
582             blockPanel.processDocumentNotifications(notifsArray);
583         }
584         FrameSetPanel frameSetPanel = this.frameSetPanel;
585         if(frameSetPanel != null) {
586             frameSetPanel.processDocumentNotifications(notifsArray);
587         }
588     }
589     
590     private class SelectionDispatch extends EventDispatch2 {
591         /* (non-Javadoc)
592          * @see org.xamjwg.util.EventDispatch2#dispatchEvent(java.util.EventListener, java.util.EventObject)
593          */

594         protected void dispatchEvent(EventListener listener, EventObject event) {
595             ((SelectionChangeListener) listener).selectionChanged((SelectionChangeEvent) event);
596         }
597     }
598     
599     private class LocalDocumentNotificationListener implements DocumentNotificationListener {
600         public void allInvalidated() {
601             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, null));
602         }
603
604         public void invalidated(NodeImpl node) {
605             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, node));
606         }
607
608         public void lookInvalidated(NodeImpl node) {
609             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.LOOK, node));
610         }
611
612         public void positionInvalidated(NodeImpl node) {
613             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.POSITION, node));
614         }
615
616         public void sizeInvalidated(NodeImpl node) {
617             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.SIZE, node));
618         }
619
620         public void externalScriptLoading(NodeImpl node) {
621             // Ignorable here.
622
}
623
624         public void nodeLoaded(NodeImpl node) {
625             HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, node));
626         }
627     }
628     
629     private class NotificationTimerAction implements java.awt.event.ActionListener JavaDoc {
630         public void actionPerformed(ActionEvent JavaDoc e) {
631             HtmlPanel.this.processNotifications();
632         }
633     }
634 }
635
Popular Tags