KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output > OutputTabInner


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.core.output;
21
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.io.PipedReader JavaDoc;
26 import java.io.PipedWriter JavaDoc;
27 import java.io.Reader JavaDoc;
28 import java.io.OutputStreamWriter JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.io.FileWriter JavaDoc;
31 import java.io.File JavaDoc;
32
33 import java.awt.*;
34 import java.awt.event.*;
35 import java.awt.datatransfer.*;
36 import java.beans.PropertyChangeListener JavaDoc;
37 import java.beans.PropertyChangeEvent JavaDoc;
38 import java.util.ArrayList JavaDoc;
39 import java.util.Observable JavaDoc;
40 import java.util.Observer JavaDoc;
41 import java.util.Set JavaDoc;
42 import javax.swing.AbstractAction JavaDoc;
43
44 import javax.swing.JComponent JavaDoc;
45 import javax.swing.JPanel JavaDoc;
46 import javax.swing.KeyStroke JavaDoc;
47 import javax.swing.JPopupMenu JavaDoc;
48 import javax.swing.JMenuItem JavaDoc;
49 import javax.swing.JSplitPane JavaDoc;
50 import javax.swing.SwingUtilities JavaDoc;
51 import javax.swing.text.Keymap JavaDoc;
52
53 import org.openide.ErrorManager;
54 import org.openide.windows.*;
55 import org.openide.awt.MouseUtils;
56 import org.openide.actions.CutAction;
57 import org.openide.actions.DeleteAction;
58 import org.openide.actions.PasteAction;
59 import org.openide.actions.CopyAction;
60 import org.openide.actions.FindAction;
61 import org.openide.util.io.NullOutputStream;
62 import org.openide.util.NbBundle;
63 import org.openide.util.Mutex;
64 import org.openide.util.datatransfer.*;
65 import org.openide.util.actions.ActionPerformer;
66 import org.openide.util.actions.SystemAction;
67 import org.openide.util.actions.CallbackSystemAction;
68
69 import org.netbeans.lib.terminalemulator.*;
70 import org.openide.util.Lookup;
71
72 /**
73  * Modified OutputTabTerm to be used as inner component of OutputView
74  * for new output window implementation. Prototype. If possible should be
75  * merged with OutputTabTerm or common code should be separated. Or common
76  * subclass could be created.
77  *
78  * @author Marek Slama
79  *
80  */

81
82 public class OutputTabInner extends TopComponent
83 implements InputOutput, PropertyChangeListener JavaDoc {
84
85
86     public static final String JavaDoc ICON_RESOURCE =
87         "/org/netbeans/core/resources/frames/output.gif"; // NOI18N
88

89     /** Should this InputOutput take the focus and bring the window to the front,
90     * when anything is written into the stream. */

91     private boolean focusTaken = false;
92
93     /** The reader for standard input */
94     private Reader JavaDoc inReader;
95     /** piped writer */
96     PipedWriter JavaDoc inWriter = new PipedWriter JavaDoc();
97     
98     /** If true, the error output is separated from the normal output */
99     private boolean errSeparated = false;
100     private Boolean JavaDoc inputVisible = null;
101     private boolean errVisible = false;
102     
103     /** Splitpane used to display split stdout/stderr */
104     private JSplitPane JavaDoc splitpane = null;
105
106     /** singleton instance of the standard output tab */
107     //private static OutputTabInner standard;
108

109     private static final long serialVersionUID =3276412782250080205L;
110     
111     private OutTermPane output;
112     /** Error pane */
113     private OutTermPane error = null;
114     
115     //private static Factory factory;
116

117     private boolean hideOnly = false;
118     
119     // #37711 Hack, to be able to call isClosed API method from any thread while
120
// the windows API can be called from AWT thread only.
121
/** Flag indicating whether it is opened in window system. */
122     private boolean openedInWinSys;
123     private final Object JavaDoc LOCK_OPENED_IN_WINSYS = new Object JavaDoc();
124     
125     OutputTabInner(final String JavaDoc name) {
126         synchronized (OutputTabInner.class) {
127             
128             output = new OutTermPane(this);
129             
130             synchronized(OutputView.ioCache) {
131                 OutputView.ioCache.put(name, this);
132             }
133             
134             Mutex.EVENT.readAccess(new Runnable JavaDoc() {
135                 public void run() {
136                     output.setName("StdOut"); //NOI18N
137
// set accessible description
138
getAccessibleContext ().setAccessibleName (
139                         NbBundle.getBundle (OutputTabInner.class).getString ("ACSN_OutputWindow"));
140                     getAccessibleContext ().setAccessibleDescription (
141                         NbBundle.getBundle (OutputTabInner.class).getString ("ACSD_OutputWindow"));
142                     setBorder (null);
143                     add (output);
144                     setName (name);
145                 }
146             });
147
148             TopComponent.getRegistry().addPropertyChangeListener(
149                 org.openide.util.WeakListener.propertyChange(this, TopComponent.getRegistry()));
150             
151             getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
152                 KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK),
153                 "discard"); //NOI18N
154

155             getActionMap().put("discard", new DiscardAction());
156         }
157     }
158     
159     private class DiscardAction extends AbstractAction JavaDoc {
160         public void actionPerformed (ActionEvent ae) {
161             OutputView view = (OutputView) SwingUtilities.getAncestorOfClass(
162                 OutputView.class, OutputTabInner.this);
163             if (view != null) {
164                 view.discardTab(OutputTabInner.this);
165             }
166         }
167     }
168
169     public int getPersistenceType() {
170         return TopComponent.PERSISTENCE_ONLY_OPENED;
171     }
172
173     public void layout() {
174         if (splitpane != null) {
175             splitpane.setBounds (0, 0, getWidth(), getHeight());
176         } else {
177             output.setBounds (0, 0, getWidth(), getHeight());
178         }
179     }
180     
181     private synchronized OutTermPane getErrorTerm (boolean create) {
182         if (error == null && create) {
183             error = new OutTermPane (this);
184             error.setName("StdErr"); //NOI18N
185
Mutex.EVENT.readAccess(new SplitPaneInstaller());
186         }
187         return error;
188     }
189     
190     private class SplitPaneInstaller implements Runnable JavaDoc {
191         public void run() {
192             splitpane = new JSplitPane JavaDoc();
193             if (splitpane.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) {
194                 splitpane.setLeftComponent(error);
195                 splitpane.setRightComponent(output);
196             } else {
197                 splitpane.setLeftComponent(output);
198                 splitpane.setRightComponent(error);
199             }
200             splitpane.setDividerLocation(getWidth() / 2);
201             add (splitpane);
202         }
203     }
204     
205     /** Runnable which removes the split pane and restores the output to being
206      * child of this component, to run on the event thread. */

207     private class SplitPaneRemover implements Runnable JavaDoc {
208         public void run() {
209             //theoretically, one could munge the order of things enough for
210
//the splitpane to be null, so check it
211
if (splitpane != null) {
212                 remove (splitpane);
213             }
214             error = null;
215             add (output);
216             Container parent = getParent ();
217             if (parent != null) parent.validate ();
218         }
219     }
220     
221     public synchronized Object JavaDoc writeReplace() throws java.io.ObjectStreamException JavaDoc {
222         // bugfix #15703 - don't serialize tabs which you don't want to deseralize
223
// later in Replace.readResolve, otherwise InstanceDataObject fails to
224
// create instance of the tab
225
if (!this.equals(OutputView.getFactory().getStdOutputTab())) {
226             return null;
227         }
228
229         if (replace == null) {
230             replace = new Replace(this.equals(OutputView.getFactory().getStdOutputTab()));
231         }
232         return replace;
233     }
234     
235     // mainly for testing and debugging purposes
236
// returns ActiveTerm for output OutTermPane
237
public Term getTerm() {
238         return getTerm( true );
239     }
240     
241     // mainly for testing and debugging purposes
242
// returns ActiveTerm for output or error OutTermPane
243
public Term getTerm(boolean fromOutputPane) {
244         if ( fromOutputPane ) {
245             return output.getTerm();
246         } else {
247             return getErrorTerm(true).getTerm();
248         }
249     }
250     
251     /* Returns text content of output tab. This is
252     * content of terminal buffer, which is limited by
253     * history size.
254     * <p>
255     * This function is no MT-safe call it from the AWT Event Dispatch thread.
256     *
257     * @return text content of output window buffer
258     */

259     public String JavaDoc toString () {
260         Term term = getTerm( true );
261         if ( term == null )
262             return ""; // NOI18N
263

264         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
265         for (int i=0; i<=term.getCursorCoord().row; i++) {
266             if ( i > 0 )
267                 buf.append('\n');
268             buf.append( term.getRowText( i ) );
269         }
270         return buf.toString();
271     }
272
273     private Replace replace;
274
275     /** This class is serializaed instead of OutputTabInner */
276     static class Replace implements java.io.Serializable JavaDoc {
277         
278         boolean defaultInstance;
279
280         private static final long serialVersionUID =-3126744916624172415L;
281
282         public Replace (boolean defaultInstance) {
283             this.defaultInstance = defaultInstance;
284         }
285         
286         /** Resolve as default singleton or null */
287         public Object JavaDoc readResolve() throws java.io.ObjectStreamException JavaDoc {
288             if ( defaultInstance )
289                 return OutputView.getFactory().getStdOutputTab();
290             else
291                 return null;
292         }
293     }
294     
295     void ensureOpen() {
296         //issue 37810, this code must run in EQ
297
//#37996 When called from EQ it must be synchronous to keep
298
//code execution order.
299
Mutex.EVENT.readAccess(new Runnable JavaDoc() {
300             public void run() {
301                 if (isClosed()) {
302                     //debug("opening"); // NOI18N
303
OutputView.findDefault().openInOV(OutputTabInner.this);
304                 }
305             }
306         });
307     }
308     
309     static String JavaDoc getOutDisplayName() {
310         return NbBundle.getBundle(OutputTabInner.class).getString("CTL_OutputWindow");
311     }
312     
313     //
314
// TopComponent methods
315
//
316

317     /** Activates the component */
318     protected void componentActivated () {
319         output.activated();
320     }
321
322     /** Deactivates the component */
323     protected void componentDeactivated () {
324         output.deactivated();
325     }
326
327     public void requestFocus () {
328         super.requestFocus();
329         output.requestFocus();
330     }
331     
332     public boolean requestFocusInWindow () {
333         super.requestFocusInWindow();
334         boolean result = output.requestFocusInWindow();
335         return result;
336     }
337     
338     //
339
// InputOutput methods
340
//
341

342     public boolean isFocusTaken() {
343         return focusTaken;
344     }
345     
346     /** Set true, if you want to focus out window, when anything is written
347     * into the stream */

348     public void setFocusTaken(boolean value) {
349         focusTaken = value;
350     }
351
352     private boolean firstOut=true;
353     public org.openide.windows.OutputWriter getOut() {
354         if (firstOut) {
355             select();
356             firstOut = false;
357         }
358         return output.writer;
359     }
360     
361     public void select() {
362         /*System.out.println("== OutputTabInner.select ENTER"
363         + " name:" + getName());*/

364         
365         Mutex.EVENT.readAccess(new Runnable JavaDoc() {
366             public void run() {
367                 ensureOpen();
368                 OutputView.findDefault().requestVisible(OutputTabInner.this);
369             }
370         });
371         /*System.out.println("== OutputTabInner.select LEAVE 2"
372         + " name:" + getName());*/

373     }
374     
375     public org.openide.windows.OutputWriter getErr() {
376         //debug("getErr"); // NOI18N
377
if (errSeparated) {
378             return getErrorTerm(true).writer;
379         }
380         else return output.writer;
381     }
382     
383     /** Method to acquire a Reader for accessing an input of the tab.
384     * @return a Reader for accessing an input line of the tab.
385     */

386     public java.io.Reader JavaDoc getIn() {
387         //debug("getIn()"); // NOI18N
388
// init inReader
389
flushReader();
390         // only if input isn't explicitly forbidden
391
if ( inputVisible == null || inputVisible.booleanValue() )
392             setInputVisible(true);
393         return inReader;
394     }
395
396     public void setErrSeparated(boolean value) {
397         if (errSeparated != value) {
398             errSeparated = value;
399             if (errVisible != errSeparated) {
400                 setErrVisible(errSeparated);
401             }
402         }
403     }
404     
405     public java.io.Reader JavaDoc flushReader() {
406         //debug("flushReader"); // NOI18N
407
inWriter = new PipedWriter JavaDoc();
408         try {
409             inReader = new PipedReader JavaDoc(inWriter);
410         } catch (java.io.IOException JavaDoc ex) {
411             ErrorManager.getDefault().notify(ex);
412             return null;
413         }
414         return inReader;
415     }
416     
417     public void setOutputVisible(boolean param) {
418         //debug("setOutputVisible"); // NOI18N
419
if (param) {
420             Mutex.EVENT.readAccess(new Runnable JavaDoc() {
421                 public void run() {
422                     ensureOpen();
423                 }
424             });
425         }
426         else {
427             hideOnly = true;
428             doClose();
429         }
430     }
431     
432     public void setErrVisible(boolean value) {
433         if (errSeparated || errVisible != value ) {
434             errVisible = value;
435             if (errVisible) {
436                 //create the error term - this will install
437
//the splitpane for us
438
getErrorTerm(true);
439             } else {
440                 if (error != null) {
441                     Mutex.EVENT.readAccess(new SplitPaneRemover());
442                 }
443             }
444         }
445     }
446     
447     public boolean isClosed() {
448         //debug("isClosed()"); // NOI18N
449
// XXX
450
boolean isInContainerTopComponent = false;
451         Component JavaDoc p = getParent();
452         if(p instanceof javax.swing.JComponent JavaDoc) {
453             javax.swing.JComponent JavaDoc parent = (javax.swing.JComponent JavaDoc)p;
454             Object JavaDoc value = parent.getClientProperty("ContainerTopComponent"); // TEMP NOI18N
455
if(value instanceof Boolean JavaDoc) {
456                 isInContainerTopComponent = ((Boolean JavaDoc)value).booleanValue();
457             }
458         }
459         
460         return !isOpenedInWinSys() && !isInContainerTopComponent;
461     }
462     
463     protected void componentOpened() {
464         super.componentOpened();
465         setOpenedInWinSys(true);
466     }
467     
468     protected void componentClosed() {
469         super.componentClosed();
470         setOpenedInWinSys(false);
471     }
472     
473     private void setOpenedInWinSys(boolean o) {
474         synchronized(LOCK_OPENED_IN_WINSYS) {
475             openedInWinSys = o;
476         }
477     }
478     
479     private boolean isOpenedInWinSys() {
480         synchronized(LOCK_OPENED_IN_WINSYS) {
481             return openedInWinSys;
482         }
483     }
484     
485     public void setInputVisible(boolean param) {
486         //debug("setInputVisible:" + param); // NOI18N
487
if (param) {
488             Mutex.EVENT.readAccess(new Runnable JavaDoc() {
489                 public void run() {
490                     ensureOpen();
491                 }
492             });
493         }
494         inputVisible = param ? Boolean.TRUE : Boolean.FALSE;
495         output.setReadWrite( param );
496     }
497     
498     public boolean isErrSeparated() {
499         //debug("isErrSeparated"); // NOI18N
500
return errSeparated;
501     }
502     
503     public void closeInputOutput() {
504         //debug("closeInputOutput"); // NOI18N
505
doClose();
506         try {
507             inWriter.flush();
508             inWriter.close();
509         } catch (IOException JavaDoc ioe) {
510             // do nothing in this case
511
} catch (NullPointerException JavaDoc npe) {
512             // only for sure - do nothing in this case
513
}
514         output.writer.close();
515         if (error != null) {
516             error.writer.close();
517         }
518     }
519     
520     private void doClose() {
521         Mutex.EVENT.readAccess(new Runnable JavaDoc() {
522             public void run() {
523                 if(isDisplayable()) {
524                     OutputView.findDefault().discardTab(OutputTabInner.this);
525                 }
526             }
527         });
528     }
529     
530     public void open () {
531         OutputView.findDefault().openInOV(this);
532     }
533     
534     // Replacement for TopComponentListener. Listen on opened components.
535
public void propertyChange(PropertyChangeEvent JavaDoc evt) {
536         if(TopComponent.Registry.PROP_OPENED.equals(evt.getPropertyName())) {
537             Object JavaDoc oldValue = evt.getOldValue();
538             Object JavaDoc newValue = evt.getNewValue();
539             
540             if(!hideOnly
541             && oldValue instanceof Set JavaDoc && ((Set JavaDoc)oldValue).contains(this) // Was opened.
542
&& newValue instanceof Set JavaDoc && !((Set JavaDoc)newValue).contains(this)) { // Is not opened yet -> was closed.
543
output.doClear();
544                 if (error != null) {
545                     error.doClear();
546                 }
547             }
548             hideOnly = false;
549         }
550     }
551  
552     private static OutputSettings outputSettings () {
553         return (OutputSettings)OutputSettings.findObject (OutputSettings.class, true);
554     }
555     
556
557
558     // share 'keyStrokeSet' among all Terms.
559
// doubles as a one-time flag.
560
private static HashSet JavaDoc keyStrokeSet = null;
561
562     // see setHyperlinkNavigationEnabled() to see what's this all about
563
private static HashSet JavaDoc keyStrokeSet2 = null;
564
565     private static void updateKeyStrokeSet() {
566     keyStrokeSet.clear();
567     keyStrokeSet.addAll( java.util.Arrays.asList( ((Keymap JavaDoc)Lookup.getDefault ().lookup ( Keymap JavaDoc.class ) ).getBoundKeyStrokes()) );
568     for (int ks = KeyEvent.VK_A; ks <= KeyEvent.VK_Z; ks++)
569         keyStrokeSet.add( KeyStroke.getKeyStroke( ks, Event.ALT_MASK ) );
570
571     // Note that we have to have the keyChar be ASCII Ctrl-T for this
572
// to work.
573
KeyStroke JavaDoc ks1 = KeyStroke.getKeyStroke(new Character JavaDoc((char)('T'-64)),
574                     Event.CTRL_MASK|Event.SHIFT_MASK);
575     KeyStroke JavaDoc ks2 = KeyStroke.getKeyStroke(new Character JavaDoc((char)('T'-64)),
576                     Event.CTRL_MASK);
577     keyStrokeSet2 = (HashSet JavaDoc) keyStrokeSet.clone();
578     keyStrokeSet2.add(ks1);
579     keyStrokeSet2.add(ks2);
580     }
581
582
583     private static HashSet JavaDoc getCommonKeyStrokeSet() {
584
585     if (keyStrokeSet != null)
586         return keyStrokeSet;
587
588     // Initialize only once
589

590     keyStrokeSet = new HashSet JavaDoc();
591
592     // Arrange so that we track changes in global keymap
593
Keymap JavaDoc map = (Keymap JavaDoc)org.openide.util.Lookup.getDefault().lookup(Keymap JavaDoc.class);
594         // This is a hack. Since there is no official API for notifying changes
595
// from Keymap, just rely on fact that core impl is an Observable.
596
if (map instanceof Observable JavaDoc) {
597             Observable JavaDoc o = (Observable JavaDoc)map;
598             o.addObserver(new Observer JavaDoc() {
599                 public void update(Observable JavaDoc o, Object JavaDoc arg) {
600             updateKeyStrokeSet();
601                 }
602             });
603         }
604
605     updateKeyStrokeSet();
606
607     return keyStrokeSet;
608     }
609
610     private static HashSet JavaDoc getCommonKeyStrokeSet2() {
611     getCommonKeyStrokeSet();
612     return keyStrokeSet2;
613     }
614
615
616
617     public static final class OutTermPane extends JPanel JavaDoc implements ActionPerformer,
618                                     ActionListener, PropertyChangeListener JavaDoc, Runnable JavaDoc {
619         /** generated Serialized Version UID */
620         static final long serialVersionUID = -633812012958420549L;
621
622         private static final String JavaDoc REDIR_EXT = ".out"; // NOI18N
623

624         /** Information channel for this pane */
625         TermOutputWriter writer;
626
627         /** My parent output tab */
628         OutputTabInner tab;
629         private boolean findNextEnabled = false;
630
631
632         /** Copy action */
633         private static CopyAction copy = (CopyAction)CopyAction.get (CopyAction.class);
634         private static FindAction find = (FindAction)FindAction.get (FindAction.class);
635         private static CutAction cut = (CutAction) CutAction.findObject(CutAction.class, true);
636         private static DeleteAction delete = (DeleteAction) DeleteAction.findObject(DeleteAction.class, true);
637         private static PasteAction paste = (PasteAction) PasteAction.findObject(PasteAction.class, true);
638
639         /** Private instance of Next jump action */
640         private static NextOutJumpAction nextAction = (NextOutJumpAction)NextOutJumpAction.get (NextOutJumpAction.class);
641
642         /** Private instance of Previous jump action */
643         private static PreviousOutJumpAction previousAction = (PreviousOutJumpAction)PreviousOutJumpAction.get (PreviousOutJumpAction.class);
644
645         /** Performer for jump actions */
646         private JumpActionPerformer jumpPerformer = new JumpActionPerformer();
647         private ActionPerformer copyActionPerformer = null;
648         CallbackSystemAction csa;
649
650         private HashMap JavaDoc listeners = new HashMap JavaDoc();
651
652         /** PopupMenu */
653         JPopupMenu JavaDoc jPopup;
654         private JMenuItem JavaDoc selectAllItem;
655         private JMenuItem JavaDoc findNextItem;
656         private JMenuItem JavaDoc clearItem;
657         private JMenuItem JavaDoc redirItem;
658         private JMenuItem JavaDoc discardItem;
659         private JMenuItem JavaDoc discardAllItem;
660
661         private TermListener listener;
662         private TermInputListener input_listener;
663         ActiveTerm term;
664
665         private boolean redirection = false;
666         private int tabSize;
667
668     private ActiveRegion currentHyperlink = null;
669     private ActiveRegion activeHyperlink = null; // was 'currentRegion'
670

671         /** Creates pane without association to a tab.
672         */

673         public OutTermPane() {
674             this (null);
675         }
676
677
678         /** Creates new OutTermPane in the specific OutputTabInner */
679         public OutTermPane(OutputTabInner tab) {
680
681             this.tab = tab;
682
683             listener = new TermListener() {
684                 public void sizeChanged(Dimension cells, Dimension pixels) {
685                     //PENDING
686
}
687             };
688
689             term = new ActiveTerm ();
690
691             updateNextPrevActions();
692
693             outputSettings ().addPropertyChangeListener (this);
694
695             term.addListener(listener);
696             setReadWrite( false );
697             term.setHistorySize( outputSettings().getHistorySize() );
698             term.pushStream(new LineDiscipline());
699             if ( tab != null ) {
700                 input_listener = new TIListener( tab );
701                 term.addInputListener(input_listener);
702             }
703             term.setClickToType(true);
704             term.setAutoCopy( false );
705             term.setScrollOnOutput( false );
706         if (tab != null)
707         term.getAccessibleContext().setAccessibleName(tab.getName());
708
709             writer = new TermOutputWriter (term);
710
711             term.setWordDelineator(new WordDelineator() {
712                 public int charClass(char c) {
713                     if (Character.isJavaIdentifierPart(c))
714                         return 1;
715                     else
716                         return 0;
717                 }
718             } );
719
720             setLayout(new BorderLayout());
721             add( term );
722
723
724             term.setActionListener(new ActiveTermListener() {
725                     public void action(ActiveRegion r, InputEvent e) {
726                         if ( r.end.equals( new Coord() ) ) {
727                 // dangling tail region(?)
728
return;
729             }
730
731             if ( e instanceof MouseEvent ) {
732                 // We either hit the inner or outer region.
733
// Favor picking the outer region since that's what
734
// we're working with.
735

736                 if (r.parent() == term.regionManager().root()) {
737                 // outer region, keep it
738
} else {
739                 r = r.parent();
740                 }
741
742                 gotoHyperlink(r);
743                 activateHyperlink(true);
744             }
745             }
746         } );
747
748         term.setKeyStrokeSet(getCommonKeyStrokeSet());
749             //XXX fix alt keys here
750

751             term.getCanvas().addMouseListener(new MouseUtils.PopupMouseAdapter() {
752                 public void showPopup(MouseEvent mevt) {
753                     if (jPopup == null) {
754                         createPopupMenu();
755                     }
756                     //Issue 39947, ensure find action in correct state
757
updateCopyCutAction();
758                     
759                     jPopup.show(term, mevt.getX(), mevt.getY());
760                 }
761             });
762
763             term.addPropertyChangeListener (new PropertyChangeListener JavaDoc() {
764                 public void propertyChange(PropertyChangeEvent JavaDoc evt) {
765                     if ( "selectionExtent".equals( evt.getPropertyName() )) {
766                         updateCopyCutAction();
767                     }
768                 }
769             });
770
771             //XXX do this with a normal Swing InputMap/ActionMap:
772
term.getCanvas().addKeyListener( new KeyAdapter() {
773
774                 public void keyPressed(KeyEvent e) {
775
776                     switch (e.getKeyCode()) {
777                         case KeyEvent.VK_ENTER:
778                         case KeyEvent.VK_SPACE:
779                 // Consume it first because link activation
780
// may shift focus and we don't want the
781
// event to be delivered elsewhere.
782
// Except that it doesn't seem to help
783
e.consume();
784                 activateHyperlink(true);
785                             break;
786
787                         case KeyEvent.VK_T:
788                             if (e.getModifiers() == (Event.CTRL_MASK |
789                              Event.SHIFT_MASK) ) {
790                 // Shift-Ctrl-T previous error
791
prevHyperlink();
792                 e.consume();
793                 } else if (e.getModifiers() == Event.CTRL_MASK) {
794                 // Ctrl-T next error
795
linkOnStart = false;
796                 nextHyperlink();
797                 e.consume();
798                 }
799                 break;
800
801                         case KeyEvent.VK_A:
802                             if ( e.getModifiers() == Event.CTRL_MASK ) {
803                                 selectAll();
804                             }
805                             break;
806                         case KeyEvent.VK_F3:
807                             if ( e.getModifiers() == 0 ) {
808                                 findNextPattern();
809                             }
810                             break;
811                     }
812                 }
813
814             });
815
816             setSettings ();
817             getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
818                 KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK),
819                 "popup"); //NOI18N
820
getActionMap().put("popup", new PopupAction());
821         }
822         
823         public void requestFocus() {
824             super.requestFocus();
825             term.requestFocus();
826             activateLater();
827         }
828         
829         public boolean requestFocusInWindow() {
830             super.requestFocusInWindow();
831             boolean result = term.requestFocusInWindow();
832             if (result) {
833                 activateLater();
834             }
835             return result;
836         }
837         
838         /** Calls activated() via SwingUtilities.invokeLater(). Necessary to do
839          * this from requestFocus(), or the action performers will be reset by the
840          * winsys after we set them. */

841         private void activateLater() {
842             SwingUtilities.invokeLater (new Runnable JavaDoc() {
843                 public void run() {
844                     //Make sure we really got focus
845
Component JavaDoc c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
846                     if (c == OutTermPane.this || OutTermPane.this.isAncestorOf(c)) {
847                         activated();
848                     }
849                 }
850             });
851         }
852         
853     
854         private class PopupAction extends AbstractAction JavaDoc {
855             public void actionPerformed (ActionEvent ae) {
856                 if (jPopup == null) {
857                     createPopupMenu();
858                 }
859                 //Issue 39947, ensure find action in correct state
860
updateCopyCutAction();
861                 jPopup.show((JComponent JavaDoc) getTerm(), 0, 0);
862             }
863         }
864
865         public Term getTerm() {
866             return term;
867         }
868         
869         private boolean isFindNextEnabled() {
870             return findNextEnabled;
871         }
872         
873         private void setFindNextEnabled(boolean val) {
874             findNextEnabled = val;
875             if (findNextItem != null) {
876                 findNextItem.setEnabled (val);
877             }
878         }
879
880         /** The writer for this pane.
881         */

882         public OutputWriter getOut () {
883             return writer;
884         }
885         
886         private void createPopupMenu() {
887             jPopup = SystemAction.createPopupMenu( new SystemAction[] {copy, find} );
888
889             if ( tab == null )
890                 // no redirection for notify exception
891
redirection = false;
892             else
893                 redirection = outputSettings().isRedirection();
894
895             findNextItem = new JMenuItem JavaDoc(NbBundle.getBundle(OutTermPane.class).getString("CTL_FindNext"));
896             findNextItem.addActionListener(this);
897             findNextItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_F3, 0 ) );
898             findNextItem.setEnabled (isFindNextEnabled());
899             jPopup.add(findNextItem);
900
901             selectAllItem = new JMenuItem JavaDoc(NbBundle.getBundle(OutTermPane.class).getString("CTL_SelectAll"));
902             selectAllItem.addActionListener(this);
903             selectAllItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_A, Event.CTRL_MASK ) );
904             jPopup.add(selectAllItem);
905             jPopup.addSeparator();
906             // add clear
907
clearItem = new JMenuItem JavaDoc(NbBundle.getBundle(OutTermPane.class).getString("CTL_Clear"));
908             clearItem.addActionListener(this);
909             jPopup.add(clearItem);
910             // add redirection
911
redirItem = new JMenuItem JavaDoc();
912             redirItem.addActionListener(this);
913             redirItem.setToolTipText(NbBundle.getBundle(OutputTabInner.class).getString("HINT_Redirect_Tab"));
914             if ( tab != null ) {
915                 checkRedirItem();
916                 jPopup.addSeparator();
917                 jPopup.add(redirItem);
918             }
919             jPopup.addSeparator();
920             //Add Discard
921
discardItem = new JMenuItem JavaDoc(NbBundle.getBundle(OutTermPane.class).getString("LBL_Discard"));
922             discardItem.addActionListener(this);
923             jPopup.add(discardItem);
924             //Add DiscardAll
925
discardAllItem = new JMenuItem JavaDoc(NbBundle.getBundle(OutTermPane.class).getString("LBL_DiscardAll"));
926             discardAllItem.addActionListener(this);
927             jPopup.add(discardAllItem);
928         }
929
930         void updateNextPrevActions() {
931
932         // Much simpler than before since now we have wrapping. We only
933
// need to enable the actions if we have any hyperlinks.
934

935         if (firstHyperlink() != null) {
936         nextAction.setActionPerformer(jumpPerformer);
937         previousAction.setActionPerformer(jumpPerformer);
938         } else {
939                 //Fix issue 34908, don't set performer to null if we don't own
940
//the performer - tasklist or some other module may own it
941
if (nextAction.getActionPerformer() == jumpPerformer) {
942                     nextAction.setActionPerformer(null);
943                 }
944                 if (previousAction.getActionPerformer() == jumpPerformer) {
945                     previousAction.setActionPerformer(null);
946                 }
947         }
948         }
949
950         boolean updateCopyCutAction () {
951             cut.setActionPerformer( null );
952             delete.setActionPerformer( null );
953             find.setActionPerformer( new FindActionPerformer() );
954             boolean ret;
955             if ( term.getSelectedText() != null && term.getSelectedText().length() > 0 ) {
956                 copy.setActionPerformer( getCopyActionPerformer() );
957                 ret = true;
958             }
959             else {
960                 copy.setActionPerformer( null );
961                 ret = false;
962             }
963             updatePasteAction();
964             return ret;
965         }
966         
967         private class FindActionPerformer implements ActionPerformer {
968             public void performAction(final SystemAction action) {
969                 //XXX Will this code ever be called from off the event thread???
970
invokeLater(new Runnable JavaDoc() {
971                     public void run() {
972                         FindDialogPanel findDialog = FindDialogPanel.showFindDialog();
973                         if ( findDialog.isAccepted() ) {
974                             String JavaDoc pattern = findDialog.getPattern();
975                             if ( pattern != null && pattern.length() > 0 )
976                                 findPattern(
977                                     pattern,
978                                     ! findDialog.isBackwardSearch(),
979                                     findDialog.isMatchCase(),
980                                     findDialog.isWholeWordsOnly()
981                                 );
982                         }
983                     }
984                 });
985             }
986         }
987         
988         /** Find and select pattern in output window, which meets find criteria
989          */

990         private void findPattern(String JavaDoc pattern, boolean forward, boolean matchcase, boolean wholewords) {
991             int lastFindRow = 0;
992             int lastFindCol = 0;
993             boolean doClearSel = false;
994             if ( term.getSelectionExtent() != null ) {
995                 doClearSel = true;
996                 lastFindRow = term.getSelectionExtent().begin.row;
997                 lastFindCol = term.getSelectionExtent().begin.col;
998             }
999             else if ( ! forward ) {
1000                lastFindRow = term.getCursorCoord().row;
1001                lastFindCol = term.getCursorCoord().col;
1002            }
1003            int row = lastFindRow;
1004            int bcol;
1005            int ecol;
1006            int found = -1;
1007            int n = term.getCursorRow();
1008            while ( found == -1 && row <= n && row >= 0) {
1009                String JavaDoc s = term.getRowText( row );
1010                if ( row == lastFindRow ) {
1011                    if ( forward ) {
1012                        ecol = s.length();
1013                        if ( lastFindCol + 1 < ecol )
1014                            bcol = lastFindCol + 1;
1015                        else
1016                            bcol = ecol;
1017                    }
1018                    else {
1019                        bcol = 0;
1020                        if ( lastFindCol + pattern.length() - 1 < s.length() )
1021                            ecol = lastFindCol + pattern.length() - 1;
1022                        else
1023                            ecol = 0; // NOI18N
1024
}
1025                }
1026                else {
1027                    bcol = 0;
1028                    ecol = s.length();
1029                }
1030                if ( s.length() > 0 )
1031                    found = nextIndexOf( s, bcol, ecol, pattern, forward, matchcase, wholewords );
1032                if ( found == -1 ) {
1033                    if ( forward )
1034                        row ++;
1035                    else
1036                        row --;
1037                }
1038            }
1039            if ( found > -1 ) {
1040                // select found pattern
1041
Coord beginC = Coord.make( row, found );
1042                Extent selExt = new Extent( beginC, Coord.make( row, found + pattern.length() - 1 ) );
1043                term.setSelectionExtent( selExt );
1044                term.possiblyNormalize( beginC );
1045                setFindNextEnabled(true);
1046            }
1047            else if ( doClearSel )
1048                    term.clearSelection();
1049        }
1050        
1051        /** Find next pattern (if it was already defined) using previous find criteria
1052         */

1053        private void findNextPattern() {
1054            String JavaDoc pattern = FindDialogPanel.getPattern();
1055            if ( pattern != null && pattern.length() > 0 )
1056                findPattern(
1057                    pattern,
1058                    ! FindDialogPanel.isBackwardSearch(),
1059                    FindDialogPanel.isMatchCase(),
1060                    FindDialogPanel.isWholeWordsOnly()
1061                );
1062        }
1063        
1064        /** Returns next index of pattern in line (between bcol and ecol columns)
1065         * meeting find criteria
1066         */

1067        private int nextIndexOf(String JavaDoc line, int bcol, int ecol, String JavaDoc pattern, boolean forward,
1068                                boolean matchcase, boolean wholewords) {
1069            int found = -1;
1070            if ( ! matchcase ) {
1071                pattern = pattern.toLowerCase();
1072                line = line.toLowerCase();
1073            }
1074            int len = pattern.length();
1075            while ( found == -1 && bcol < ecol ) {
1076                if ( forward )
1077                    found = line.substring( bcol, ecol ).indexOf( pattern );
1078                else
1079                    found = line.substring( bcol, ecol ).lastIndexOf( pattern );
1080                if ( found > -1 ) {
1081                    found = found + bcol;
1082                    if ( wholewords ) {
1083                        if ( ( found > 0 && Character.isUnicodeIdentifierPart( line.charAt( found - 1 ) ) ) ||
1084                            ( found + len < line.length()
1085                                && Character.isUnicodeIdentifierPart( line.charAt( found + len ) ) ) )
1086                        {
1087                            if ( forward ) {
1088                                bcol = found + len;
1089                            }
1090                            else
1091                                ecol = found;
1092                            found = -1;
1093                        }
1094                    }
1095                }
1096                else
1097                    break;
1098            }
1099                
1100            return found;
1101        }
1102        
1103        private void updatePasteAction () {
1104            if ( term.isReadOnly() ) {
1105                paste.setPasteTypes (null);
1106                return;
1107            }
1108
1109            Clipboard clipboard = getClipboard();
1110            Transferable contents = clipboard.getContents( this );
1111            if (contents == null) {
1112                paste.setPasteTypes (null);
1113                return;
1114            }
1115
1116            if (!contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
1117                paste.setPasteTypes (null);
1118                return;
1119            }
1120
1121            paste.setPasteTypes( new PasteType[] {
1122                new PasteType() {
1123                    public Transferable paste() throws IOException JavaDoc {
1124                        term.paste();
1125                        return null;
1126                    }
1127                }
1128            });
1129        }
1130
1131        private ActionPerformer getCopyActionPerformer() {
1132            if ( copyActionPerformer == null )
1133                copyActionPerformer = new CopyActionPerformer( term );
1134            return copyActionPerformer;
1135        }
1136        
1137        private static Clipboard getClipboard() {
1138            Clipboard c = (java.awt.datatransfer.Clipboard JavaDoc)
1139                org.openide.util.Lookup.getDefault().lookup(java.awt.datatransfer.Clipboard JavaDoc.class);
1140            if (c == null) {
1141                c = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
1142            }
1143            return c;
1144        }
1145
1146
1147
1148
1149
1150    // We use terminalemulator.ActiveTerm's ActiveRegions to simulate
1151
// hyperlinks, but not in the obvious way.
1152
//
1153
// For accessibility purposes we separate the currently navigated to
1154
// hyperlink from the activated hyperlink. Navigation between links
1155
// happens using Ctrl-T and Shift-Ctrl-T, Activation happens using
1156
// Space,Enter,Return. Navigation wraps!
1157
// When we click on a hyperlink or use next prev actions the
1158
// navigation and activation are combined.
1159
//
1160
// Traditionally OW hilited the "whole" error message. This is done
1161
// when the hyperlink is activated. (There is the issue with OWs
1162
// detecting the whole error being rather ad-hoc but that's for elsewhere).
1163
// To implement this the chain of ActiveRegions is two-level so getting
1164
// the hyperlink is a bit messy. Here's what we get:
1165
//
1166
// Some random text
1167
// +-= SyntaxError.java [14:1] invalid method declaration; return ...
1168
// | public SyntaxErrorr() {
1169
// +-- ^
1170
// More random text
1171
//
1172
// We see the beginning of an error and create an "outer" region to wrap
1173
// the whole message. This outer region doesn't appear as a hyperlink
1174
// and only gets hilited. We also create an "inner" region that gets
1175
// rendered as a hyperlink. This line is marked with '=' above.
1176
//
1177
// Now that we've defined "outer" and "inner", 'currentHyperlink' and
1178
// 'activeHyperlink' point to the "outer" ActiveRegion!
1179

1180    private void activateHyperlink(boolean doAction) {
1181        if (currentHyperlink != null) {
1182        invokeJumpListener(currentHyperlink.begin, doAction );
1183
1184        // erase old stuff
1185
if ( activeHyperlink != null ) {
1186            int brow = activeHyperlink.begin.row;
1187            int erow = activeHyperlink.end.row;
1188            for (int r = brow; r <= erow; r++)
1189            term.setRowGlyph(r, 0, 0);
1190        }
1191
1192        activeHyperlink = currentHyperlink;
1193
1194        // draw new stuff
1195
if ( activeHyperlink != null ) {
1196            int brow = activeHyperlink.begin.row;
1197            int erow = activeHyperlink.end.row;
1198            for (int r = brow; r <= erow; r++)
1199            term.setRowGlyph(r, 0, 58+1); // first custom color
1200
term.flush();
1201        }
1202        }
1203    }
1204
1205    private ActiveRegion firstHyperlink() {
1206        ActiveRegion ar = term.regionManager().root();
1207        if (ar != null)
1208        ar = ar.firstChild();
1209        return ar;
1210    }
1211
1212    private ActiveRegion lastHyperlink() {
1213        ActiveRegion ar = term.regionManager().root();
1214        if (ar != null)
1215        ar = ar.lastChild();
1216        return ar;
1217    }
1218
1219    private boolean next_wrap_warned = false;
1220    private boolean prev_wrap_warned = false;
1221        private boolean linkOnStart = false;
1222
1223        private boolean nextHyperlink() {
1224            return nextHyperlink( false );
1225        }
1226        
1227    private boolean nextHyperlink(boolean forAction) {
1228        ActiveRegion ar = currentHyperlink;
1229        if (ar == null) {
1230        ar = firstHyperlink();
1231            } else if ( forAction && linkOnStart ) {
1232                ar = firstHyperlink();
1233        } else {
1234        prev_wrap_warned = false;
1235        ar = ar.getNextSibling();
1236        if (ar == null) {
1237            if (next_wrap_warned) {
1238            next_wrap_warned = false;
1239            ar = firstHyperlink();
1240            } else {
1241            String JavaDoc msg = NbBundle.getBundle(OutputTabInner.class).
1242                getString("MSG_AtLastError");
1243            org.openide.awt.StatusDisplayer.getDefault().setStatusText(msg);
1244            next_wrap_warned = true;
1245            return false;
1246            }
1247        }
1248        }
1249        gotoHyperlink(ar);
1250        return true;
1251    }
1252
1253    private boolean prevHyperlink() {
1254        ActiveRegion ar = currentHyperlink;
1255        if (ar == null) {
1256        ar = lastHyperlink();
1257        } else {
1258        next_wrap_warned = false;
1259        ar = ar.getPreviousSibling();
1260        if (ar == null) {
1261            if (prev_wrap_warned) {
1262            prev_wrap_warned = false;
1263            ar = lastHyperlink();
1264            } else {
1265            String JavaDoc msg = NbBundle.getBundle(OutputTabInner.class).
1266                getString("MSG_AtFirstError");
1267            org.openide.awt.StatusDisplayer.getDefault().setStatusText(msg);
1268            prev_wrap_warned = true;
1269            return false;
1270            }
1271        }
1272        }
1273        gotoHyperlink(ar);
1274        return true;
1275    }
1276
1277    private void gotoHyperlink(ActiveRegion ar) {
1278        if (ar == null)
1279        return;
1280
1281        // Find the "inner" portion
1282
ActiveRegion link = ar.firstChild();
1283        if (link == null) {
1284        // We have a one-level region
1285
link = ar;
1286        }
1287
1288        currentHyperlink = ar;
1289        term.setSelectionExtent(link.getExtent());
1290        term.possiblyNormalize(ar);
1291    }
1292
1293
1294        private void invokeJumpListener( Coord index, boolean doAction ) {
1295            //debug ("invokeJumpListener:doAction:" + doAction + ":index:" + index); // NOI18N
1296

1297        /* DEBUG
1298        System.out.println("invokeJumpListener() " + index + " " + doAction); // NOI18N
1299        */

1300
1301        // This can be improved by registering the listener with
1302
// the region instead of an independent map.
1303

1304            OutputListener olistener = (OutputListener)listeners.get(index);
1305            String JavaDoc str = term.textWithin(currentHyperlink.begin,
1306                     currentHyperlink.end);
1307            linkOnStart = false;
1308            if (olistener != null) {
1309                if ( doAction )
1310                    olistener.outputLineAction( new OutputEventImpl(InputOutput.NULL, str ));
1311                else
1312                    olistener.outputLineSelected( new OutputEventImpl(InputOutput.NULL, str ));
1313            }
1314        }
1315
1316
1317        private static final class OutputEventImpl extends OutputEvent {
1318            private String JavaDoc txt;
1319
1320            static final long serialVersionUID =-437312125483471519L;
1321
1322            public OutputEventImpl(InputOutput src, String JavaDoc txt) {
1323                super(src);
1324                this.txt = txt;
1325            }
1326
1327            /** Returns text on the line.
1328            * @return the text on the line
1329            */

1330            public String JavaDoc getLine () {
1331                //return model.getElementAt(index).toString ();
1332
return txt; // NOI18N
1333
}
1334        }
1335
1336
1337        /** Writer, which can insert text into the output document.
1338        * @see java.io.Writer
1339        */

1340        class TermOutputWriter extends OutputWriter {
1341            ActiveTerm aterm = null;
1342            boolean timerSet = false;
1343            Boolean JavaDoc timerMode = null;
1344            boolean redirOpened = false;
1345            FileWriter JavaDoc redirWriter = null;
1346            private ActiveRegion region = null;
1347
1348            TermOutputWriter(ActiveTerm term) {
1349                super(new OutputStreamWriter JavaDoc(new NullOutputStream()));
1350                aterm = term;
1351                setPageMode (false);
1352        aterm.setRefreshEnabled(false);
1353                if ( tab != null )
1354                    redirection = outputSettings ().isRedirection();
1355                checkRedirItem();
1356                if ( redirection )
1357                    redirOpen();
1358            }
1359
1360            /** ---------------- OutputWriter methods ------------------------------*/
1361            public void reset() throws java.io.IOException JavaDoc {
1362                //debug ("reset");
1363
invokeNow(new Runnable JavaDoc() {
1364            public void run() {
1365            doClear();
1366            aterm.setRefreshEnabled(false);
1367            resetRegion ();
1368            prev_wrap_warned = false;
1369            next_wrap_warned = false;
1370            }
1371        });
1372            }
1373
1374            public void println(String JavaDoc str, final OutputListener outputListener) throws java.io.IOException JavaDoc {
1375        final String JavaDoc strCopy = str;
1376
1377                //debug ("println:str: " + str + ":" + outputListener);
1378
ensureOpen ();
1379        invokeNow(new Runnable JavaDoc() {
1380            public void run() {
1381            historySizeKeeper ();
1382
1383            if ( outputListener != null ) {
1384
1385                // This code is fraught with various assumptions about
1386
// the ordering and semantics of of output from the compiler.
1387
// It expects that the compiler sends stuff in two println's
1388
// where the first one may be a "hyperlink" and the second
1389
// one may not be. If we get any other ordering we'll get
1390
// poorly formed regions.
1391
// On this basis beginRegion() and endRegion() should really
1392
// be called something like firstHalf() and secondHalf().
1393

1394                            /* Dead, but may be of historical interest:
1395                if (isFromCompiler (outputListener)) {
1396                if (isHyperLink (outputListener)) {
1397                    firstHalf(strCopy);
1398                    registerListener(region ,outputListener);
1399                } else {
1400                    secondHalf(strCopy);
1401                }
1402                } else {
1403                             */

1404                            createRegion(strCopy, true);
1405                            registerListener(region ,outputListener);
1406            } else
1407                appendText(strCopy,false);
1408
1409            printEOL ();
1410            }
1411        });
1412            }
1413
1414            public void println() {
1415                //debug ("println");
1416
ensureOpen ();
1417        invokeNow(new Runnable JavaDoc() {
1418            public void run() {
1419            printEOL ();
1420            }
1421        });
1422        }
1423
1424            public void write(char cbuf[], final int off, final int len) {
1425                //debug ("WRITE:cbuf: " + new String(cbuf,off,len));
1426
ensureOpen ();
1427        final char cbufCopy[] = safe_way? new char[cbuf.length]: cbuf;
1428        if (safe_way)
1429            System.arraycopy(cbuf, 0, cbufCopy, 0, cbuf.length);
1430        invokeNow(new Runnable JavaDoc() {
1431            public void run() {
1432            appendText(new String JavaDoc(cbufCopy,off,len), !isTimerMode ());
1433            }
1434        });
1435            }
1436
1437            public void write(final int ch) {
1438                //debug ("write:ch:" + (char)ch + ":int: " + ch);
1439
invokeNow(new Runnable JavaDoc() {
1440            public void run() {
1441            write ( new char[]{(char)ch}, 0, 1);
1442            }
1443        });
1444            }
1445
1446            public void write(String JavaDoc str, int off, final int len) {
1447                //debug ("write:str: " + str);
1448
final char[] chars = new char [len];
1449        str.getChars(off,off+len,chars,0);
1450        invokeNow(new Runnable JavaDoc() {
1451            public void run() {
1452            write(chars,0,len);
1453            }
1454        });
1455            }
1456
1457            public void flush() {
1458                //debug ("flush");
1459
invokeNow(new Runnable JavaDoc() {
1460            public void run() {
1461            aterm.flush();
1462            if ( redirWriter != null )
1463                try {
1464                redirWriter.flush();
1465                } catch (IOException JavaDoc e) {
1466                // XXX really ignore?
1467
}
1468            }
1469        });
1470            }
1471
1472            public void close() {
1473                //debug ("close");
1474
invokeNow(new Runnable JavaDoc() {
1475            public void run() {
1476            if ( redirOpened )
1477                redirClose();
1478            }
1479        });
1480            }
1481
1482            /** ----------------- Private helper methods -----------------*/
1483
1484            private void redirOpen() {
1485                File JavaDoc redirFile = null;
1486                try {
1487                    File JavaDoc redirDirFile = outputSettings ().getDirectory();
1488
1489                    String JavaDoc name = "";
1490                    if (tab != null) {
1491                        name = tab.getName();
1492                        int ispace = name.indexOf(' ');
1493                        if (ispace > 0)
1494                            name = name.substring( 0, ispace );
1495                    }
1496                    name = name.replace(File.separatorChar, '_');
1497                    name = name + REDIR_EXT;
1498                    redirFile = new File JavaDoc (redirDirFile, name);
1499
1500                    redirFile.createNewFile();
1501                    redirWriter = new FileWriter JavaDoc(redirFile.getAbsolutePath(),true);
1502
1503                    redirOpened = true;
1504                } catch (Exception JavaDoc e) {
1505                    ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Redir file: " + redirFile, null, null, null); // NOI18N
1506
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1507                }
1508            }
1509
1510            private void redirClose() {
1511                if ( redirWriter != null )
1512                    try {
1513                        redirWriter.close();
1514                        redirWriter = null;
1515                    } catch (IOException JavaDoc e) {
1516                        ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1517                    }
1518            }
1519
1520            private void redirPrint(char[] chars,int offs, int len) {
1521                if ( redirWriter == null )
1522                    redirOpen();
1523                if (redirWriter == null) return;
1524                try {
1525                    redirWriter.write(chars, offs, len);
1526                } catch (IOException JavaDoc e) {
1527                    ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1528                }
1529            }
1530
1531            private void redirPrint(String JavaDoc str) {
1532                if ( redirWriter == null )
1533                    redirOpen();
1534                if (redirWriter == null) return;
1535                try {
1536                    redirWriter.write(str);
1537                } catch (IOException JavaDoc e) {
1538                    ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1539                }
1540            }
1541
1542            /** If region is created then switched to PageMode. */
1543            private ActiveRegion createRegion(String JavaDoc str, boolean hyperlink) {
1544                beginRegion (str, hyperlink);
1545                return endRegion (null);
1546            }
1547
1548            private ActiveRegion beginRegion(String JavaDoc str, boolean hyperlink) {
1549                resetRegion ();
1550                setPageMode (true);
1551                Coord beginCoord = Coord.make(aterm.getCursorCoord().row, 0 );
1552
1553                try {
1554                    region = aterm.regionManager().beginRegion(beginCoord);
1555                } catch (RegionException x) {
1556                    return null;
1557                }
1558                // TRY THIS INSTEAD: region = term.beginRegion(false);
1559

1560                region.setFeedbackEnabled(false);
1561                //region.setFeedbackEnabled(true);
1562
region.setSelectable(false);
1563
1564                if (hyperlink) {
1565                    aterm.setAttribute( getLinkAttr() ); // fg -> blue
1566
aterm.setAttribute(4); // underline
1567
}
1568
1569                if (str != null)
1570                    appendText(str, false);
1571
1572                return region;
1573            }
1574
1575            private ActiveRegion endRegion(String JavaDoc str) {
1576                aterm.setAttribute(0); // reset
1577
if (str != null)
1578                    appendText(str, false);
1579
1580                aterm.endRegion();
1581
1582                updateNextPrevActions();
1583                return region;
1584            }
1585
1586            private void firstHalf(String JavaDoc str) {
1587                setPageMode (true);
1588                Coord beginCoord = Coord.make(aterm.getCursorCoord().row, 0 );
1589
1590        // Create beginning of region of whole error message
1591
try {
1592                    region = aterm.regionManager().beginRegion(beginCoord);
1593                } catch (RegionException x) {
1594                    return;
1595                }
1596
1597                region.setFeedbackEnabled(false);
1598                region.setSelectable(false);
1599
1600
1601        // Create region for hyperlink
1602
ActiveRegion link_region;
1603                try {
1604                    link_region = aterm.regionManager().beginRegion(beginCoord);
1605                } catch (RegionException x) {
1606                    return;
1607                }
1608
1609        aterm.setAttribute( getLinkAttr() ); // fg -> blue
1610
aterm.setAttribute(4); // underline
1611

1612        appendText(str, false);
1613
1614                aterm.setAttribute(0); // reset
1615

1616                aterm.endRegion();
1617
1618        // done with hyperlink region, the main region will be close
1619
// in secondHalf().
1620
}
1621
1622            private void secondHalf(String JavaDoc str) {
1623        appendText(str, false);
1624
1625                aterm.endRegion();
1626
1627                updateNextPrevActions();
1628            }
1629
1630            /**
1631         * Switches whether timer should be used as a way of optimalization.
1632             * Default ON (QA evaluated performance without timer as
1633         * insufficient). If this optimization needs to be excluded
1634             * then this method should return false. If problems occur or
1635             * you need to debug then switch it off.
1636             */

1637            private boolean isTimerMode () {
1638                if (timerMode == null) {
1639                    String JavaDoc tempStr = System.getProperty("org.netbeans.core.output.OutputTabInner.timerMode");
1640                    timerMode = (tempStr != null && tempStr.equalsIgnoreCase("false")) ? Boolean.FALSE : Boolean.TRUE; // NOI18N
1641
}
1642                /** Long enough to discourage everybody*/
1643                return timerMode.booleanValue();
1644            }
1645
1646            /** Optimization way: all chars putted or appended to terminaemulator
1647             * are requested not to cause repaint. Repaint is made using this
1648             * timer. If this way of optimization will be used is switchable and
1649             * depends if timer mode is switched see isTimerMode (). If not
1650             * smoothly enough then please decrease timer period or
1651             * switch timeMode off.
1652             */

1653            private void repaintTimer() {
1654                if ( ! timerSet ) {
1655                    timerSet = true;
1656                    org.openide.util.RequestProcessor.getDefault().post( new Runnable JavaDoc() {
1657                        public void run() {
1658                            flush();
1659                            timerSet = false;
1660                        }
1661                    },
1662                    120);
1663                }
1664            }
1665
1666            /** Page mode == mode of usage in terminalemulator.
1667             * Perpetually grows (danger of OutOfMemory), but may be reset.
1668             * If not Page mode then Interactive mode. Interactive mode keeps
1669             * historySize, so old Lines are removed from Buffer, but not reliable
1670             * coordinate operation, then not reliable work with regions.
1671             * In OW default Interactive mode. If any region request the
1672             * switched to PageMode. If in PageMode exceeded historySize
1673             * then reset. Not clever but unavoidable at the moment. Cannot be
1674             * reverted from PageMode to IneractiveMode.
1675             */

1676            private void setPageMode (boolean pageMode) {
1677                if (isPageMode ()) return;
1678                aterm.setAnchored (pageMode);
1679            }
1680
1681            private boolean isPageMode () {
1682                return aterm.isAnchored ();
1683            }
1684
1685            /**
1686             * Has meaning only in PageMode. If historySize is exceeded reset
1687             * is called
1688             */

1689            private void historySizeKeeper () {
1690                if (!isPageMode ()) return;
1691                /** Compiler output is about 100 lines. If anybody sets historySize
1692                 * smaller then 100 line. Then bad luck.*/

1693                if (aterm.getHistoryBuffSize () - aterm.getHistorySize () > 0) {
1694                    try {
1695                        reset ();
1696                    } catch (Exception JavaDoc exc) {
1697                        // XXX really ignore?
1698
}
1699                }
1700
1701            }
1702
1703            private void resetRegion () {
1704                if (region != null) {
1705                    aterm.endRegion();
1706                    region = null;
1707                }
1708            }
1709
1710            private void registerListener (ActiveRegion regRegion, OutputListener outpList) {
1711                if (regRegion != null && outpList != null)
1712                    listeners.put( regRegion.begin, outpList );
1713            }
1714
1715            private void printEOL () {
1716                appendText("\n",!isTimerMode ());//NOI18N
1717
}
1718
1719            /**
1720             * Mysterious method, probably a relic of obsolete stack trace
1721             * processing. Don't be afraid to kill it.
1722             */

1723            private void appendText (String JavaDoc str, boolean repaint) {
1724                if (isTimerMode ()) {
1725                    /** I don`t want to call term.appendText basically. But it
1726                     * is only one method that doesn`t call repaint. But has
1727                     * side effect: expands '\n' to "\n\r".
1728                     */

1729                    if ( redirection )
1730                        redirPrint(str);
1731
1732                    char[] cs = str.toCharArray();
1733                    aterm.putChars(cs, 0, cs.length);
1734                    repaintTimer();
1735                    return;
1736                }
1737
1738                char[] tmp = new char[str.length()];
1739                str.getChars(0, str.length(), tmp, 0);
1740
1741                appendChars(tmp,0, str.length(), repaint);
1742            }
1743
1744            private void appendChars(char[] chars,int offs, int len, boolean repaint) {
1745                if (isTimerMode ()) {
1746                    appendText (new String JavaDoc (chars, offs, len), repaint);
1747                    return;
1748                }
1749
1750                if ( redirection )
1751                    redirPrint(chars, offs, len);
1752
1753                if (!repaint && aterm.isRefreshEnabled()) {
1754                    /** Introduced in Ivan`s DirectTermOutputWriter: but I
1755                     * hasitate if has any sense because
1756                     * term.setRefreshEnabled(true) calls repaint then can be
1757                     * directly called term.putChars(chars, offs, len);
1758                     */

1759                    aterm.setRefreshEnabled(false);
1760                    aterm.putChars(chars, offs, len);
1761                    aterm.setRefreshEnabled(true);
1762                } else aterm.putChars(chars, offs, len);
1763            }
1764        }
1765
1766        final class JumpActionPerformer implements ActionPerformer {
1767
1768            /** Performer for actions */
1769            public void performAction(final SystemAction action) {
1770        invokeLater(new Runnable JavaDoc() {
1771            public void run() {
1772
1773            if (action instanceof NextOutJumpAction) {
1774                // Traditionally bound to F12
1775
if (nextHyperlink(true))
1776                activateHyperlink(true);
1777            }
1778
1779            if (action instanceof PreviousOutJumpAction) {
1780                // Traditionally bound to Shift-F12
1781
if (prevHyperlink())
1782                activateHyperlink(true);
1783            }
1784
1785            updateNextPrevActions();
1786            }
1787        });
1788            }
1789        }
1790
1791        static class CopyActionPerformer extends Object JavaDoc implements
1792            org.openide.util.actions.ActionPerformer {
1793
1794            ActiveTerm at;
1795
1796            public CopyActionPerformer (ActiveTerm at) {
1797                this.at = at;
1798            }
1799
1800            /** Perform copy or cut action. */
1801            public void performAction(org.openide.util.actions.SystemAction action) {
1802        invokeLater(new Runnable JavaDoc() {
1803            public void run() {
1804            at.copy();
1805            }
1806        });
1807            }
1808        }
1809
1810        private static class TIListener implements TermInputListener {
1811
1812            private OutputTabInner tab;
1813
1814            public TIListener(OutputTabInner tab) {
1815                this.tab = tab;
1816            }
1817
1818            public void sendChar(char c) {
1819                try {
1820                    tab.inWriter.write(c);
1821                    tab.inWriter.flush();
1822                } catch (Exception JavaDoc x) {
1823                    // XXX really ignore?
1824
}
1825            }
1826            public void sendChars(char[] c, int offset, int n) {
1827                try {
1828                    tab.inWriter.write(c, offset, n);
1829                    tab.inWriter.flush();
1830                } catch (Exception JavaDoc x) {
1831                    // XXX really ignore?
1832
}
1833            }
1834        }
1835
1836        /** Sets action performers. Called by OutputTabInner.componentActivated(). */
1837        public void activated() {
1838            updateCopyCutAction ();
1839            if (csa == null) {
1840                try {
1841                    // XXX what is wrong with SystemAction.get(PopupAction.class)?
1842
Class JavaDoc popup = Class.forName("org.openide.actions.PopupAction"); // NOI18N
1843
csa = (CallbackSystemAction) CallbackSystemAction.get(popup);
1844                } catch (ClassNotFoundException JavaDoc e) {
1845                    Error JavaDoc err = new NoClassDefFoundError JavaDoc();
1846                    ErrorManager.getDefault().annotate(err, e);
1847                    throw err;
1848                }
1849            }
1850            csa.setActionPerformer(this);
1851            setFindNextEnabled( FindDialogPanel.getPattern() != null );
1852        }
1853
1854        /** Unsets action performers. Called by OutputTabInner.componentDeactivated(). */
1855        public void deactivated() {
1856            if (csa != null && this.equals( csa.getActionPerformer() ) ) {
1857                csa.setActionPerformer(null);
1858            }
1859        }
1860
1861        public void focusGained(FocusEvent ev) {
1862            //Code moved to activated() called by OutputTabInner.componentActivated()
1863
}
1864
1865        public void focusLost(FocusEvent ev) {
1866            //Code moved to deactivated() called by OutputTabInner.componentDeactivated()
1867
}
1868
1869        /**
1870     * Performer for action which shows popup menu (Shift-F10)
1871     */

1872        public void performAction(SystemAction action) {
1873            if (! (action instanceof org.openide.actions.PopupAction) )
1874        return;
1875        Mutex.EVENT.readAccess( new Runnable JavaDoc() {
1876        public void run() {
1877            Coord xy = null;
1878
1879            if ( ! term.isReadOnly() && term.isCoordVisible(term.getCursorCoord()) ) {
1880            xy = term.getCursorCoord();
1881            }
1882            else if ( term.getSelectionExtent() != null ) {
1883            if ( term.isCoordVisible(term.getSelectionExtent().begin )) {
1884                xy = term.getSelectionExtent().begin;
1885            }
1886            else if ( term.isCoordVisible(term.getSelectionExtent().end )) {
1887                xy = term.getSelectionExtent().end;
1888            }
1889            }
1890
1891            if ( xy == null && currentHyperlink != null ) {
1892            if ( term.isCoordVisible(currentHyperlink.begin )) {
1893                xy = currentHyperlink.begin;
1894            }
1895            else if ( term.isCoordVisible(currentHyperlink.end )) {
1896                xy = currentHyperlink.end;
1897            }
1898            }
1899
1900            Point p = null;
1901            if ( xy == null )
1902            p = new Point( 0, 0 );
1903            else
1904            p = term.toPixel( xy );
1905
1906            if (p == null) {
1907            return;
1908            }
1909                    if (jPopup == null) {
1910                        createPopupMenu();
1911                    }
1912            jPopup.show(term, p.x, p.y);
1913           }
1914       });
1915        }
1916
1917        public void actionPerformed(java.awt.event.ActionEvent JavaDoc actionEvent) {
1918            if (actionEvent.getSource() == selectAllItem) {
1919                selectAll();
1920            }
1921            else if (actionEvent.getSource() == findNextItem) {
1922                findNextPattern();
1923            } else if (actionEvent.getSource() == clearItem) {
1924                doClear();
1925            } else if (actionEvent.getSource() == redirItem) {
1926                redirection = !redirection;
1927                checkRedirItem();
1928            } else if (actionEvent.getSource() == discardItem) {
1929                OutputView.findDefault().discardTab();
1930            } else if (actionEvent.getSource() == discardAllItem) {
1931                OutputView.findDefault().discardAllTabs();
1932            }
1933        }
1934
1935        void doClear() {
1936            term.clearHistory();
1937            term.clear();
1938        setHyperlinkNavigationEnabled(false);
1939            Iterator JavaDoc it = listeners.values().iterator();
1940            while (it.hasNext()) {
1941                OutputListener oli = (OutputListener)it.next();
1942                oli.outputLineCleared( new OutputEventImpl(InputOutput.NULL, null ));
1943            }
1944            listeners.clear();
1945            activeHyperlink = null;
1946        currentHyperlink = null;
1947            updateNextPrevActions();
1948        }
1949
1950        /** output settings */
1951        private static OutputSettings outputSettings () {
1952            return (OutputSettings)OutputSettings.findObject (OutputSettings.class, true);
1953        }
1954
1955
1956    private boolean hyperlinkNavigationEnabled = false;
1957
1958    private void setHyperlinkNavigationEnabled(boolean hlne) {
1959
1960        if (hyperlinkNavigationEnabled == hlne)
1961        return;
1962        hyperlinkNavigationEnabled = hlne;
1963
1964        // Switch between the keysets where Ctrl-T and Ctrl-Shift-T are
1965
// allowed and one where they aren't.
1966

1967        if (hlne) {
1968        term.setScrollOnInput(false);
1969        term.setKeyStrokeSet(getCommonKeyStrokeSet2());
1970        } else {
1971        term.setScrollOnInput(true);
1972        term.setKeyStrokeSet(getCommonKeyStrokeSet());
1973        }
1974
1975    }
1976
1977        private void checkRedirItem() {
1978            if ( tab == null )
1979                return;
1980            if ( !redirection && writer != null )
1981                ((TermOutputWriter)writer).redirClose();
1982            if ( redirItem == null )
1983                return;
1984            if ( redirection ) {
1985                if (outputSettings().checkRedirectionDirExists(outputSettings().getDirectory())) {
1986                    redirItem.setText(NbBundle.getBundle (OutputTabInner.class).getString ("CTL_Redirect_Off"));
1987                } else {
1988                    redirection = false;
1989                }
1990            } else {
1991                redirItem.setText(NbBundle.getBundle (OutputTabInner.class).getString ("CTL_Redirect_On"));
1992            }
1993        }
1994
1995        private void checkFont() {
1996            Font font = term.getFont();
1997            if (font != null &&
1998                font.isPlain() &&
1999                (font.getSize() == outputSettings ().getFontSize())) {
2000                return;
2001            } else {
2002                Font nf = new Font("Monospaced", java.awt.Font.PLAIN, outputSettings ().getFontSize()); // NOI18N
2003
term.setFont(nf);
2004            }
2005        }
2006        
2007        private void setSettings () {
2008            checkFont();
2009            term.setForeground( outputSettings ().getBaseForeground());
2010            term.setBackground( outputSettings ().getBaseBackground());
2011        term.setCustomColor(1, outputSettings().getJumpCursorBackground());
2012            term.setHistorySize( outputSettings().getHistorySize() );
2013            tabSize = outputSettings ().getTabSize();
2014            term.setTabSize( tabSize );
2015            term.setCustomColor( 0, outputSettings ().getJumpLinkForeground());
2016            term.setHighlightColor( outputSettings ().getSelectionBackground());
2017        }
2018
2019        public void propertyChange (PropertyChangeEvent JavaDoc evt) {
2020            if ( OutputSettings.PROP_REDIRECTION.equals( evt.getPropertyName() )) {
2021                if ( tab != null ) {
2022                    redirection = ((Boolean JavaDoc)evt.getNewValue()).booleanValue();
2023                    checkRedirItem();
2024                }
2025        }
2026            else if ( OutputSettings.PROP_DIRECTORY.equals( evt.getPropertyName() )) {
2027                if ( tab != null ) {
2028                    if ( redirection && writer != null ) {
2029                        ((TermOutputWriter)writer).redirClose();
2030                        if (outputSettings().getDirectory().exists()) {
2031                            ((TermOutputWriter)writer).redirOpen();
2032                        }
2033                    }
2034                }
2035        }
2036            else
2037                setSettings ();
2038        }
2039
2040        private void selectAll() {
2041            Extent allExt = new Extent( Coord.make(0,0), term.getCursorCoord() );
2042            term.setSelectionExtent( allExt );
2043        }
2044
2045        private void ensureOpen() {
2046            if (isShowing()) {
2047                return;
2048            }
2049            //Note, intentionally not using Mutex.EVENT.readAccess here. Since
2050
//this can be called for individual characters that are output,
2051
//avoiding any unnecessary overhead, however trivial
2052
if (!SwingUtilities.isEventDispatchThread()) {
2053                SwingUtilities.invokeLater(new Runnable JavaDoc() {
2054                    public void run() {
2055                        _ensureOpen();
2056                    }
2057                });
2058            } else {
2059                _ensureOpen();
2060            }
2061        }
2062        
2063        private void _ensureOpen() {
2064            if (tab != null) {
2065                tab.ensureOpen();
2066            }
2067        }
2068        
2069        private ArrayList JavaDoc runnables = new ArrayList JavaDoc();
2070        private boolean runningRunnables;
2071        private Object JavaDoc syncObject = new Object JavaDoc();
2072        
2073        /**
2074         * Utility to schedule stuff on the Event Dispatch thread.
2075         * <p>
2076         * The usual pattern is for every function foo() there be a fooImpl()
2077         * and call fooImpl() or wrap it in a Runnable. With this 'invokeNow'
2078         * we always wrap in a Runnable but I believe it's a small cost
2079         * since inmost cases (heavy character output) we're going through the
2080         * Runnable anyway.
2081         * <p>
2082         * For an explanation of why invokeAndWait() as opposed to invokeLater()
2083         * see .../terminalemulator/ReleaseNotes.ivan.txt under tag ivan_17.
2084         */

2085        private void invokeNow(final Runnable JavaDoc runnable) {
2086            if (SwingUtilities.isEventDispatchThread()) {
2087                runnable.run();
2088            } else {
2089                if (safe_way) {
2090                    synchronized ( syncObject ) {
2091                        if (runningRunnables) {
2092                            try {
2093                                syncObject.wait (500);
2094                            } catch (InterruptedException JavaDoc ex) {
2095                            }
2096                        }
2097                        
2098                        runnables.add( runnable );
2099                        if (runnables.size () > 50) {
2100                            try {
2101                                syncObject.wait (500);
2102                            } catch (InterruptedException JavaDoc ex) {
2103                            }
2104                        }
2105                        if ( runnables.size() == 1 )
2106                            SwingUtilities.invokeLater( this );
2107                    }
2108                }
2109                else {
2110                    try {
2111                        SwingUtilities.invokeAndWait(runnable);
2112                    } catch (Exception JavaDoc x) {
2113                        ;
2114                    }
2115                }
2116            }
2117        }
2118        
2119        public void run () {
2120            ArrayList JavaDoc torun;
2121            synchronized ( syncObject ) {
2122                runningRunnables = true;
2123                torun = (ArrayList JavaDoc)runnables.clone();
2124                runnables = new ArrayList JavaDoc();
2125            }
2126            Iterator JavaDoc it = torun.iterator();
2127            while ( it.hasNext() )
2128                ((Runnable JavaDoc)(it.next())).run();
2129            synchronized ( syncObject ) {
2130                runningRunnables = false;
2131                syncObject.notifyAll();
2132            }
2133        }
2134
2135        private int getLinkAttr() {
2136            // custom color 0 in term
2137
return 50;
2138        }
2139
2140        void setReadWrite( boolean rw ) {
2141            term.setCursorVisible ( rw );
2142            term.setReadOnly ( !rw );
2143        }
2144    }
2145
2146    final static private boolean safe_way = true;
2147
2148    
2149    
2150
2151    private static void invokeLater(Runnable JavaDoc runnable) {
2152    if (SwingUtilities.isEventDispatchThread()) {
2153        runnable.run();
2154    } else {
2155            SwingUtilities.invokeLater(runnable);
2156    }
2157    }
2158
2159    /* DEBUG
2160    private static void ckEventDispatchThread() {
2161    if (!SwingUtilities.isEventDispatchThread()) {
2162        System.out.println("OW: NOT IN EventDispatchThread");
2163        Thread.dumpStack();
2164    }
2165    }
2166    */

2167    
2168}
2169
Popular Tags