alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.core.output;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Reader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.FileWriter;
import java.io.File;

import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import javax.swing.AbstractAction;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Keymap;

import org.openide.ErrorManager;
import org.openide.windows.*;
import org.openide.awt.MouseUtils;
import org.openide.actions.CutAction;
import org.openide.actions.DeleteAction;
import org.openide.actions.PasteAction;
import org.openide.actions.CopyAction;
import org.openide.actions.FindAction;
import org.openide.util.io.NullOutputStream;
import org.openide.util.NbBundle;
import org.openide.util.Mutex;
import org.openide.util.datatransfer.*;
import org.openide.util.actions.ActionPerformer;
import org.openide.util.actions.SystemAction;
import org.openide.util.actions.CallbackSystemAction;

import org.netbeans.lib.terminalemulator.*;
import org.openide.util.Lookup;

/**
 * Modified OutputTabTerm to be used as inner component of OutputView
 * for new output window implementation. Prototype. If possible should be
 * merged with OutputTabTerm or common code should be separated. Or common
 * subclass could be created.
 *
 * @author  Marek Slama
 *
 */

public class OutputTabInner extends TopComponent
implements InputOutput, PropertyChangeListener {


    public static final String ICON_RESOURCE =
        "/org/netbeans/core/resources/frames/output.gif"; // NOI18N
    
    /** Should this InputOutput take the focus and bring the window to the front,
    * when anything is written into the stream. */
    private boolean focusTaken = false;

    /** The reader for standard input */
    private Reader inReader;
    /** piped writer */
    PipedWriter inWriter = new PipedWriter();
    
    /** If true, the error output is separated from the normal output */
    private boolean errSeparated = false;
    private Boolean inputVisible = null;
    private boolean errVisible = false;
    
    /** Splitpane used to display split stdout/stderr */
    private JSplitPane splitpane = null;

    /** singleton instance of the standard output tab */
    //private static OutputTabInner standard;
    
    private static final long serialVersionUID =3276412782250080205L;
    
    private OutTermPane output;
    /** Error pane */
    private OutTermPane error = null;
    
    //private static Factory factory;
    
    private boolean hideOnly = false;
    
    // #37711 Hack, to be able to call isClosed API method from any thread while
    // the windows API can be called from AWT thread only.
    /** Flag indicating whether it is opened in window system. */
    private boolean openedInWinSys;
    private final Object LOCK_OPENED_IN_WINSYS = new Object();
    
    OutputTabInner(final String name) {
        synchronized (OutputTabInner.class) {
            
            output = new OutTermPane(this);
            
            synchronized(OutputView.ioCache) {
                OutputView.ioCache.put(name, this);
            }
            
            Mutex.EVENT.readAccess(new Runnable() {
                public void run() {
                    output.setName("StdOut"); //NOI18N
                    // set accessible description
                    getAccessibleContext ().setAccessibleName (
                        NbBundle.getBundle (OutputTabInner.class).getString ("ACSN_OutputWindow"));
                    getAccessibleContext ().setAccessibleDescription (
                        NbBundle.getBundle (OutputTabInner.class).getString ("ACSD_OutputWindow"));
                    setBorder (null);
                    add (output);
                    setName (name);
                }
            });

            TopComponent.getRegistry().addPropertyChangeListener(
                org.openide.util.WeakListener.propertyChange(this, TopComponent.getRegistry()));
            
            getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK), 
                "discard"); //NOI18N
            
            getActionMap().put("discard", new DiscardAction());
        }        
    }
    
    private class DiscardAction extends AbstractAction {
        public void actionPerformed (ActionEvent ae) {
            OutputView view = (OutputView) SwingUtilities.getAncestorOfClass(
                OutputView.class, OutputTabInner.this);
            if (view != null) {
                view.discardTab(OutputTabInner.this);
            }
        }
    }

    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_ONLY_OPENED;
    }

    public void layout() {
        if (splitpane != null) {
            splitpane.setBounds (0, 0, getWidth(), getHeight());
        } else {
            output.setBounds (0, 0, getWidth(), getHeight());
        }
    }
    
    private synchronized OutTermPane getErrorTerm (boolean create) {
        if (error == null && create) {
            error = new OutTermPane (this);
            error.setName("StdErr"); //NOI18N
            Mutex.EVENT.readAccess(new SplitPaneInstaller());
        }
        return error;
    }
    
    private class SplitPaneInstaller implements Runnable {
        public void run() {
            splitpane = new JSplitPane();
            if (splitpane.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) {
                splitpane.setLeftComponent(error);
                splitpane.setRightComponent(output);
            } else {
                splitpane.setLeftComponent(output);
                splitpane.setRightComponent(error);
            }
            splitpane.setDividerLocation(getWidth() / 2);
            add (splitpane);
        }
    }
    
    /** Runnable which removes the split pane and restores the output to being
     * child of this component, to run on the event thread. */
    private class SplitPaneRemover implements Runnable {
        public void run() {
            //theoretically, one could munge the order of things enough for
            //the splitpane to be null, so check it
            if (splitpane != null) { 
                remove (splitpane);
            }
            error = null;
            add (output);
            Container parent = getParent ();
            if (parent != null) parent.validate ();
        }
    }    
    
    public synchronized Object writeReplace() throws java.io.ObjectStreamException {
        // bugfix #15703 - don't serialize tabs which you don't want to deseralize
        // later in Replace.readResolve, otherwise InstanceDataObject fails to
        // create instance of the tab
        if (!this.equals(OutputView.getFactory().getStdOutputTab())) {
            return null;
        }

        if (replace == null) {
            replace = new Replace(this.equals(OutputView.getFactory().getStdOutputTab()));
        }
        return replace;
    }
    
    // mainly for testing and debugging purposes
    // returns ActiveTerm for output OutTermPane
    public Term getTerm() {
        return getTerm( true );
    }
    
    // mainly for testing and debugging purposes
    // returns ActiveTerm for output or error OutTermPane
    public Term getTerm(boolean fromOutputPane) {
        if ( fromOutputPane ) {
            return output.getTerm();
        } else {
            return getErrorTerm(true).getTerm();
        }
    }
    
    /* Returns text content of output tab. This is
    * content of terminal buffer, which is limited by
    * history size.
    * 

* This function is no MT-safe call it from the AWT Event Dispatch thread. * * @return text content of output window buffer */ public String toString () { Term term = getTerm( true ); if ( term == null ) return ""; // NOI18N StringBuffer buf = new StringBuffer(); for (int i=0; i<=term.getCursorCoord().row; i++) { if ( i > 0 ) buf.append('\n'); buf.append( term.getRowText( i ) ); } return buf.toString(); } private Replace replace; /** This class is serializaed instead of OutputTabInner */ static class Replace implements java.io.Serializable { boolean defaultInstance; private static final long serialVersionUID =-3126744916624172415L; public Replace (boolean defaultInstance) { this.defaultInstance = defaultInstance; } /** Resolve as default singleton or null */ public Object readResolve() throws java.io.ObjectStreamException { if ( defaultInstance ) return OutputView.getFactory().getStdOutputTab(); else return null; } } void ensureOpen() { //issue 37810, this code must run in EQ //#37996 When called from EQ it must be synchronous to keep //code execution order. Mutex.EVENT.readAccess(new Runnable() { public void run() { if (isClosed()) { //debug("opening"); // NOI18N OutputView.findDefault().openInOV(OutputTabInner.this); } } }); } static String getOutDisplayName() { return NbBundle.getBundle(OutputTabInner.class).getString("CTL_OutputWindow"); } // // TopComponent methods // /** Activates the component */ protected void componentActivated () { output.activated(); } /** Deactivates the component */ protected void componentDeactivated () { output.deactivated(); } public void requestFocus () { super.requestFocus(); output.requestFocus(); } public boolean requestFocusInWindow () { super.requestFocusInWindow(); boolean result = output.requestFocusInWindow(); return result; } // // InputOutput methods // public boolean isFocusTaken() { return focusTaken; } /** Set true, if you want to focus out window, when anything is written * into the stream */ public void setFocusTaken(boolean value) { focusTaken = value; } private boolean firstOut=true; public org.openide.windows.OutputWriter getOut() { if (firstOut) { select(); firstOut = false; } return output.writer; } public void select() { /*System.out.println("== OutputTabInner.select ENTER" + " name:" + getName());*/ Mutex.EVENT.readAccess(new Runnable() { public void run() { ensureOpen(); OutputView.findDefault().requestVisible(OutputTabInner.this); } }); /*System.out.println("== OutputTabInner.select LEAVE 2" + " name:" + getName());*/ } public org.openide.windows.OutputWriter getErr() { //debug("getErr"); // NOI18N if (errSeparated) { return getErrorTerm(true).writer; } else return output.writer; } /** Method to acquire a Reader for accessing an input of the tab. * @return a Reader for accessing an input line of the tab. */ public java.io.Reader getIn() { //debug("getIn()"); // NOI18N // init inReader flushReader(); // only if input isn't explicitly forbidden if ( inputVisible == null || inputVisible.booleanValue() ) setInputVisible(true); return inReader; } public void setErrSeparated(boolean value) { if (errSeparated != value) { errSeparated = value; if (errVisible != errSeparated) { setErrVisible(errSeparated); } } } public java.io.Reader flushReader() { //debug("flushReader"); // NOI18N inWriter = new PipedWriter(); try { inReader = new PipedReader(inWriter); } catch (java.io.IOException ex) { ErrorManager.getDefault().notify(ex); return null; } return inReader; } public void setOutputVisible(boolean param) { //debug("setOutputVisible"); // NOI18N if (param) { Mutex.EVENT.readAccess(new Runnable() { public void run() { ensureOpen(); } }); } else { hideOnly = true; doClose(); } } public void setErrVisible(boolean value) { if (errSeparated || errVisible != value ) { errVisible = value; if (errVisible) { //create the error term - this will install //the splitpane for us getErrorTerm(true); } else { if (error != null) { Mutex.EVENT.readAccess(new SplitPaneRemover()); } } } } public boolean isClosed() { //debug("isClosed()"); // NOI18N // XXX boolean isInContainerTopComponent = false; Component p = getParent(); if(p instanceof javax.swing.JComponent) { javax.swing.JComponent parent = (javax.swing.JComponent)p; Object value = parent.getClientProperty("ContainerTopComponent"); // TEMP NOI18N if(value instanceof Boolean) { isInContainerTopComponent = ((Boolean)value).booleanValue(); } } return !isOpenedInWinSys() && !isInContainerTopComponent; } protected void componentOpened() { super.componentOpened(); setOpenedInWinSys(true); } protected void componentClosed() { super.componentClosed(); setOpenedInWinSys(false); } private void setOpenedInWinSys(boolean o) { synchronized(LOCK_OPENED_IN_WINSYS) { openedInWinSys = o; } } private boolean isOpenedInWinSys() { synchronized(LOCK_OPENED_IN_WINSYS) { return openedInWinSys; } } public void setInputVisible(boolean param) { //debug("setInputVisible:" + param); // NOI18N if (param) { Mutex.EVENT.readAccess(new Runnable() { public void run() { ensureOpen(); } }); } inputVisible = param ? Boolean.TRUE : Boolean.FALSE; output.setReadWrite( param ); } public boolean isErrSeparated() { //debug("isErrSeparated"); // NOI18N return errSeparated; } public void closeInputOutput() { //debug("closeInputOutput"); // NOI18N doClose(); try { inWriter.flush(); inWriter.close(); } catch (IOException ioe) { // do nothing in this case } catch (NullPointerException npe) { // only for sure - do nothing in this case } output.writer.close(); if (error != null) { error.writer.close(); } } private void doClose() { Mutex.EVENT.readAccess(new Runnable() { public void run() { if(isDisplayable()) { OutputView.findDefault().discardTab(OutputTabInner.this); } } }); } public void open () { OutputView.findDefault().openInOV(this); } // Replacement for TopComponentListener. Listen on opened components. public void propertyChange(PropertyChangeEvent evt) { if(TopComponent.Registry.PROP_OPENED.equals(evt.getPropertyName())) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); if(!hideOnly && oldValue instanceof Set && ((Set)oldValue).contains(this) // Was opened. && newValue instanceof Set && !((Set)newValue).contains(this)) { // Is not opened yet -> was closed. output.doClear(); if (error != null) { error.doClear(); } } hideOnly = false; } } private static OutputSettings outputSettings () { return (OutputSettings)OutputSettings.findObject (OutputSettings.class, true); } // share 'keyStrokeSet' among all Terms. // doubles as a one-time flag. private static HashSet keyStrokeSet = null; // see setHyperlinkNavigationEnabled() to see what's this all about private static HashSet keyStrokeSet2 = null; private static void updateKeyStrokeSet() { keyStrokeSet.clear(); keyStrokeSet.addAll( java.util.Arrays.asList( ((Keymap)Lookup.getDefault ().lookup ( Keymap.class ) ).getBoundKeyStrokes()) ); for (int ks = KeyEvent.VK_A; ks <= KeyEvent.VK_Z; ks++) keyStrokeSet.add( KeyStroke.getKeyStroke( ks, Event.ALT_MASK ) ); // Note that we have to have the keyChar be ASCII Ctrl-T for this // to work. KeyStroke ks1 = KeyStroke.getKeyStroke(new Character((char)('T'-64)), Event.CTRL_MASK|Event.SHIFT_MASK); KeyStroke ks2 = KeyStroke.getKeyStroke(new Character((char)('T'-64)), Event.CTRL_MASK); keyStrokeSet2 = (HashSet) keyStrokeSet.clone(); keyStrokeSet2.add(ks1); keyStrokeSet2.add(ks2); } private static HashSet getCommonKeyStrokeSet() { if (keyStrokeSet != null) return keyStrokeSet; // Initialize only once keyStrokeSet = new HashSet(); // Arrange so that we track changes in global keymap Keymap map = (Keymap)org.openide.util.Lookup.getDefault().lookup(Keymap.class); // This is a hack. Since there is no official API for notifying changes // from Keymap, just rely on fact that core impl is an Observable. if (map instanceof Observable) { Observable o = (Observable)map; o.addObserver(new Observer() { public void update(Observable o, Object arg) { updateKeyStrokeSet(); } }); } updateKeyStrokeSet(); return keyStrokeSet; } private static HashSet getCommonKeyStrokeSet2() { getCommonKeyStrokeSet(); return keyStrokeSet2; } public static final class OutTermPane extends JPanel implements ActionPerformer, ActionListener, PropertyChangeListener, Runnable { /** generated Serialized Version UID */ static final long serialVersionUID = -633812012958420549L; private static final String REDIR_EXT = ".out"; // NOI18N /** Information channel for this pane */ TermOutputWriter writer; /** My parent output tab */ OutputTabInner tab; private boolean findNextEnabled = false; /** Copy action */ private static CopyAction copy = (CopyAction)CopyAction.get (CopyAction.class); private static FindAction find = (FindAction)FindAction.get (FindAction.class); private static CutAction cut = (CutAction) CutAction.findObject(CutAction.class, true); private static DeleteAction delete = (DeleteAction) DeleteAction.findObject(DeleteAction.class, true); private static PasteAction paste = (PasteAction) PasteAction.findObject(PasteAction.class, true); /** Private instance of Next jump action */ private static NextOutJumpAction nextAction = (NextOutJumpAction)NextOutJumpAction.get (NextOutJumpAction.class); /** Private instance of Previous jump action */ private static PreviousOutJumpAction previousAction = (PreviousOutJumpAction)PreviousOutJumpAction.get (PreviousOutJumpAction.class); /** Performer for jump actions */ private JumpActionPerformer jumpPerformer = new JumpActionPerformer(); private ActionPerformer copyActionPerformer = null; CallbackSystemAction csa; private HashMap listeners = new HashMap(); /** PopupMenu */ JPopupMenu jPopup; private JMenuItem selectAllItem; private JMenuItem findNextItem; private JMenuItem clearItem; private JMenuItem redirItem; private JMenuItem discardItem; private JMenuItem discardAllItem; private TermListener listener; private TermInputListener input_listener; ActiveTerm term; private boolean redirection = false; private int tabSize; private ActiveRegion currentHyperlink = null; private ActiveRegion activeHyperlink = null; // was 'currentRegion' /** Creates pane without association to a tab. */ public OutTermPane() { this (null); } /** Creates new OutTermPane in the specific OutputTabInner */ public OutTermPane(OutputTabInner tab) { this.tab = tab; listener = new TermListener() { public void sizeChanged(Dimension cells, Dimension pixels) { //PENDING } }; term = new ActiveTerm (); updateNextPrevActions(); outputSettings ().addPropertyChangeListener (this); term.addListener(listener); setReadWrite( false ); term.setHistorySize( outputSettings().getHistorySize() ); term.pushStream(new LineDiscipline()); if ( tab != null ) { input_listener = new TIListener( tab ); term.addInputListener(input_listener); } term.setClickToType(true); term.setAutoCopy( false ); term.setScrollOnOutput( false ); if (tab != null) term.getAccessibleContext().setAccessibleName(tab.getName()); writer = new TermOutputWriter (term); term.setWordDelineator(new WordDelineator() { public int charClass(char c) { if (Character.isJavaIdentifierPart(c)) return 1; else return 0; } } ); setLayout(new BorderLayout()); add( term ); term.setActionListener(new ActiveTermListener() { public void action(ActiveRegion r, InputEvent e) { if ( r.end.equals( new Coord() ) ) { // dangling tail region(?) return; } if ( e instanceof MouseEvent ) { // We either hit the inner or outer region. // Favor picking the outer region since that's what // we're working with. if (r.parent() == term.regionManager().root()) { // outer region, keep it } else { r = r.parent(); } gotoHyperlink(r); activateHyperlink(true); } } } ); term.setKeyStrokeSet(getCommonKeyStrokeSet()); //XXX fix alt keys here term.getCanvas().addMouseListener(new MouseUtils.PopupMouseAdapter() { public void showPopup(MouseEvent mevt) { if (jPopup == null) { createPopupMenu(); } //Issue 39947, ensure find action in correct state updateCopyCutAction(); jPopup.show(term, mevt.getX(), mevt.getY()); } }); term.addPropertyChangeListener (new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if ( "selectionExtent".equals( evt.getPropertyName() )) { updateCopyCutAction(); } } }); //XXX do this with a normal Swing InputMap/ActionMap: term.getCanvas().addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_ENTER: case KeyEvent.VK_SPACE: // Consume it first because link activation // may shift focus and we don't want the // event to be delivered elsewhere. // Except that it doesn't seem to help e.consume(); activateHyperlink(true); break; case KeyEvent.VK_T: if (e.getModifiers() == (Event.CTRL_MASK | Event.SHIFT_MASK) ) { // Shift-Ctrl-T previous error prevHyperlink(); e.consume(); } else if (e.getModifiers() == Event.CTRL_MASK) { // Ctrl-T next error linkOnStart = false; nextHyperlink(); e.consume(); } break; case KeyEvent.VK_A: if ( e.getModifiers() == Event.CTRL_MASK ) { selectAll(); } break; case KeyEvent.VK_F3: if ( e.getModifiers() == 0 ) { findNextPattern(); } break; } } }); setSettings (); getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK), "popup"); //NOI18N getActionMap().put("popup", new PopupAction()); } public void requestFocus() { super.requestFocus(); term.requestFocus(); activateLater(); } public boolean requestFocusInWindow() { super.requestFocusInWindow(); boolean result = term.requestFocusInWindow(); if (result) { activateLater(); } return result; } /** Calls activated() via SwingUtilities.invokeLater(). Necessary to do * this from requestFocus(), or the action performers will be reset by the * winsys after we set them. */ private void activateLater() { SwingUtilities.invokeLater (new Runnable() { public void run() { //Make sure we really got focus Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); if (c == OutTermPane.this || OutTermPane.this.isAncestorOf(c)) { activated(); } } }); } private class PopupAction extends AbstractAction { public void actionPerformed (ActionEvent ae) { if (jPopup == null) { createPopupMenu(); } //Issue 39947, ensure find action in correct state updateCopyCutAction(); jPopup.show((JComponent) getTerm(), 0, 0); } } public Term getTerm() { return term; } private boolean isFindNextEnabled() { return findNextEnabled; } private void setFindNextEnabled(boolean val) { findNextEnabled = val; if (findNextItem != null) { findNextItem.setEnabled (val); } } /** The writer for this pane. */ public OutputWriter getOut () { return writer; } private void createPopupMenu() { jPopup = SystemAction.createPopupMenu( new SystemAction[] {copy, find} ); if ( tab == null ) // no redirection for notify exception redirection = false; else redirection = outputSettings().isRedirection(); findNextItem = new JMenuItem(NbBundle.getBundle(OutTermPane.class).getString("CTL_FindNext")); findNextItem.addActionListener(this); findNextItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_F3, 0 ) ); findNextItem.setEnabled (isFindNextEnabled()); jPopup.add(findNextItem); selectAllItem = new JMenuItem(NbBundle.getBundle(OutTermPane.class).getString("CTL_SelectAll")); selectAllItem.addActionListener(this); selectAllItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_A, Event.CTRL_MASK ) ); jPopup.add(selectAllItem); jPopup.addSeparator(); // add clear clearItem = new JMenuItem(NbBundle.getBundle(OutTermPane.class).getString("CTL_Clear")); clearItem.addActionListener(this); jPopup.add(clearItem); // add redirection redirItem = new JMenuItem(); redirItem.addActionListener(this); redirItem.setToolTipText(NbBundle.getBundle(OutputTabInner.class).getString("HINT_Redirect_Tab")); if ( tab != null ) { checkRedirItem(); jPopup.addSeparator(); jPopup.add(redirItem); } jPopup.addSeparator(); //Add Discard discardItem = new JMenuItem(NbBundle.getBundle(OutTermPane.class).getString("LBL_Discard")); discardItem.addActionListener(this); jPopup.add(discardItem); //Add DiscardAll discardAllItem = new JMenuItem(NbBundle.getBundle(OutTermPane.class).getString("LBL_DiscardAll")); discardAllItem.addActionListener(this); jPopup.add(discardAllItem); } void updateNextPrevActions() { // Much simpler than before since now we have wrapping. We only // need to enable the actions if we have any hyperlinks. if (firstHyperlink() != null) { nextAction.setActionPerformer(jumpPerformer); previousAction.setActionPerformer(jumpPerformer); } else { //Fix issue 34908, don't set performer to null if we don't own //the performer - tasklist or some other module may own it if (nextAction.getActionPerformer() == jumpPerformer) { nextAction.setActionPerformer(null); } if (previousAction.getActionPerformer() == jumpPerformer) { previousAction.setActionPerformer(null); } } } boolean updateCopyCutAction () { cut.setActionPerformer( null ); delete.setActionPerformer( null ); find.setActionPerformer( new FindActionPerformer() ); boolean ret; if ( term.getSelectedText() != null && term.getSelectedText().length() > 0 ) { copy.setActionPerformer( getCopyActionPerformer() ); ret = true; } else { copy.setActionPerformer( null ); ret = false; } updatePasteAction(); return ret; } private class FindActionPerformer implements ActionPerformer { public void performAction(final SystemAction action) { //XXX Will this code ever be called from off the event thread??? invokeLater(new Runnable() { public void run() { FindDialogPanel findDialog = FindDialogPanel.showFindDialog(); if ( findDialog.isAccepted() ) { String pattern = findDialog.getPattern(); if ( pattern != null && pattern.length() > 0 ) findPattern( pattern, ! findDialog.isBackwardSearch(), findDialog.isMatchCase(), findDialog.isWholeWordsOnly() ); } } }); } } /** Find and select pattern in output window, which meets find criteria */ private void findPattern(String pattern, boolean forward, boolean matchcase, boolean wholewords) { int lastFindRow = 0; int lastFindCol = 0; boolean doClearSel = false; if ( term.getSelectionExtent() != null ) { doClearSel = true; lastFindRow = term.getSelectionExtent().begin.row; lastFindCol = term.getSelectionExtent().begin.col; } else if ( ! forward ) { lastFindRow = term.getCursorCoord().row; lastFindCol = term.getCursorCoord().col; } int row = lastFindRow; int bcol; int ecol; int found = -1; int n = term.getCursorRow(); while ( found == -1 && row <= n && row >= 0) { String s = term.getRowText( row ); if ( row == lastFindRow ) { if ( forward ) { ecol = s.length(); if ( lastFindCol + 1 < ecol ) bcol = lastFindCol + 1; else bcol = ecol; } else { bcol = 0; if ( lastFindCol + pattern.length() - 1 < s.length() ) ecol = lastFindCol + pattern.length() - 1; else ecol = 0; // NOI18N } } else { bcol = 0; ecol = s.length(); } if ( s.length() > 0 ) found = nextIndexOf( s, bcol, ecol, pattern, forward, matchcase, wholewords ); if ( found == -1 ) { if ( forward ) row ++; else row --; } } if ( found > -1 ) { // select found pattern Coord beginC = Coord.make( row, found ); Extent selExt = new Extent( beginC, Coord.make( row, found + pattern.length() - 1 ) ); term.setSelectionExtent( selExt ); term.possiblyNormalize( beginC ); setFindNextEnabled(true); } else if ( doClearSel ) term.clearSelection(); } /** Find next pattern (if it was already defined) using previous find criteria */ private void findNextPattern() { String pattern = FindDialogPanel.getPattern(); if ( pattern != null && pattern.length() > 0 ) findPattern( pattern, ! FindDialogPanel.isBackwardSearch(), FindDialogPanel.isMatchCase(), FindDialogPanel.isWholeWordsOnly() ); } /** Returns next index of pattern in line (between bcol and ecol columns) * meeting find criteria */ private int nextIndexOf(String line, int bcol, int ecol, String pattern, boolean forward, boolean matchcase, boolean wholewords) { int found = -1; if ( ! matchcase ) { pattern = pattern.toLowerCase(); line = line.toLowerCase(); } int len = pattern.length(); while ( found == -1 && bcol < ecol ) { if ( forward ) found = line.substring( bcol, ecol ).indexOf( pattern ); else found = line.substring( bcol, ecol ).lastIndexOf( pattern ); if ( found > -1 ) { found = found + bcol; if ( wholewords ) { if ( ( found > 0 && Character.isUnicodeIdentifierPart( line.charAt( found - 1 ) ) ) || ( found + len < line.length() && Character.isUnicodeIdentifierPart( line.charAt( found + len ) ) ) ) { if ( forward ) { bcol = found + len; } else ecol = found; found = -1; } } } else break; } return found; } private void updatePasteAction () { if ( term.isReadOnly() ) { paste.setPasteTypes (null); return; } Clipboard clipboard = getClipboard(); Transferable contents = clipboard.getContents( this ); if (contents == null) { paste.setPasteTypes (null); return; } if (!contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { paste.setPasteTypes (null); return; } paste.setPasteTypes( new PasteType[] { new PasteType() { public Transferable paste() throws IOException { term.paste(); return null; } } }); } private ActionPerformer getCopyActionPerformer() { if ( copyActionPerformer == null ) copyActionPerformer = new CopyActionPerformer( term ); return copyActionPerformer; } private static Clipboard getClipboard() { Clipboard c = (java.awt.datatransfer.Clipboard) org.openide.util.Lookup.getDefault().lookup(java.awt.datatransfer.Clipboard.class); if (c == null) { c = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard(); } return c; } // We use terminalemulator.ActiveTerm's ActiveRegions to simulate // hyperlinks, but not in the obvious way. // // For accessibility purposes we separate the currently navigated to // hyperlink from the activated hyperlink. Navigation between links // happens using Ctrl-T and Shift-Ctrl-T, Activation happens using // Space,Enter,Return. Navigation wraps! // When we click on a hyperlink or use next prev actions the // navigation and activation are combined. // // Traditionally OW hilited the "whole" error message. This is done // when the hyperlink is activated. (There is the issue with OWs // detecting the whole error being rather ad-hoc but that's for elsewhere). // To implement this the chain of ActiveRegions is two-level so getting // the hyperlink is a bit messy. Here's what we get: // // Some random text // +-= SyntaxError.java [14:1] invalid method declaration; return ... // | public SyntaxErrorr() { // +-- ^ // More random text // // We see the beginning of an error and create an "outer" region to wrap // the whole message. This outer region doesn't appear as a hyperlink // and only gets hilited. We also create an "inner" region that gets // rendered as a hyperlink. This line is marked with '=' above. // // Now that we've defined "outer" and "inner", 'currentHyperlink' and // 'activeHyperlink' point to the "outer" ActiveRegion! private void activateHyperlink(boolean doAction) { if (currentHyperlink != null) { invokeJumpListener(currentHyperlink.begin, doAction ); // erase old stuff if ( activeHyperlink != null ) { int brow = activeHyperlink.begin.row; int erow = activeHyperlink.end.row; for (int r = brow; r <= erow; r++) term.setRowGlyph(r, 0, 0); } activeHyperlink = currentHyperlink; // draw new stuff if ( activeHyperlink != null ) { int brow = activeHyperlink.begin.row; int erow = activeHyperlink.end.row; for (int r = brow; r <= erow; r++) term.setRowGlyph(r, 0, 58+1); // first custom color term.flush(); } } } private ActiveRegion firstHyperlink() { ActiveRegion ar = term.regionManager().root(); if (ar != null) ar = ar.firstChild(); return ar; } private ActiveRegion lastHyperlink() { ActiveRegion ar = term.regionManager().root(); if (ar != null) ar = ar.lastChild(); return ar; } private boolean next_wrap_warned = false; private boolean prev_wrap_warned = false; private boolean linkOnStart = false; private boolean nextHyperlink() { return nextHyperlink( false ); } private boolean nextHyperlink(boolean forAction) { ActiveRegion ar = currentHyperlink; if (ar == null) { ar = firstHyperlink(); } else if ( forAction && linkOnStart ) { ar = firstHyperlink(); } else { prev_wrap_warned = false; ar = ar.getNextSibling(); if (ar == null) { if (next_wrap_warned) { next_wrap_warned = false; ar = firstHyperlink(); } else { String msg = NbBundle.getBundle(OutputTabInner.class). getString("MSG_AtLastError"); org.openide.awt.StatusDisplayer.getDefault().setStatusText(msg); next_wrap_warned = true; return false; } } } gotoHyperlink(ar); return true; } private boolean prevHyperlink() { ActiveRegion ar = currentHyperlink; if (ar == null) { ar = lastHyperlink(); } else { next_wrap_warned = false; ar = ar.getPreviousSibling(); if (ar == null) { if (prev_wrap_warned) { prev_wrap_warned = false; ar = lastHyperlink(); } else { String msg = NbBundle.getBundle(OutputTabInner.class). getString("MSG_AtFirstError"); org.openide.awt.StatusDisplayer.getDefault().setStatusText(msg); prev_wrap_warned = true; return false; } } } gotoHyperlink(ar); return true; } private void gotoHyperlink(ActiveRegion ar) { if (ar == null) return; // Find the "inner" portion ActiveRegion link = ar.firstChild(); if (link == null) { // We have a one-level region link = ar; } currentHyperlink = ar; term.setSelectionExtent(link.getExtent()); term.possiblyNormalize(ar); } private void invokeJumpListener( Coord index, boolean doAction ) { //debug ("invokeJumpListener:doAction:" + doAction + ":index:" + index); // NOI18N /* DEBUG System.out.println("invokeJumpListener() " + index + " " + doAction); // NOI18N */ // This can be improved by registering the listener with // the region instead of an independent map. OutputListener olistener = (OutputListener)listeners.get(index); String str = term.textWithin(currentHyperlink.begin, currentHyperlink.end); linkOnStart = false; if (olistener != null) { if ( doAction ) olistener.outputLineAction( new OutputEventImpl(InputOutput.NULL, str )); else olistener.outputLineSelected( new OutputEventImpl(InputOutput.NULL, str )); } } private static final class OutputEventImpl extends OutputEvent { private String txt; static final long serialVersionUID =-437312125483471519L; public OutputEventImpl(InputOutput src, String txt) { super(src); this.txt = txt; } /** Returns text on the line. * @return the text on the line */ public String getLine () { //return model.getElementAt(index).toString (); return txt; // NOI18N } } /** Writer, which can insert text into the output document. * @see java.io.Writer */ class TermOutputWriter extends OutputWriter { ActiveTerm aterm = null; boolean timerSet = false; Boolean timerMode = null; boolean redirOpened = false; FileWriter redirWriter = null; private ActiveRegion region = null; TermOutputWriter(ActiveTerm term) { super(new OutputStreamWriter(new NullOutputStream())); aterm = term; setPageMode (false); aterm.setRefreshEnabled(false); if ( tab != null ) redirection = outputSettings ().isRedirection(); checkRedirItem(); if ( redirection ) redirOpen(); } /** ---------------- OutputWriter methods ------------------------------*/ public void reset() throws java.io.IOException { //debug ("reset"); invokeNow(new Runnable() { public void run() { doClear(); aterm.setRefreshEnabled(false); resetRegion (); prev_wrap_warned = false; next_wrap_warned = false; } }); } public void println(String str, final OutputListener outputListener) throws java.io.IOException { final String strCopy = str; //debug ("println:str: " + str + ":" + outputListener); ensureOpen (); invokeNow(new Runnable() { public void run() { historySizeKeeper (); if ( outputListener != null ) { // This code is fraught with various assumptions about // the ordering and semantics of of output from the compiler. // It expects that the compiler sends stuff in two println's // where the first one may be a "hyperlink" and the second // one may not be. If we get any other ordering we'll get // poorly formed regions. // On this basis beginRegion() and endRegion() should really // be called something like firstHalf() and secondHalf(). /* Dead, but may be of historical interest: if (isFromCompiler (outputListener)) { if (isHyperLink (outputListener)) { firstHalf(strCopy); registerListener(region ,outputListener); } else { secondHalf(strCopy); } } else { */ createRegion(strCopy, true); registerListener(region ,outputListener); } else appendText(strCopy,false); printEOL (); } }); } public void println() { //debug ("println"); ensureOpen (); invokeNow(new Runnable() { public void run() { printEOL (); } }); } public void write(char cbuf[], final int off, final int len) { //debug ("WRITE:cbuf: " + new String(cbuf,off,len)); ensureOpen (); final char cbufCopy[] = safe_way? new char[cbuf.length]: cbuf; if (safe_way) System.arraycopy(cbuf, 0, cbufCopy, 0, cbuf.length); invokeNow(new Runnable() { public void run() { appendText(new String(cbufCopy,off,len), !isTimerMode ()); } }); } public void write(final int ch) { //debug ("write:ch:" + (char)ch + ":int: " + ch); invokeNow(new Runnable() { public void run() { write ( new char[]{(char)ch}, 0, 1); } }); } public void write(String str, int off, final int len) { //debug ("write:str: " + str); final char[] chars = new char [len]; str.getChars(off,off+len,chars,0); invokeNow(new Runnable() { public void run() { write(chars,0,len); } }); } public void flush() { //debug ("flush"); invokeNow(new Runnable() { public void run() { aterm.flush(); if ( redirWriter != null ) try { redirWriter.flush(); } catch (IOException e) { // XXX really ignore? } } }); } public void close() { //debug ("close"); invokeNow(new Runnable() { public void run() { if ( redirOpened ) redirClose(); } }); } /** ----------------- Private helper methods -----------------*/ private void redirOpen() { File redirFile = null; try { File redirDirFile = outputSettings ().getDirectory(); String name = ""; if (tab != null) { name = tab.getName(); int ispace = name.indexOf(' '); if (ispace > 0) name = name.substring( 0, ispace ); } name = name.replace(File.separatorChar, '_'); name = name + REDIR_EXT; redirFile = new File (redirDirFile, name); redirFile.createNewFile(); redirWriter = new FileWriter(redirFile.getAbsolutePath(),true); redirOpened = true; } catch (Exception e) { ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Redir file: " + redirFile, null, null, null); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } private void redirClose() { if ( redirWriter != null ) try { redirWriter.close(); redirWriter = null; } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } private void redirPrint(char[] chars,int offs, int len) { if ( redirWriter == null ) redirOpen(); if (redirWriter == null) return; try { redirWriter.write(chars, offs, len); } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } private void redirPrint(String str) { if ( redirWriter == null ) redirOpen(); if (redirWriter == null) return; try { redirWriter.write(str); } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } /** If region is created then switched to PageMode. */ private ActiveRegion createRegion(String str, boolean hyperlink) { beginRegion (str, hyperlink); return endRegion (null); } private ActiveRegion beginRegion(String str, boolean hyperlink) { resetRegion (); setPageMode (true); Coord beginCoord = Coord.make(aterm.getCursorCoord().row, 0 ); try { region = aterm.regionManager().beginRegion(beginCoord); } catch (RegionException x) { return null; } // TRY THIS INSTEAD: region = term.beginRegion(false); region.setFeedbackEnabled(false); //region.setFeedbackEnabled(true); region.setSelectable(false); if (hyperlink) { aterm.setAttribute( getLinkAttr() ); // fg -> blue aterm.setAttribute(4); // underline } if (str != null) appendText(str, false); return region; } private ActiveRegion endRegion(String str) { aterm.setAttribute(0); // reset if (str != null) appendText(str, false); aterm.endRegion(); updateNextPrevActions(); return region; } private void firstHalf(String str) { setPageMode (true); Coord beginCoord = Coord.make(aterm.getCursorCoord().row, 0 ); // Create beginning of region of whole error message try { region = aterm.regionManager().beginRegion(beginCoord); } catch (RegionException x) { return; } region.setFeedbackEnabled(false); region.setSelectable(false); // Create region for hyperlink ActiveRegion link_region; try { link_region = aterm.regionManager().beginRegion(beginCoord); } catch (RegionException x) { return; } aterm.setAttribute( getLinkAttr() ); // fg -> blue aterm.setAttribute(4); // underline appendText(str, false); aterm.setAttribute(0); // reset aterm.endRegion(); // done with hyperlink region, the main region will be close // in secondHalf(). } private void secondHalf(String str) { appendText(str, false); aterm.endRegion(); updateNextPrevActions(); } /** * Switches whether timer should be used as a way of optimalization. * Default ON (QA evaluated performance without timer as * insufficient). If this optimization needs to be excluded * then this method should return false. If problems occur or * you need to debug then switch it off. */ private boolean isTimerMode () { if (timerMode == null) { String tempStr = System.getProperty("org.netbeans.core.output.OutputTabInner.timerMode"); timerMode = (tempStr != null && tempStr.equalsIgnoreCase("false")) ? Boolean.FALSE : Boolean.TRUE; // NOI18N } /** Long enough to discourage everybody*/ return timerMode.booleanValue(); } /** Optimization way: all chars putted or appended to terminaemulator * are requested not to cause repaint. Repaint is made using this * timer. If this way of optimization will be used is switchable and * depends if timer mode is switched see isTimerMode (). If not * smoothly enough then please decrease timer period or * switch timeMode off. */ private void repaintTimer() { if ( ! timerSet ) { timerSet = true; org.openide.util.RequestProcessor.getDefault().post( new Runnable() { public void run() { flush(); timerSet = false; } }, 120); } } /** Page mode == mode of usage in terminalemulator. * Perpetually grows (danger of OutOfMemory), but may be reset. * If not Page mode then Interactive mode. Interactive mode keeps * historySize, so old Lines are removed from Buffer, but not reliable * coordinate operation, then not reliable work with regions. * In OW default Interactive mode. If any region request the * switched to PageMode. If in PageMode exceeded historySize * then reset. Not clever but unavoidable at the moment. Cannot be * reverted from PageMode to IneractiveMode. */ private void setPageMode (boolean pageMode) { if (isPageMode ()) return; aterm.setAnchored (pageMode); } private boolean isPageMode () { return aterm.isAnchored (); } /** * Has meaning only in PageMode. If historySize is exceeded reset * is called */ private void historySizeKeeper () { if (!isPageMode ()) return; /** Compiler output is about 100 lines. If anybody sets historySize * smaller then 100 line. Then bad luck.*/ if (aterm.getHistoryBuffSize () - aterm.getHistorySize () > 0) { try { reset (); } catch (Exception exc) { // XXX really ignore? } } } private void resetRegion () { if (region != null) { aterm.endRegion(); region = null; } } private void registerListener (ActiveRegion regRegion, OutputListener outpList) { if (regRegion != null && outpList != null) listeners.put( regRegion.begin, outpList ); } private void printEOL () { appendText("\n",!isTimerMode ());//NOI18N } /** * Mysterious method, probably a relic of obsolete stack trace * processing. Don't be afraid to kill it. */ private void appendText (String str, boolean repaint) { if (isTimerMode ()) { /** I don`t want to call term.appendText basically. But it * is only one method that doesn`t call repaint. But has * side effect: expands '\n' to "\n\r". */ if ( redirection ) redirPrint(str); char[] cs = str.toCharArray(); aterm.putChars(cs, 0, cs.length); repaintTimer(); return; } char[] tmp = new char[str.length()]; str.getChars(0, str.length(), tmp, 0); appendChars(tmp,0, str.length(), repaint); } private void appendChars(char[] chars,int offs, int len, boolean repaint) { if (isTimerMode ()) { appendText (new String (chars, offs, len), repaint); return; } if ( redirection ) redirPrint(chars, offs, len); if (!repaint && aterm.isRefreshEnabled()) { /** Introduced in Ivan`s DirectTermOutputWriter: but I * hasitate if has any sense because * term.setRefreshEnabled(true) calls repaint then can be * directly called term.putChars(chars, offs, len); */ aterm.setRefreshEnabled(false); aterm.putChars(chars, offs, len); aterm.setRefreshEnabled(true); } else aterm.putChars(chars, offs, len); } } final class JumpActionPerformer implements ActionPerformer { /** Performer for actions */ public void performAction(final SystemAction action) { invokeLater(new Runnable() { public void run() { if (action instanceof NextOutJumpAction) { // Traditionally bound to F12 if (nextHyperlink(true)) activateHyperlink(true); } if (action instanceof PreviousOutJumpAction) { // Traditionally bound to Shift-F12 if (prevHyperlink()) activateHyperlink(true); } updateNextPrevActions(); } }); } } static class CopyActionPerformer extends Object implements org.openide.util.actions.ActionPerformer { ActiveTerm at; public CopyActionPerformer (ActiveTerm at) { this.at = at; } /** Perform copy or cut action. */ public void performAction(org.openide.util.actions.SystemAction action) { invokeLater(new Runnable() { public void run() { at.copy(); } }); } } private static class TIListener implements TermInputListener { private OutputTabInner tab; public TIListener(OutputTabInner tab) { this.tab = tab; } public void sendChar(char c) { try { tab.inWriter.write(c); tab.inWriter.flush(); } catch (Exception x) { // XXX really ignore? } } public void sendChars(char[] c, int offset, int n) { try { tab.inWriter.write(c, offset, n); tab.inWriter.flush(); } catch (Exception x) { // XXX really ignore? } } } /** Sets action performers. Called by OutputTabInner.componentActivated(). */ public void activated() { updateCopyCutAction (); if (csa == null) { try { // XXX what is wrong with SystemAction.get(PopupAction.class)? Class popup = Class.forName("org.openide.actions.PopupAction"); // NOI18N csa = (CallbackSystemAction) CallbackSystemAction.get(popup); } catch (ClassNotFoundException e) { Error err = new NoClassDefFoundError(); ErrorManager.getDefault().annotate(err, e); throw err; } } csa.setActionPerformer(this); setFindNextEnabled( FindDialogPanel.getPattern() != null ); } /** Unsets action performers. Called by OutputTabInner.componentDeactivated(). */ public void deactivated() { if (csa != null && this.equals( csa.getActionPerformer() ) ) { csa.setActionPerformer(null); } } public void focusGained(FocusEvent ev) { //Code moved to activated() called by OutputTabInner.componentActivated() } public void focusLost(FocusEvent ev) { //Code moved to deactivated() called by OutputTabInner.componentDeactivated() } /** * Performer for action which shows popup menu (Shift-F10) */ public void performAction(SystemAction action) { if (! (action instanceof org.openide.actions.PopupAction) ) return; Mutex.EVENT.readAccess( new Runnable() { public void run() { Coord xy = null; if ( ! term.isReadOnly() && term.isCoordVisible(term.getCursorCoord()) ) { xy = term.getCursorCoord(); } else if ( term.getSelectionExtent() != null ) { if ( term.isCoordVisible(term.getSelectionExtent().begin )) { xy = term.getSelectionExtent().begin; } else if ( term.isCoordVisible(term.getSelectionExtent().end )) { xy = term.getSelectionExtent().end; } } if ( xy == null && currentHyperlink != null ) { if ( term.isCoordVisible(currentHyperlink.begin )) { xy = currentHyperlink.begin; } else if ( term.isCoordVisible(currentHyperlink.end )) { xy = currentHyperlink.end; } } Point p = null; if ( xy == null ) p = new Point( 0, 0 ); else p = term.toPixel( xy ); if (p == null) { return; } if (jPopup == null) { createPopupMenu(); } jPopup.show(term, p.x, p.y); } }); } public void actionPerformed(java.awt.event.ActionEvent actionEvent) { if (actionEvent.getSource() == selectAllItem) { selectAll(); } else if (actionEvent.getSource() == findNextItem) { findNextPattern(); } else if (actionEvent.getSource() == clearItem) { doClear(); } else if (actionEvent.getSource() == redirItem) { redirection = !redirection; checkRedirItem(); } else if (actionEvent.getSource() == discardItem) { OutputView.findDefault().discardTab(); } else if (actionEvent.getSource() == discardAllItem) { OutputView.findDefault().discardAllTabs(); } } void doClear() { term.clearHistory(); term.clear(); setHyperlinkNavigationEnabled(false); Iterator it = listeners.values().iterator(); while (it.hasNext()) { OutputListener oli = (OutputListener)it.next(); oli.outputLineCleared( new OutputEventImpl(InputOutput.NULL, null )); } listeners.clear(); activeHyperlink = null; currentHyperlink = null; updateNextPrevActions(); } /** output settings */ private static OutputSettings outputSettings () { return (OutputSettings)OutputSettings.findObject (OutputSettings.class, true); } private boolean hyperlinkNavigationEnabled = false; private void setHyperlinkNavigationEnabled(boolean hlne) { if (hyperlinkNavigationEnabled == hlne) return; hyperlinkNavigationEnabled = hlne; // Switch between the keysets where Ctrl-T and Ctrl-Shift-T are // allowed and one where they aren't. if (hlne) { term.setScrollOnInput(false); term.setKeyStrokeSet(getCommonKeyStrokeSet2()); } else { term.setScrollOnInput(true); term.setKeyStrokeSet(getCommonKeyStrokeSet()); } } private void checkRedirItem() { if ( tab == null ) return; if ( !redirection && writer != null ) ((TermOutputWriter)writer).redirClose(); if ( redirItem == null ) return; if ( redirection ) { if (outputSettings().checkRedirectionDirExists(outputSettings().getDirectory())) { redirItem.setText(NbBundle.getBundle (OutputTabInner.class).getString ("CTL_Redirect_Off")); } else { redirection = false; } } else { redirItem.setText(NbBundle.getBundle (OutputTabInner.class).getString ("CTL_Redirect_On")); } } private void checkFont() { Font font = term.getFont(); if (font != null && font.isPlain() && (font.getSize() == outputSettings ().getFontSize())) { return; } else { Font nf = new Font("Monospaced", java.awt.Font.PLAIN, outputSettings ().getFontSize()); // NOI18N term.setFont(nf); } } private void setSettings () { checkFont(); term.setForeground( outputSettings ().getBaseForeground()); term.setBackground( outputSettings ().getBaseBackground()); term.setCustomColor(1, outputSettings().getJumpCursorBackground()); term.setHistorySize( outputSettings().getHistorySize() ); tabSize = outputSettings ().getTabSize(); term.setTabSize( tabSize ); term.setCustomColor( 0, outputSettings ().getJumpLinkForeground()); term.setHighlightColor( outputSettings ().getSelectionBackground()); } public void propertyChange (PropertyChangeEvent evt) { if ( OutputSettings.PROP_REDIRECTION.equals( evt.getPropertyName() )) { if ( tab != null ) { redirection = ((Boolean)evt.getNewValue()).booleanValue(); checkRedirItem(); } } else if ( OutputSettings.PROP_DIRECTORY.equals( evt.getPropertyName() )) { if ( tab != null ) { if ( redirection && writer != null ) { ((TermOutputWriter)writer).redirClose(); if (outputSettings().getDirectory().exists()) { ((TermOutputWriter)writer).redirOpen(); } } } } else setSettings (); } private void selectAll() { Extent allExt = new Extent( Coord.make(0,0), term.getCursorCoord() ); term.setSelectionExtent( allExt ); } private void ensureOpen() { if (isShowing()) { return; } //Note, intentionally not using Mutex.EVENT.readAccess here. Since //this can be called for individual characters that are output, //avoiding any unnecessary overhead, however trivial if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { _ensureOpen(); } }); } else { _ensureOpen(); } } private void _ensureOpen() { if (tab != null) { tab.ensureOpen(); } } private ArrayList runnables = new ArrayList(); private boolean runningRunnables; private Object syncObject = new Object(); /** * Utility to schedule stuff on the Event Dispatch thread. *

* The usual pattern is for every function foo() there be a fooImpl() * and call fooImpl() or wrap it in a Runnable. With this 'invokeNow' * we always wrap in a Runnable but I believe it's a small cost * since inmost cases (heavy character output) we're going through the * Runnable anyway. *

* For an explanation of why invokeAndWait() as opposed to invokeLater() * see .../terminalemulator/ReleaseNotes.ivan.txt under tag ivan_17. */ private void invokeNow(final Runnable runnable) { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { if (safe_way) { synchronized ( syncObject ) { if (runningRunnables) { try { syncObject.wait (500); } catch (InterruptedException ex) { } } runnables.add( runnable ); if (runnables.size () > 50) { try { syncObject.wait (500); } catch (InterruptedException ex) { } } if ( runnables.size() == 1 ) SwingUtilities.invokeLater( this ); } } else { try { SwingUtilities.invokeAndWait(runnable); } catch (Exception x) { ; } } } } public void run () { ArrayList torun; synchronized ( syncObject ) { runningRunnables = true; torun = (ArrayList)runnables.clone(); runnables = new ArrayList(); } Iterator it = torun.iterator(); while ( it.hasNext() ) ((Runnable)(it.next())).run(); synchronized ( syncObject ) { runningRunnables = false; syncObject.notifyAll(); } } private int getLinkAttr() { // custom color 0 in term return 50; } void setReadWrite( boolean rw ) { term.setCursorVisible ( rw ); term.setReadOnly ( !rw ); } } final static private boolean safe_way = true; private static void invokeLater(Runnable runnable) { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { SwingUtilities.invokeLater(runnable); } } /* DEBUG private static void ckEventDispatchThread() { if (!SwingUtilities.isEventDispatchThread()) { System.out.println("OW: NOT IN EventDispatchThread"); Thread.dumpStack(); } } */ }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.