0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: *
0041: * Contributor(s): Ivan Soleimanipour.
0042: */
0043:
0044: package org.netbeans.lib.terminalemulator;
0045:
0046: import java.awt.*;
0047: import java.awt.event.*;
0048: import java.awt.datatransfer.*;
0049: import javax.swing.*;
0050: import javax.accessibility.*;
0051:
0052: import java.awt.font.*;
0053: import java.awt.geom.Point2D;
0054:
0055: import java.util.HashSet;
0056: import java.util.Date;
0057: import java.util.LinkedList;
0058: import java.util.ListIterator;
0059:
0060: /**
0061: Term is a pure Java multi-purpose terminal emulator.
0062: <p>
0063: It has the following generic features:
0064: <ul>
0065: <li>All "dumb" operations. Basically putting characters on a screen and
0066: processing keyboard input.
0067: <li>ANSI mode "smart" operations. Cursor control etc.
0068: <li>Character attributes like color, reverse-video etc.
0069: <li>Selection service in character, word and line modes matching xterm
0070: with configurable word boundary detection.
0071: <li>History buffer.
0072: <li>Facilities to iterate through logical lines, to implement search for
0073: example.
0074: <li>Support for nested pickable regions in order to support hyperlinked
0075: views or more complex active text utilities.
0076: <li>Support for double-width Oriental characters.
0077: </ul>
0078: <p>
0079: <h2>Coordinate systems</h2>
0080: The following coordinate systems are used with Term.
0081: They are all cartesian and have their origin at the top left.
0082: All but the first are 0-origin.
0083: But they differ in all other respects:
0084: <dl>
0085:
0086: <dt>ANSI Screen coordinates
0087: <dd>
0088: Address only the visible portion of the screen.
0089: They are 1-origin and extend thru the width and height of the visible
0090: portion of the screen per getColumns() and getRows().
0091: <p>
0092: This is how an application (like 'vi' etc) views the screen.
0093: This coordinate system primarily comes into play in the cursor addressing
0094: directive, op_cm() and otherwise is not really used in the implementation.
0095: <p>
0096:
0097: <dt>Cell coordinates
0098: <dd>
0099: Each character usually takes one cell, and all placement on the screen
0100: is in terms of a grid of cells getColumns() wide This cellular nature
0101: is why fixed font is "required". In some locales some characters may
0102: be double-width.
0103: Japanese characters are like this, so they take up two cells.
0104: There are no double-height characters (that I know of).
0105: <p>
0106: Cursor motion is in cell coordinates, so to move past a Japanese character
0107: you need the cursor to move right twice. A cursor can also be placed on
0108: the second cell of a double-width character.
0109: <p>
0110: Note that this is strictly an internal coordinate system. For example
0111: Term.getCursorCol() and getCursorCoord() return buffer coordinates.
0112: <p>
0113: The main purpose of this coordinate system is to capture logical columns.
0114: In the vertical direction sometimes it extends only the height of the
0115: screen and sometimes the height of the buffer.
0116: <p>
0117:
0118: <dt>Buffer coordinates ...
0119: <dd>
0120: ... address the whole history character buffer.
0121: These are 0-origin and extend thru the width
0122: of the screen per getColumns(), or more if horizontal scrolling is
0123: enabled, and the whole history, that is, getHistorySize()+getRows().
0124: <p>
0125: The BCoord class captures the value of such coordinates.
0126: It is more akin to the 'int offset' used in the Java text package
0127: as opposed to javax.swing.text.Position.
0128: <p>
0129: If there are no double-width characters the buffer coords pretty much
0130: overlap with cell coords. If double-width characters are added then
0131: the buffer column and cell column will have a larger skew the more right
0132: you go.
0133: <p>
0134: <dt>Absolute coordinates ...
0135: <dd>
0136: ... are like Buffer coordinates in the horizontal direction.
0137: In the vertical direction their origin is the first line that was
0138: sent to the terminal. This line might have scrolled out of history and
0139: might no longer be in the buffer. In effect each line ever printed by
0140: Term gets a unique Absolute row.
0141: <p>
0142: What good is this? The ActiveRegion mechanism maintains coordinates
0143: for its' boundaries. As text scrolls out of history buffer row coordinates
0144: have to shift and all ActiveRegions' coords need to be relocated. This
0145: can get expensive because as soon as the history buffer becomes full
0146: each newline will require a relocation. This is the approach that
0147: javax.swing.text.Position implements and it's justified there because
0148: no Swing component has a "history buffer".
0149: However, if you use absolute coordinates you'll never have to
0150: relocate anything! Simple and effective.
0151: <p>
0152: Well almost. What happens when you reach Integer.MAX_VALUE? You wrap and
0153: that can confuse everything. What are the chances of this happening?
0154: Suppose term can process 4000 lines per second. A runaway process will
0155: produce Integer.MAX_VALUE lines in about 4 days. That's too close
0156: for comfort, so Term does detect the wrap and only then goes and
0157: relocates stuff. This, however, causes a secondary problem with
0158: testability since no-one wants to wait 4 days for a single wrap.
0159: So what I've done is periodically set Term.modulo to something
0160: smaller and tested stuff.
0161: <p>
0162: I'm indebted to Alan Kostinsky for this bit of lateral thinking.
0163: </dl>
0164:
0165:
0166: <p>
0167: <h2>Modes of use</h2>
0168: There are three ways Term can be used.
0169: These modes aren't explicit they are just a convenient way of discussing
0170: functionality.
0171: <dl>
0172: <dt>Screen mode
0173: <dd>
0174: This represents the traditional terminal without a history buffer.
0175: Applications
0176: running under the terminal assume they are dealing with a fixed size
0177: screen and interact with it in the screen coordinate system through
0178: escape sequence (ANSI or otherwise). The most common application which
0179: uses terminals in this ways is the screen editor, like vi or emacs.
0180: <p>
0181: Term will convert keystrokes to an output stream and will process
0182: characters in an input stream and render them unto the screen.
0183: What and how these streams are connected to is up to the client of Term,
0184: since it is usually highly platform dependent. For example on unixes
0185: the streams may be connected to partially-JNI-based "pty" streams.
0186: <p>
0187: This mode works correctly even if there is history and you see a
0188: scrollbar, just as it does under XTerm and it's derivatives.
0189:
0190: <p>
0191: <dt>Buffer/Interactive mode
0192: <dd>
0193: This is the primary facility that XTerm and other derivatives provide. The
0194: screen has a history buffer in the vertical dimension.
0195: <p>
0196: Because of limited history active regions can scroll out of history and
0197: while the coordinate invalidation problem is not addressed by absolute
0198: coordiantes sometimes we don't want stuff to wink out.
0199: <br>
0200: Which is why we have ...
0201:
0202: <p>
0203: <dt>Page mode
0204: <dd>
0205: It is possible to "anchor" a location in the buffer and prevent it
0206: from going out of history. This can be helpful in having the
0207: client of Term make sure that crucial output doesn't get lost due to
0208: short-sighted history settings on the part of the user.
0209: <p>
0210: To use Term
0211: in this mode you can use setText() or appendText() instead of
0212: connecting to Terms streams.
0213: This mode is called page mode because the most common use of it
0214: would be as something akin to a hypertext browser.
0215: To that end
0216: Term supports nestable ActiveRegions and mapping of coordinates
0217: to regions. ActiveTerm puts all of this together in a comprehensive
0218: subclass.
0219: </dl>
0220:
0221: <p>
0222: <h2>What Term is not</h2>
0223: <ul>
0224: <li>
0225: While there is an internal Buffer class, and while it behaves like a
0226: document in that it can
0227: be implicitly "edited" and character attributes explicitly changed,
0228: Term is not a document editing widget.
0229: <p>
0230: <li>
0231: Term is also not a command line processor in the sense that a MS Windows
0232: console is. Its shuttling of keyboard events to an output stream and
0233: rendering of characters from the input stream unto the screen are completely
0234: independent activities.
0235: <p>
0236: This is due to Terms unix heritage where shells (ksh, bash etc) do their own
0237: cmdline and history editing, but if you're so inclined the LineDiscipline
0238: may be used for experimentation with indigenous cmdline processing.
0239: </ul>
0240: */
0241:
0242: public class Term extends JComponent implements Accessible {
0243: private State st = new State();
0244: private Sel sel = new Sel(this , st);
0245: private Ops ops = new OpsImpl();
0246:
0247: private int top_margin = 0; // 0 means default (see topMargin())
0248: private int bot_margin = 0;
0249:
0250: // Stuff to control how often RegionManager.cull() gets called
0251: private int cull_count = 0;
0252: private static final int cull_frequency = 50;
0253:
0254: // 'firsta' is the absolute line number of the line at 'lines[0]'.
0255: protected int firsta = 0;
0256:
0257: // chars gone by in lines that winked out of history
0258: // 'firsta' ~= 'linesInPrehistory'
0259: private int charsInPrehistory = 0;
0260:
0261: private static final int modulo = Integer.MAX_VALUE / 2;
0262:
0263: private Screen screen;
0264: private JScrollBar vscroll_bar;
0265: private ScrollWrapper hscroll_wrapper;
0266: private JScrollBar hscroll_bar;
0267: private boolean has_focus;
0268:
0269: // statistics
0270: private int n_putchar;
0271: private int n_putchars;
0272: private int n_linefeeds;
0273: private int n_repaint;
0274: private int n_paint;
0275:
0276: MyFontMetrics metrics = null;
0277:
0278: Buffer buf = new Buffer(80);
0279:
0280: private RegionManager region_manager = new RegionManager();
0281:
0282: // 'left_down_point' remembers where the left button came down as a
0283: // workaround for the flakey mouseDragged event. The flakiness has to with
0284: // the fact that the mousePressed coord is not delivered in the first drag
0285: // event, so if you proess and drag very quickly, the first drag coord
0286: // will be quite far from the initial press location.
0287:
0288: private Point left_down_point;
0289:
0290: // getSystemSelection() wasn't available on Java prior to 1.4
0291: private Clipboard systemClipboard = getToolkit()
0292: .getSystemClipboard();
0293: private Clipboard systemSelection = getToolkit()
0294: .getSystemSelection();
0295:
0296: /**
0297: * ScrollWrapper is a HACK that allows us to make pairs of scrollbars
0298: * look nice.
0299: * <p>
0300: * A JScrollPane, or more specifically ScrollPaneLayout, arranges
0301: * a pair of vertical and horizontal scrollbars as follows:
0302: * | |
0303: * | |
0304: * |v|
0305: * -------------
0306: * >|
0307: * -------------
0308: * ... so that here is a nice square corner.
0309: *
0310: * But ScrollPaneLayout insists that it's viewport is a JViewport and that
0311: * it's container is a JScrollPane. It is probably possible to make the
0312: * screen be a JViewPort and use a JScrollPane to contain the screen, but
0313: * it's very tricky. (For that matter it should be possible to avoid doing
0314: * our own scrolling altogether and use JScrollPane functionality, but
0315: * it's also tricky).
0316: *
0317: * Since we're using a BorderLayout putting the horizontal SB in the SOUTH
0318: * portion yields something like this:
0319: * | |
0320: * | |
0321: * |v|
0322: * ----------------
0323: * >|
0324: * ----------------
0325: * Soooo, to make things look right, we use ScrollWrapper to control the
0326: * sizing of the horizontal scrollbar. It basically uses a GridBagLayout
0327: * and GridBagConstraints.insets to create the square corner.
0328: */
0329:
0330: private class ScrollWrapper extends JComponent implements
0331: Accessible {
0332: public JScrollBar scroll_bar;
0333:
0334: public ScrollWrapper(JScrollBar scroll_bar) {
0335: GridBagLayout gbl = new GridBagLayout();
0336: setLayout(gbl);
0337:
0338: GridBagConstraints gbc = new GridBagConstraints();
0339: gbc.anchor = GridBagConstraints.WEST;
0340: gbc.fill = GridBagConstraints.BOTH;
0341: gbc.gridwidth = 1;
0342: gbc.gridheight = 1;
0343: gbc.weightx = 1.0f;
0344: gbc.weighty = 1.0f;
0345: int slop = vscroll_bar.getMaximumSize().width;
0346: gbc.insets = new Insets(0, 0, 0, slop);
0347: add(scroll_bar, gbc);
0348:
0349: this .scroll_bar = scroll_bar;
0350: }
0351:
0352: protected void paintComponent(Graphics g) {
0353: // If we don't do this, the square corner will end getting filled
0354: // with random grot.
0355: Dimension sz = getSize();
0356: g.clearRect(0, 0, sz.width, sz.height);
0357: }
0358:
0359: //......................................................................
0360: // Accessibility stuff is all here
0361: //......................................................................
0362:
0363: public AccessibleContext getAccessibleContext() {
0364: if (accessible_context == null) {
0365: accessible_context = new AccessibleScrollWrapper();
0366: }
0367: return accessible_context;
0368: }
0369:
0370: private AccessibleContext accessible_context;
0371:
0372: protected class AccessibleScrollWrapper extends
0373: AccessibleJComponent {
0374: public AccessibleRole getAccessibleRole() {
0375: return AccessibleRole.PANEL;
0376: }
0377: }
0378: }
0379:
0380: private class BaseTermStream extends TermStream {
0381: public void flush() {
0382: repaint(true);
0383: }
0384:
0385: public void putChar(char c) {
0386: /*
0387: * echoes a character unto the screen
0388: */
0389: ckEventDispatchThread();
0390: // OLD NPE-x synchronized(Term.this)
0391: {
0392: n_putchar++;
0393: putc_work(c);
0394: }
0395: possibly_repaint(true);
0396:
0397: // pavel.buzek@czech.sun.com put this as a fix to speed up
0398: // StreamTerm on windows. This will make raw mode not work,
0399: // Instead now LineDiscipline properly buffers incoming characters.
0400: // so should be considered temporary.
0401: // repaint(c == '\n');
0402: }
0403:
0404: public void putChars(char buf[], int offset, int count) {
0405: ckEventDispatchThread();
0406: // OLD NPE-x synchronized (Term.this)
0407: {
0408: n_putchars++;
0409: for (int bx = 0; bx < count; bx++) {
0410: putc_work(buf[offset + bx]);
0411: }
0412: }
0413: possibly_repaint(true);
0414: }
0415:
0416: public void sendChar(char c) {
0417: fireChar(c);
0418: }
0419:
0420: public void sendChars(char buf[], int offset, int count) {
0421: fireChars(buf, offset, count);
0422: }
0423: }
0424:
0425: // head is closer to Term
0426: // pushes extend tail
0427: private TermStream base_stream = new BaseTermStream();
0428: private TermStream dce_end = base_stream;
0429: private TermStream dte_end = base_stream;
0430:
0431: /**
0432: *
0433: */
0434: public void pushStream(TermStream stream) {
0435: // Term will send keystrokes by calling dte_end.sendChar
0436: // Characters sent via Term.putChar will be sent down dce_end.
0437: //
0438: // The base stream is strange in that on the first push it will get
0439: // split into two parts, one sticking at the end of the dce chain,
0440: // the other at the end of the dte chain. Hence the special case
0441: // treatment
0442:
0443: if (dce_end == base_stream) {
0444: // towards dce
0445: dte_end = stream;
0446: stream.setToDCE(base_stream);
0447:
0448: // towards dte
0449: stream.setToDTE(base_stream);
0450: dce_end = stream;
0451:
0452: } else {
0453: // towards dce
0454: dte_end.setToDCE(stream);
0455: stream.setToDCE(base_stream);
0456:
0457: // towards dte
0458: stream.setToDTE(dce_end);
0459: dce_end = stream;
0460: }
0461:
0462: stream.setTerm(this );
0463: }
0464:
0465: /*
0466: * Debugging utilities
0467: */
0468: public static final int DEBUG_OPS = 1 << 0;
0469: public static final int DEBUG_KEYS = 1 << 1;
0470: public static final int DEBUG_INPUT = 1 << 2;
0471: public static final int DEBUG_OUTPUT = 1 << 3;
0472: public static final int DEBUG_WRAP = 1 << 4;
0473: public static final int DEBUG_MARGINS = 1 << 5;
0474:
0475: private int debug_gutter_width = 0;
0476:
0477: public void setDebugFlags(int flags) {
0478: debug = flags;
0479: }
0480:
0481: private int debug = /* DEBUG_OPS|DEBUG_KEYS|DEBUG_INPUT|DEBUG_OUTPUT | */0;
0482:
0483: private boolean debugOps() {
0484: return (debug & DEBUG_OPS) == DEBUG_OPS;
0485: }
0486:
0487: private boolean debugKeys() {
0488: return (debug & DEBUG_KEYS) == DEBUG_KEYS;
0489: }
0490:
0491: private boolean debugWrap() {
0492: return (debug & DEBUG_WRAP) == DEBUG_WRAP;
0493: }
0494:
0495: private boolean debugMargins() {
0496: return true;
0497: /* TMP
0498: return (debug & DEBUG_MARGINS) == DEBUG_MARGINS;
0499: */
0500: }
0501:
0502: /**
0503: * Return true if DEBUG_INPUT flag has been set.
0504: */
0505: protected boolean debugInput() {
0506: return (debug & DEBUG_INPUT) == DEBUG_INPUT;
0507: }
0508:
0509: /**
0510: * Return true if DEBUG_OUTPUT flag has been set.
0511: */
0512: protected boolean debugOutput() {
0513: return (debug & DEBUG_OUTPUT) == DEBUG_OUTPUT;
0514: }
0515:
0516: /*
0517: * Top and bottom margins are stored as 1-origin values, with 0
0518: * denoting default.
0519: *
0520: * topMargin() & botMargin() return 0-origin values which is what we
0521: * use for screen coordinates.
0522: *
0523: * The margin lines are inclusive, that is, lines on the margin lines
0524: * participate in scrolling.
0525: */
0526:
0527: private int topMargin() {
0528: return (top_margin == 0) ? 0 : top_margin - 1;
0529: }
0530:
0531: private int botMargin() {
0532: return (bot_margin == 0) ? st.rows - 1 : bot_margin - 1;
0533: }
0534:
0535: /**
0536: * beginx is row lines above the bottom.
0537: * It's used for all cursor motion calculations and cursor relative
0538: * line operations.
0539: * It's used instead of firstx because firstx changes as we scroll.
0540: * This allows us to restrict screen editing to the last chunk of
0541: * the buffer.
0542: */
0543: private int beginx() {
0544: return buf.nlines - st.rows;
0545: }
0546:
0547: private Line cursor_line() {
0548: return buf.lineAt(st.cursor.row);
0549: }
0550:
0551: /**
0552: * Set/unset WordDelineator.
0553: * Passing a null sets it to the default WordDelineator.
0554: */
0555: public void setWordDelineator(WordDelineator word_delineator) {
0556: if (word_delineator == null)
0557: this .word_delineator = default_word_delineator;
0558: else
0559: this .word_delineator = word_delineator;
0560: }
0561:
0562: /**
0563: * Get the WordDelineator used by this.
0564: */
0565: public WordDelineator getWordDelineator() {
0566: return this .word_delineator;
0567: }
0568:
0569: private WordDelineator default_word_delineator = new WordDelineator();
0570: WordDelineator word_delineator = default_word_delineator;
0571:
0572: /**
0573: * Set/unset input listener.
0574: * Entered text gets sent via this listener
0575: *
0576: * @deprecated Replaced by {@link #addInputListener(TermInputListener)}.
0577: */
0578: public void setInputListener(TermInputListener l) {
0579: addInputListener(l);
0580: }
0581:
0582: /**
0583: * Add an input listener to this.
0584: * <p>
0585: * Text entered via the keyboard gets sent via this listener.
0586: */
0587: public void addInputListener(TermInputListener l) {
0588: input_listeners.add(l);
0589: }
0590:
0591: public void removeInputListener(TermInputListener l) {
0592: input_listeners.remove(l);
0593: }
0594:
0595: private void fireChar(char c) {
0596: ListIterator iter = input_listeners.listIterator();
0597: while (iter.hasNext()) {
0598: TermInputListener l = (TermInputListener) iter.next();
0599: l.sendChar(c);
0600: }
0601: }
0602:
0603: private void fireChars(char buf[], int offset, int count) {
0604: ListIterator iter = input_listeners.listIterator();
0605: while (iter.hasNext()) {
0606: TermInputListener l = (TermInputListener) iter.next();
0607: l.sendChars(buf, offset, count);
0608: }
0609: }
0610:
0611: private LinkedList input_listeners = new LinkedList();
0612:
0613: /**
0614: * Set/unset misc listener.
0615: * The following events gets sent via this listener:
0616: * window size changes
0617: *
0618: * @deprecated Replaced by{@link #addListener(TermListener)}.
0619: */
0620: public void setListener(TermListener l) {
0621: addListener(l);
0622: }
0623:
0624: /**
0625: * Add a TermListener to this.
0626: */
0627: public void addListener(TermListener l) {
0628: listeners.add(l);
0629: }
0630:
0631: /**
0632: * Remove the given TermListener from this.
0633: */
0634: public void removeListener(TermListener l) {
0635: listeners.remove(l);
0636: }
0637:
0638: private void fireSizeChanged(Dimension cells, Dimension pixels) {
0639: ListIterator iter = listeners.listIterator();
0640: while (iter.hasNext()) {
0641: TermListener l = (TermListener) iter.next();
0642: l.sizeChanged(cells, pixels);
0643: }
0644: }
0645:
0646: private LinkedList listeners = new LinkedList();
0647:
0648: /**
0649: * Set/unset focus policy.
0650: * <br>
0651: * When set, the Term screen will grab focus when clicked on, otherwise
0652: * it will grab focus when the mouse moves into it.
0653: */
0654: public void setClickToType(boolean click_to_type) {
0655: this .click_to_type = click_to_type;
0656: }
0657:
0658: public boolean isClickToType() {
0659: return click_to_type;
0660: }
0661:
0662: private boolean click_to_type = true;
0663:
0664: /**
0665: * Control whether keystrokes are ignored.
0666: */
0667: public void setReadOnly(boolean read_only) {
0668: this .read_only = read_only;
0669: }
0670:
0671: /**
0672: * Return whether keystrokes are ignored.
0673: */
0674: public boolean isReadOnly() {
0675: return read_only;
0676: }
0677:
0678: private boolean read_only = false;
0679:
0680: /**
0681: * Clear the visible portion of screen
0682: */
0683: public void clear() {
0684: for (int row = 0; row < st.rows; row++) {
0685: Line l = buf.lineAt(beginx() + row);
0686: l.reset();
0687: }
0688: regionManager().reset();
0689: }
0690:
0691: /**
0692: * Clear all of the history without repainting the screen.
0693: * <p>
0694: * This is useful if you want to avoid flicker.
0695: */
0696: public void clearHistoryNoRefresh() {
0697: sel.cancel(true);
0698:
0699: int old_cols = buf.visibleCols();
0700: buf = new Buffer(old_cols);
0701:
0702: firsta = 0;
0703: charsInPrehistory = 0;
0704:
0705: st.firstx = 0;
0706: st.firsty = 0;
0707: st.cursor.row = 0;
0708: st.cursor.col = 0;
0709: st.attr = 0;
0710: st.saveCursor(); // This clobbers the saved cursor value
0711: st.restoreCursor(); // release reference to saved cursor object
0712:
0713: adjust_lines(st.rows);
0714:
0715: st.firstx = 0;
0716: st.firsty = 0;
0717:
0718: regionManager().reset();
0719:
0720: screen.possiblyUpdateCaretText();
0721: }
0722:
0723: /**
0724: * Clear all of the history, including any visible portion of it.
0725: * <p>
0726: * Use {@link #clearHistoryNoRefresh()} if you find that clearHistory
0727: * causes flickering.
0728: */
0729: public void clearHistory() {
0730: clearHistoryNoRefresh();
0731: repaint(true);
0732: }
0733:
0734: /**
0735: * Return the RegionManager associated with this Term
0736: */
0737: public RegionManager regionManager() {
0738: return region_manager;
0739: }
0740:
0741: public String textWithin(Coord begin, Coord end) {
0742: if (begin == null || end == null)
0743: return null;
0744:
0745: final StringBuffer buf = new StringBuffer();
0746:
0747: visitLines(begin, end, false, new LineVisitor() {
0748: public boolean visit(Line l, int row, int bcol, int ecol) {
0749: buf.append(l.charArray(), bcol, ecol - bcol + 1);
0750: return true;
0751: }
0752: });
0753: return buf.toString();
0754: }
0755:
0756: public String getRowText(int row) {
0757: Line line = buf.lineAt(row);
0758: if (line == null)
0759: return null;
0760: return line.stringBuffer().toString();
0761: }
0762:
0763: /**
0764: * Get KeyStroke set.
0765: * <p>
0766: * Be default Term consumes all keystrokes.
0767: * Any KeyStroke added to this set will be passed through and not consumed.
0768: * <p>
0769: * Be careful with control characters you need to create the keystroke
0770: * as follows (note the - 64):
0771: * <pre>
0772: * KeyStroke.getKeyStroke(new Character((char)('T'-64)), Event.CTRL_MASK)
0773: * </pre>
0774: */
0775: public HashSet getKeyStrokeSet() {
0776: return keystroke_set;
0777: }
0778:
0779: /*
0780: * Set the KeyStroke set.
0781: * <p>
0782: * While Term has a KeyStroke set set up by default, often many Terms
0783: * share the same keystroke. This method allows this sharing.
0784: */
0785: public void setKeyStrokeSet(HashSet keystroke_set) {
0786: this .keystroke_set = keystroke_set;
0787:
0788: /* DEBUG
0789: System.out.println("-----------------------------------------");//NOI18N
0790: java.util.Iterator i = keystroke_set.iterator();
0791: while (i.hasNext()) {
0792: KeyStroke ks = (KeyStroke) i.next();
0793: System.out.println("--- " + ks); // NOI18N
0794: }
0795: */
0796: }
0797:
0798: private HashSet keystroke_set = new HashSet();
0799:
0800: // attempted partial fix for IZ 17337
0801: // 'keystroke_set' is a collection of KeyStrokes in the form:
0802: // ks3 = getKeyStroke(VK_C, CTRL_MASK)
0803: // we use maybeConsume() in keyPressed and keyTyped events. During
0804: // keyTyped the event->KS gives us
0805: // ks2 = getKeyStroke((char) ('c'-64), CTRL_MASK)
0806: // ks2 and ks3 while logically equivalent don't hash to the same so
0807: // maybeConsume() says yes to ks2 and the Ctrl-C gets passed on.
0808: //
0809: // So to detect whether something in 'keystroke_set' needs to be dropped
0810: // we need to check at keyPress time but take action at keyTyped time.
0811: // 'passOn' helps us do that.
0812:
0813: private boolean passOn = true;
0814:
0815: /**
0816: * Return true (and consume it) if 'e' is allowed to be consumed by us.
0817: *
0818: * If our owner is interested in some keys they will put someting into
0819: * keystroke_set.
0820: */
0821: private boolean maybeConsume(KeyEvent e) {
0822:
0823: if (e.isConsumed())
0824: return false;
0825:
0826: KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
0827:
0828: /* DEBUG
0829: System.out.println("Term.maybeConsume(" + e + ")"); // NOI18N
0830: System.out.println("\tKS = " + ks); // NOI18N
0831: System.out.println("\tcontained = " + keystroke_set.contains(ks)); // NOI18N
0832: */
0833:
0834: if (keystroke_set == null || !keystroke_set.contains(ks)) {
0835: e.consume();
0836: return true;
0837: }
0838: return false;
0839: }
0840:
0841: /**
0842: * Visit the physical lines from begin, through 'end'.
0843: * <p>
0844: * If 'newlines' is set, the passed 'ecol' is set to the actual
0845: * number of columns in the view to signify that the newline is included.
0846: * This way of doing it helps with rendering of a whole-line selection.
0847: * Also Line knows about this and will tack on a "\n" when Line.text()
0848: * is asked for.
0849: */
0850: void visitLines(Coord begin, Coord end, boolean newlines,
0851: LineVisitor visitor) {
0852: buf.visitLines(begin.toBCoord(firsta), end.toBCoord(firsta),
0853: newlines, visitor);
0854: }
0855:
0856: /**
0857: * Visit logical lines from begin through end.
0858: * <p>
0859: * If begin is null, then the start of the buffer is assumed.
0860: * If end is null, then the end of the buffer is assumed.
0861: */
0862: public void visitLogicalLines(Coord begin, Coord end,
0863: final LogicalLineVisitor llv) {
0864:
0865: // Create a trampoline visitor
0866: LineVisitor tramp = new LineVisitor() {
0867: String text = ""; // NOI18N
0868: int lineno = 0;
0869: Coord begin = null;
0870: Coord end = null;
0871:
0872: public boolean visit(Line l, int brow, int bcol, int ecol) {
0873:
0874: if (l.isWrapped()) {
0875: if (begin == null)
0876: begin = new Coord(new BCoord(brow, bcol),
0877: firsta);
0878: text += l.text(bcol, ecol);
0879:
0880: } else {
0881: if (begin == null)
0882: begin = new Coord(new BCoord(brow, bcol),
0883: firsta);
0884: end = new Coord(new BCoord(brow, ecol), firsta);
0885: text += l.text(bcol, ecol);
0886:
0887: if (!llv.visit(lineno, begin, end, text))
0888: return false;
0889:
0890: lineno++;
0891: text = ""; // NOI18N
0892: begin = null;
0893: end = null;
0894: }
0895:
0896: return true;
0897: }
0898: };
0899:
0900: if (begin == null)
0901: begin = new Coord();
0902:
0903: if (end == null) {
0904: int lastrow = buf.nlines - 1;
0905: Line l = buf.lineAt(lastrow);
0906: end = new Coord(new BCoord(lastrow, l.length() - 1), firsta);
0907: }
0908:
0909: if (begin.compareTo(end) > 0)
0910: return;
0911:
0912: buf.visitLines(begin.toBCoord(firsta), end.toBCoord(firsta),
0913: false, tramp);
0914: }
0915:
0916: /**
0917: * Visit logical lines in reverse from end through begin.
0918: * <p>
0919: * If begin is null, then the start of the buffer is assumed.
0920: * If end is null, then the end of the buffer is assumed.
0921: */
0922: public void reverseVisitLogicalLines(Coord begin, Coord end,
0923: final LogicalLineVisitor llv) {
0924:
0925: // Create a trampoline visitor
0926: LineVisitor tramp = new LineVisitor() {
0927: String text = ""; // NOI18N
0928: int lineno = 0;
0929: Coord begin = null;
0930: Coord end = null;
0931:
0932: public boolean visit(Line l, int brow, int bcol, int ecol) {
0933:
0934: boolean line_is_continuation = false;
0935: if (brow > 0 && buf.lineAt(brow - 1).isWrapped())
0936: line_is_continuation = true;
0937:
0938: if (line_is_continuation) {
0939: if (end == null)
0940: end = new Coord(new BCoord(brow, ecol), firsta);
0941: text = l.text(bcol, ecol) + text;
0942:
0943: } else {
0944: if (end == null)
0945: end = new Coord(new BCoord(brow, ecol), firsta);
0946: begin = new Coord(new BCoord(brow, bcol), firsta);
0947: text = l.text(bcol, ecol) + text;
0948:
0949: if (!llv.visit(lineno, begin, end, text))
0950: return false;
0951:
0952: lineno++;
0953: text = ""; // NOI18N
0954: begin = null;
0955: end = null;
0956: }
0957:
0958: return true;
0959: }
0960: };
0961:
0962: if (begin == null)
0963: begin = new Coord();
0964:
0965: if (end == null) {
0966: int lastrow = buf.nlines - 1;
0967: Line l = buf.lineAt(lastrow);
0968: end = new Coord(new BCoord(lastrow, l.length() - 1), firsta);
0969: }
0970:
0971: if (begin.compareTo(end) > 0)
0972: return;
0973:
0974: buf.reverseVisitLines(begin.toBCoord(firsta), end
0975: .toBCoord(firsta), false, tramp);
0976: }
0977:
0978: /**
0979: * Convert offsets in logical lines into physical coordinates.
0980: * <p>
0981: * Usually called from within a LogicalLineVisitor.visit().
0982: * 'begin' should be the 'begin' Coord passed to visit. 'offset' is an
0983: * offset into the logical line, the 'text' argument passed to visit().
0984: * <p>
0985: * Note that 'offset' is relative to begin.col!
0986: * <p>
0987: * Following is an example of a line "aaaaaxxxxxxxxxXXXxxxxxxxx" which
0988: * got wrapped twice. Suppose you're searching for "XXX" and you
0989: * began your search from the first 'x' on row 2.
0990: * <pre>
0991: row | columns |
0992: ----+----------+
0993: 0 |0123456789|
0994: 1 | |
0995: 2 |aaaaaxxxxx| wrap
0996: 3 |xxxxXXXxxx| wrap
0997: 4 |xxxxx |
0998: 5 | |
0999:
1000: * </pre>
1001: * The visitor will be called with 'begin' pointing at the first 'x',
1002: * 'end' pointing at the last 'x' and 'text' containing the above line.
1003: * Let's say you use String.indexOf("XXX") and get an offset of 9.
1004: * Passing the 'begin' through and the 9 as an offset and 3 as the
1005: * length will yield an Extent (3,4) - (3,7) which youcan forward to
1006: * setSelectionExtent();
1007: * <p>
1008: * The implementation of this function is not snappy (it calls
1009: * Term.advance() in a loop) but the assumption is that his function
1010: * will not be called in a tight loop.
1011: */
1012: public Extent extentInLogicalLine(Coord begin, int offset,
1013: int length) {
1014:
1015: // SHOULD factor the A/B Coord conversion out
1016:
1017: Coord from = (Coord) begin.clone();
1018: while (offset-- > 0) {
1019: from = new Coord(buf.advance(from.toBCoord(firsta)), firsta);
1020: }
1021:
1022: Coord to = (Coord) from.clone();
1023: while (--length > 0) {
1024: to = new Coord(buf.advance(to.toBCoord(firsta)), firsta);
1025: }
1026:
1027: return new Extent(from, to);
1028: }
1029:
1030: private boolean cursor_was_visible() {
1031: return st.cursor.row - 1 >= st.firstx
1032: && st.cursor.row - 1 < st.firstx + st.rows;
1033: }
1034:
1035: /**
1036: * Ensure that the given coordinate is visible.
1037: * <p>
1038: * If the given coordinate is visible within the screen nothing happens.
1039: * Otherwise the screen is scrolled so that the given target ends up
1040: * approximately mid-sceeen.
1041: */
1042: public void possiblyNormalize(Coord target) {
1043: if (target == null)
1044: return;
1045:
1046: BCoord btarget = target.toBCoord(firsta);
1047: if (btarget.row >= st.firstx
1048: && btarget.row < st.firstx + st.rows)
1049: return;
1050:
1051: st.firstx = btarget.row - (st.rows / 2);
1052: if (st.firstx < 0)
1053: st.firstx = 0;
1054: else if (st.firstx + st.rows > buf.nlines)
1055: st.firstx = buf.nlines - st.rows;
1056:
1057: repaint(true);
1058: }
1059:
1060: /**
1061: * Ensure that maximum of the given region is visible.
1062: * <p>
1063: * If the given region is visible within the screen nothing happens.
1064: * If the region is bigger than the screen height, first line of the region
1065: * will be displayed in first line of screen and end of region
1066: * won't be displayed.
1067: * If the region is bigger than the half of screen height, last line
1068: * of the region will be displayed in the last line of the screen.
1069: * Otherwise the region will start approximately in mid-sceeen
1070: */
1071: public void possiblyNormalize(ActiveRegion region) {
1072: if (region == null)
1073: return;
1074:
1075: BCoord bbegin = region.begin.toBCoord(firsta);
1076: BCoord bend = region.end.toBCoord(firsta);
1077: if (bbegin.row >= st.firstx && bend.row < st.firstx + st.rows)
1078: return;
1079:
1080: if (bend.row - bbegin.row + 1 >= st.rows)
1081: // region has more rows then screen
1082: st.firstx = bbegin.row;
1083: else {
1084: st.firstx = bbegin.row - (st.rows / 2);
1085: if (st.firstx < 0)
1086: st.firstx = 0;
1087: else if (st.firstx + st.rows > buf.nlines)
1088: st.firstx = buf.nlines - st.rows;
1089: else if (st.firstx + st.rows <= bend.row)
1090: // display also end of region
1091: st.firstx = bend.row - st.rows + 1;
1092: }
1093:
1094: repaint(true);
1095: }
1096:
1097: /**
1098: * If the given coordinate is visible within the screen returns true,
1099: * otherwise returns false.
1100: */
1101: public boolean isCoordVisible(Coord target) {
1102: BCoord btarget = target.toBCoord(firsta);
1103: if (btarget.row >= st.firstx
1104: && btarget.row < st.firstx + st.rows)
1105: return true;
1106: else
1107: return false;
1108: }
1109:
1110: private void possiblyScrollOnInput() {
1111: if (!scroll_on_input)
1112: return;
1113:
1114: // vertical (row) dimension
1115: if (st.cursor.row >= st.firstx
1116: && st.cursor.row < st.firstx + st.rows) {
1117: ;
1118: } else {
1119: st.firstx = buf.nlines - st.rows;
1120: repaint(true);
1121: }
1122: }
1123:
1124: /*
1125: * Horizontal scrolling to track the cursor.
1126: *
1127: * The view/buffer etc calculations, performed by possiblyHScroll(), are not
1128: * terribly complex, but they do depend on up-to-date cursor position.
1129: * If we were to do all this work on every character received we would
1130: * get some slowdown, but worse if say a large file with long lines is
1131: * being 'cat'ed Term will end up scrolling left and right. It would be
1132: * rather annoying.
1133: * But attempting to do the scrolling on typed input isn't going to do
1134: * because (because of non-local echoing) the cursor isn't yet updated when
1135: * a key has been pressed.
1136: * Additionally, a key may result in more than character being echoed.
1137: * for example, an ENTER might result in a "\n\r", a TAB in 8 ' 's,
1138: * and a '\b' in a "\b \b" sequence so a single flag won't do.
1139: * Instead we use a count, 'hscroll_count' which says: attempt
1140: * possiblyHScroll() on the next <n> received characters. It may happen
1141: * there there is a lot of keyboard input and very little output. In
1142: * such cases 'hscroll_count' will start accumulating a defecit. To
1143: * counter this we reset the count to 8 on "newline" type stuff.
1144: */
1145:
1146: private int hscroll_count = 0;
1147:
1148: private void hscrollReset(char c) {
1149: ckEventDispatchThread();
1150: // OLD NPE-x synchronized(hscroll_lock)
1151: {
1152: if (c == '\n' || c == '\r')
1153: hscroll_count = 8;
1154: else
1155: hscroll_count += 8;
1156: }
1157: }
1158:
1159: private void possiblyHScroll() {
1160:
1161: // decide if we actually need to do it
1162:
1163: if (!horizontally_scrollable)
1164: return;
1165:
1166: ckEventDispatchThread();
1167: // OLD NPE-x synchronized(hscroll_lock)
1168: {
1169: if (hscroll_count > 0) {
1170: hscroll_count--;
1171: } else {
1172: return;
1173: }
1174: }
1175:
1176: /* DEBUG
1177: System.out.println("Checking hscroll cursor.col " + st.cursor.col + // NOI18N
1178: " firsty " + st.firsty // NOI18N
1179: " visibleCols " + buf.visibleCols()); // NOI18N
1180: */
1181:
1182: // horizontal (col) dimension
1183: if (st.cursor.col >= st.firsty + buf.visibleCols()) {
1184: /* DEBUG
1185: System.out.println("Need to scroll right"); // NOI18N
1186: */
1187: st.firsty = st.cursor.col - buf.visibleCols() + 1;
1188:
1189: // Expand our idea of column count so that if the cursor is
1190: // at the end the scrollbar allows us to scroll to it.
1191: // This is a bit unusual in that if we type stuff right up to the
1192: // last visible column and return the hscrollbar will display this
1193: // one extra column even though nothing was ever typed in it.
1194: // This is particularly annoying in 'vi' with right-butting lines.
1195: // In the future SHOULD think up of something "smart".
1196: buf.noteColumn(st.cursor.col + 1);
1197:
1198: repaint(true);
1199:
1200: } else if (st.cursor.col - buf.visibleCols() < st.firsty) {
1201: /* DEBUG
1202: System.out.println("Need to scroll left"); // NOI18N
1203: */
1204: st.firsty = st.cursor.col - buf.visibleCols() + 1;
1205: if (st.firsty < 0)
1206: st.firsty = 0;
1207: else
1208: repaint(true);
1209: }
1210: }
1211:
1212: /**
1213: * Set the display attribute for characters printed from now on.
1214: * <p>
1215: * Unrecognized values silently ignored.
1216: */
1217:
1218: public void setAttribute(int value) {
1219: st.attr = Attr.setAttribute(st.attr, value);
1220: }
1221:
1222: /**
1223: * Set or unset the display attribute for characters from 'begin' to 'end'
1224: * inclusive to 'value' depending on the value of 'on'.
1225: * <p>
1226: * Will cause a repaint.
1227: */
1228: public void setCharacterAttribute(Coord begin, Coord end,
1229: final int value, final boolean on) {
1230:
1231: visitLines(begin, end, false, new LineVisitor() {
1232: public boolean visit(Line l, int brow, int bcol, int ecol) {
1233: l.setCharacterAttribute(bcol, ecol, value, on);
1234: return true;
1235: }
1236: });
1237:
1238: repaint(false);
1239: }
1240:
1241: /*
1242: * Set the glyph and line background colors for the line the cursor is on.
1243: * <p>
1244: * Will not repaint.
1245: */
1246: public void setGlyph(int glyph_id, int background_id) {
1247: Line l = cursor_line();
1248: l.glyph_glyph = glyph_id;
1249: l.glyph_rendition = background_id;
1250: }
1251:
1252: /*
1253: * Set the glyph and line background colors for the give line.
1254: * <p>
1255: * Will repaint.
1256: */
1257: public void setRowGlyph(int row, int glyph_id, int background_id) {
1258: Coord c = new Coord();
1259: c.row = row;
1260: c.col = 0;
1261: BCoord b = c.toBCoord(firsta);
1262: Line l = buf.lineAt(b.row);
1263: if (l == null)
1264: return;
1265: l.glyph_glyph = glyph_id;
1266: l.glyph_rendition = background_id;
1267:
1268: possibly_repaint(false);
1269: }
1270:
1271: /**
1272: * Adjust lines when the widget is resized (and also at construction time)
1273: * <p>
1274: * When growing makes sure that everything in the screen is backed up
1275: * by a buffer Line. When shrinking removes lines from the top or the
1276: * bottom as appropriate.
1277: * <p>
1278: * In other words implements the xterm resizeGravity=SouthWest semantics.
1279: * which roughly says: When resizing keep the line that was at the bottom,
1280: * _at_ the bottom.
1281: */
1282:
1283: private void adjust_lines(int delta_row) {
1284:
1285: if (delta_row > 0) {
1286: // attempt to scroll
1287: st.firstx -= delta_row;
1288:
1289: // SHOULD eliminate the loop and move the work to Buffer
1290: while (st.firstx < 0) {
1291: buf.appendLine();
1292: st.firstx++;
1293: }
1294:
1295: } else if (delta_row < 0) {
1296: // we shrunk
1297: int orows = st.rows - delta_row; // reconstruct orows
1298:
1299: // First attempt to remove lines from the bottom of the buffer.
1300: // This comes into play mostly when you have just started Term
1301: // and have but few lines near the top and nothing in history
1302: // so you really can't scroll.
1303:
1304: // How many lines can we trim at the bottom before we eat
1305: // into the cursor?
1306: // Really weird I seem to get the same results regardless of
1307: // whether I use orows or buf.nlines. SHOULD investigate more.
1308:
1309: int allowed = buf.nlines - st.cursor.row - 1;
1310:
1311: if (allowed < 0)
1312: allowed = 0;
1313:
1314: int delete_from_bottom;
1315: if (allowed > -delta_row) {
1316: // no need to scroll, we accomodate the whole resize by
1317: // removing lines from the bottom
1318: delete_from_bottom = -delta_row;
1319: } else {
1320: // delete as much as we're allowed ...
1321: delete_from_bottom = allowed;
1322: // ... and scroll for the rest
1323: st.firstx += (-delta_row - allowed);
1324: }
1325:
1326: // SHOULD eliminate the loop and move the work to Buffer
1327: while (delete_from_bottom-- > 0) {
1328: buf.removeLineAt(buf.nlines - 1);
1329: }
1330:
1331: }
1332: // printStats("From delta_row of " + delta_row); // NOI18N
1333:
1334: adjust_scrollbar();
1335: }
1336:
1337: /**
1338: * returns curent history size of buffer.
1339: */
1340: public int getHistoryBuffSize() {
1341: return buf.nlines - st.rows;
1342: }
1343:
1344: private void limit_lines() {
1345:
1346: /*
1347: * Make sure we don't exceed the buffer size limit historySize.
1348: * This implements the vanishing of lines from the beginning of history.
1349: */
1350:
1351: if (anchored)
1352: return;
1353:
1354: int history = buf.nlines - st.rows;
1355: if (history < 0)
1356: history = 0;
1357:
1358: int toremove = history - history_size;
1359: if (toremove > 0) {
1360: int charsRemoved = buf.removeLinesAt(0, toremove);
1361:
1362: charsInPrehistory += charsRemoved;
1363:
1364: // relocate all row indices
1365: st.adjust(-toremove);
1366:
1367: firsta += toremove;
1368:
1369: // cull any regions that are no longer in history
1370: if (++cull_count % cull_frequency == 0) {
1371: /* DEBUG
1372: System.out.println("Culling regions ..."); // NOI18N
1373: */
1374: region_manager.cull(firsta);
1375: }
1376:
1377: // our absolute coordinates are about to wrap
1378: if (firsta + buf.nlines >= modulo) {
1379: int old_firsta = firsta;
1380: firsta = 0;
1381:
1382: sel.relocate(old_firsta, firsta);
1383: region_manager.relocate(old_firsta, firsta);
1384: }
1385: }
1386:
1387: // deal with selection moving out of the buffer
1388: sel.adjust(firsta, 0, firsta + buf.nlines);
1389:
1390: adjust_scrollbar();
1391: }
1392:
1393: /**
1394: * Scroller is used to implement selection auto-scrolling.
1395: * <p>
1396: * When a selection drag moves out of the window a scroller object/thread
1397: * is started to periodically scroll and extend the selection.
1398: */
1399: private class Scroller extends Thread {
1400: public final static int UP = 1 << 1;
1401: public final static int DOWN = 1 << 2;
1402: public final static int LEFT = 1 << 3;
1403: public final static int RIGHT = 1 << 4;
1404:
1405: private int direction;
1406:
1407: public Scroller(int direction) {
1408: this .direction = direction;
1409: }
1410:
1411: private boolean extend() {
1412:
1413: ckEventDispatchThread();
1414: // OLD NPE-x synchronized (Term.this)
1415: {
1416:
1417: // Selection might wink out while we're auto scrolling.
1418: // Since we use 'sel.sel_extent' further below we
1419: // synchronize.
1420: if (sel.sel_extent == null)
1421: return false;
1422:
1423: BCoord x = sel.sel_extent.toBCoord(firsta);
1424: BCoord v = toViewCoord(x);
1425: int r = v.row;
1426: int c = v.col;
1427:
1428: if ((direction & DOWN) == DOWN) {
1429: lineDown(1);
1430: r = st.rows - 1;
1431: c = buf.totalCols();
1432: } else if ((direction & UP) == UP) {
1433: lineUp(1);
1434: r = 0;
1435: c = 0;
1436: } else {
1437: BCoord vc2 = toViewCoord(drag_point);
1438: r = vc2.row;
1439: c = vc2.col;
1440: }
1441:
1442: if ((direction & LEFT) == LEFT) {
1443: st.firsty--;
1444: if (st.firsty < 0)
1445: st.firsty = 0;
1446: c = 0;
1447: } else if ((direction & RIGHT) == RIGHT) {
1448: st.firsty++;
1449: int limit = buf.totalCols() - buf.visibleCols();
1450: if (limit < 0)
1451: limit = 0;
1452: if (st.firsty > limit)
1453: st.firsty = limit;
1454: c = st.firsty + buf.visibleCols();
1455: }
1456:
1457: BCoord vc = new BCoord(r, c);
1458: BCoord bc = toBufCoords(vc);
1459: sel.track(new Coord(bc, firsta));
1460: repaint(true);
1461: return true;
1462: }
1463: }
1464:
1465: public void run() {
1466: while (true) {
1467:
1468: /* DEBUG
1469: System.out.print("Scrolling "); // NOI18N
1470: if ((direction & UP) == UP)
1471: System.out.print("UP "); // NOI18N
1472: if ((direction & DOWN) == DOWN)
1473: System.out.print("DOWN "); // NOI18N
1474: if ((direction & LEFT) == LEFT)
1475: System.out.print("LEFT "); // NOI18N
1476: if ((direction & RIGHT) == RIGHT)
1477: System.out.print("RIGHT "); // NOI18N
1478: System.out.println();
1479: */
1480:
1481: extend();
1482:
1483: try {
1484: // See issue 36404
1485: sleep(10);
1486: } catch (InterruptedException x) {
1487: break;
1488: }
1489: }
1490: /* DEBUG
1491: System.out.println("Done with Scrolling"); // NOI18N
1492: */
1493: }
1494: }
1495:
1496: private Scroller scroller;
1497: private Point drag_point;
1498:
1499: private int scrolling_direction = 0;
1500:
1501: private void scroll_to(int direction, MouseEvent e) {
1502: if (direction == scrolling_direction) {
1503: if (direction == 0) {
1504: // We're moving inside the view
1505: BCoord bc = toBufCoords(toViewCoord(e.getPoint()));
1506: sel.track(new Coord(bc, firsta));
1507: repaint(false);
1508: }
1509: return;
1510: }
1511:
1512: // we changed direction
1513:
1514: // get rid of the old scroller
1515: if (scroller != null) {
1516: scroller.interrupt();
1517: scroller = null;
1518: }
1519:
1520: if (direction == 0) {
1521: BCoord bc = toBufCoords(toViewCoord(e.getPoint()));
1522: sel.track(new Coord(bc, firsta));
1523: repaint(false);
1524: } else {
1525: scroller = new Scroller(direction);
1526: scroller.start();
1527: }
1528:
1529: scrolling_direction = direction;
1530: }
1531:
1532: /**
1533: * Constructor
1534: */
1535: public Term() {
1536: st.rows = 25;
1537: st.firstx = 0;
1538: st.firsty = 0;
1539:
1540: standard_color[0] = Color.black;
1541: standard_color[1] = Color.red;
1542: standard_color[2] = Color.green;
1543: standard_color[3] = Color.yellow;
1544: standard_color[4] = Color.blue;
1545: standard_color[5] = Color.magenta;
1546: standard_color[6] = Color.cyan;
1547: standard_color[7] = Color.white;
1548:
1549: custom_color[0] = Color.black;
1550: custom_color[1] = Color.black;
1551: custom_color[2] = Color.black;
1552: custom_color[3] = Color.black;
1553: custom_color[4] = Color.black;
1554: custom_color[5] = Color.black;
1555: custom_color[6] = Color.black;
1556: custom_color[7] = Color.black;
1557:
1558: Font f = UIManager.getFont("TextArea.font"); //NOI18N
1559: if (f == null) {
1560: // on, e.g., GTK L&F
1561: f = UIManager.getFont("controlFont"); //NOI18N
1562: }
1563:
1564: if (f != null) {
1565: setFont(new Font("Monospaced", Font.PLAIN, f.getSize() + 1)); // NOI18N
1566: } else {
1567: setFont(new Font("Monospaced", Font.PLAIN, 12)); // NOI18N
1568: }
1569:
1570: BorderLayout layout = new BorderLayout();
1571: setLayout(layout);
1572: screen = new Screen(this , (buf.visibleCols() * metrics.width
1573: + glyph_gutter_width + debug_gutter_width), st.rows
1574: * metrics.height);
1575:
1576: add(BorderLayout.CENTER, screen);
1577: screen.setBackground(null); // inherit from this
1578: screen
1579: .setCursor(Cursor
1580: .getPredefinedCursor(Cursor.TEXT_CURSOR));
1581:
1582: adjust_lines(st.rows);
1583: st.cursor.row = 0;
1584:
1585: vscroll_bar = new JScrollBar(JScrollBar.VERTICAL);
1586: add(BorderLayout.EAST, vscroll_bar);
1587: vscroll_bar.setValues(st.firstx, st.rows - 1, 0, st.rows);
1588: vscroll_bar.setUnitIncrement(1);
1589: vscroll_bar.setEnabled(true);
1590: vscroll_bar.setFocusable(false);
1591:
1592: vscroll_bar.addAdjustmentListener(new AdjustmentListener() {
1593:
1594: public void adjustmentValueChanged(AdjustmentEvent e) {
1595: JScrollBar sb = (JScrollBar) e.getAdjustable();
1596: switch (e.getAdjustmentType()) {
1597: case AdjustmentEvent.TRACK:
1598:
1599: int pos = e.getValue();
1600:
1601: // adjustmentValueChanged() will get called when we
1602: // call setValues() on the scrollbar.
1603: // This test sort-of filters that out
1604: if (pos == st.firstx)
1605: break;
1606:
1607: // deal with the user moving the thumb
1608: st.firstx = pos;
1609: /* DEBUG
1610: if (st.firstx + st.rows > buf.nlines) {
1611: Thread.dumpStack();
1612: printStats("bad scroll value"); // NOI18N
1613: }
1614: */
1615: repaint(false);
1616: break;
1617:
1618: default:
1619: /* DEBUG
1620: System.out.println("adjustmentValueChanged: " + e); // NOI18N
1621: */
1622: break;
1623: }
1624: }
1625: });
1626:
1627: hscroll_bar = new JScrollBar(JScrollBar.HORIZONTAL);
1628: hscroll_bar.setValues(st.firsty, buf.totalCols() - 1, 0, buf
1629: .totalCols());
1630: hscroll_bar.setUnitIncrement(1);
1631: hscroll_bar.setEnabled(true);
1632: hscroll_bar.setFocusable(false);
1633:
1634: hscroll_wrapper = new ScrollWrapper(hscroll_bar);
1635: add(BorderLayout.SOUTH, hscroll_wrapper);
1636:
1637: hscroll_bar.addAdjustmentListener(new AdjustmentListener() {
1638:
1639: public void adjustmentValueChanged(AdjustmentEvent e) {
1640: JScrollBar sb = (JScrollBar) e.getAdjustable();
1641: switch (e.getAdjustmentType()) {
1642: case AdjustmentEvent.TRACK:
1643:
1644: int pos = e.getValue();
1645:
1646: // adjustmentValueChanged() will get called when we
1647: // call setValues() on the scrollbar.
1648: // This test sort-of filters that out
1649: if (pos == st.firsty)
1650: break;
1651:
1652: // deal with the user moving the thumb
1653: st.firsty = pos;
1654: repaint(false);
1655: break;
1656:
1657: default:
1658: /* DEBUG
1659: System.out.println("adjustmentValueChanged: " + e); // NOI18N
1660: */
1661: break;
1662: }
1663: }
1664: });
1665:
1666: screen.addComponentListener(new ComponentAdapter() {
1667:
1668: public void componentResized(ComponentEvent e) {
1669: Dimension size = screen.getSize();
1670: sizeChanged(size.width, size.height);
1671:
1672: // workaround for java bug 4711314 where a repaint()
1673: // appears before componentResized() causing issue 25313
1674: repaint(true);
1675: }
1676:
1677: });
1678:
1679: screen.addKeyListener(new KeyListener() {
1680:
1681: // We consume all events so no additional side-effects take place.
1682: // Examples:
1683: // - We don't want TAB to move focus away. (see more below)
1684: // - We don't want ^p to bring up a printer diaolog
1685: // - etc
1686:
1687: // HACK. Java maps [Return] to 10 as opposed to 13. This is due
1688: // to it's Windows-chauvinism, a 'reality' pointed out to me by
1689: // one of the AWT people.
1690: // The keycode doesn't make it to keyTyped, so for now we detect
1691: // a Return by capturing press/releases of VK_ENTER and
1692: // use a flag.
1693: boolean saw_return;
1694:
1695: public void keyTyped(KeyEvent e) {
1696: char c = e.getKeyChar();
1697:
1698: if (debugKeys())
1699: System.out.println("term: keyTyped: " + e); // NOI18N
1700:
1701: if (read_only)
1702: return;
1703:
1704: if (c == 10 && saw_return) {
1705: saw_return = false;
1706: c = (char) 13;
1707: }
1708:
1709: if (passOn && maybeConsume(e)) {
1710: on_char(c);
1711: possiblyScrollOnInput();
1712: }
1713:
1714: hscrollReset(c);
1715: }
1716:
1717: public void keyPressed(KeyEvent e) {
1718: /* DEBUG
1719: System.out.println("keyPressed " + e); // NOI18N
1720: */
1721:
1722: switch (e.getKeyCode()) {
1723: case KeyEvent.VK_COPY:
1724: copyToClipboard();
1725: break;
1726: case KeyEvent.VK_PASTE:
1727: pasteFromClipboard();
1728: break;
1729: case KeyEvent.VK_ENTER:
1730: saw_return = true;
1731: break;
1732: case KeyEvent.VK_PAGE_UP:
1733: if (e.getModifiers() == Event.CTRL_MASK) {
1734: pageLeft(1);
1735: } else {
1736: pageUp(1);
1737: }
1738: break;
1739: case KeyEvent.VK_PAGE_DOWN:
1740: if (e.getModifiers() == Event.CTRL_MASK) {
1741: pageRight(1);
1742: } else {
1743: pageDown(1);
1744: }
1745: break;
1746:
1747: case KeyEvent.VK_UP:
1748: if (e.getModifiers() == Event.CTRL_MASK) {
1749: lineUp(1);
1750: }
1751: break;
1752: case KeyEvent.VK_DOWN:
1753: if (e.getModifiers() == Event.CTRL_MASK) {
1754: lineDown(1);
1755: }
1756: break;
1757: }
1758: passOn = maybeConsume(e);
1759: }
1760:
1761: public void keyReleased(KeyEvent e) {
1762: /* DEBUG
1763: System.out.println("keyReleased"); // NOI18N
1764: */
1765:
1766: if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1767: /* DEBUG
1768: System.out.println("keyReleased VK_ENTER"); // NOI18N
1769: */
1770: saw_return = false;
1771: }
1772: maybeConsume(e);
1773: }
1774: });
1775:
1776: screen.addMouseMotionListener(new MouseMotionListener() {
1777:
1778: public void mouseDragged(MouseEvent e) {
1779: /* DEBUG
1780: System.out.println("mouseDragged"); // NOI18N
1781: */
1782:
1783: // akemr - fix of #25648
1784: if (SwingUtilities.isRightMouseButton(e))
1785: return;
1786:
1787: if (left_down_point != null) {
1788: BCoord bc = toBufCoords(toViewCoord(left_down_point));
1789: sel.track(new Coord(bc, firsta));
1790: left_down_point = null;
1791: }
1792: drag_point = e.getPoint();
1793: /* DEBUG
1794: System.out.println("mouseDrag: " + drag_point); // NOI18N
1795: */
1796:
1797: int scroll_direction = 0;
1798:
1799: if (drag_point.y < 0) {
1800: scroll_direction |= Scroller.UP;
1801: } else if (drag_point.y > screen.getSize().height) {
1802: scroll_direction |= Scroller.DOWN;
1803: }
1804:
1805: if (drag_point.x < 0) {
1806: scroll_direction |= Scroller.LEFT;
1807: } else if (drag_point.x > screen.getSize().width) {
1808: scroll_direction |= Scroller.RIGHT;
1809: }
1810:
1811: scroll_to(scroll_direction, e);
1812: }
1813:
1814: public void mouseMoved(MouseEvent e) {
1815: /* DEBUG
1816: Point p = (Point) e.getPoint().clone();
1817: BCoord bc = toBufCoords(toViewCoord(p));
1818: Coord c = new Coord(bc, firsta);
1819: Extent x = sel.getExtent();
1820: if (x == null) {
1821: System.out.println("sel intersect: no extent"); // NOI18N
1822: } else {
1823: System.out.println("sel intersect: " + // NOI18N
1824: (x.intersects(c.row, c.col)? "intersects" // NOI18N
1825: "doesn't intersect")); // NOI18N
1826: }
1827: */
1828: }
1829: });
1830:
1831: addMouseWheelHandler(screen, vscroll_bar);
1832:
1833: screen.addMouseListener(new MouseListener() {
1834:
1835: public void mouseClicked(MouseEvent e) {
1836: BCoord bcoord = toBufCoords(toViewCoord(e.getPoint()));
1837:
1838: if (SwingUtilities.isLeftMouseButton(e)) {
1839: /* DEBUG
1840: System.out.println("LEFT click"); // NOI18N
1841: */
1842: if (click_to_type)
1843: requestFocus();
1844:
1845: } else if (SwingUtilities.isMiddleMouseButton(e)) {
1846: /* DEBUG
1847: System.out.println("MIDDLE click"); // NOI18N
1848: System.out.println("Selection: '" + sel.sel_get() + "'"); // NOI18N
1849: */
1850: pasteFromSelection();
1851: }
1852: }
1853:
1854: public void mousePressed(MouseEvent e) {
1855: /* DEBUG
1856: System.out.println("mousePressed "+e.getModifiers()); // NOI18N
1857: */
1858:
1859: if (SwingUtilities.isLeftMouseButton(e)) {
1860:
1861: if (e.isShiftDown()) {
1862: // JLF/dtterm selection extension
1863: // Actually it's _addition_ so SHOULD enhance Sel
1864: // to do that instead.
1865: BCoord bc = toBufCoords(toViewCoord(e
1866: .getPoint()));
1867: if (sel.extend(new Coord(bc, firsta))) {
1868: fireSelectionExtentChanged();
1869: repaint(false);
1870: }
1871: return;
1872: }
1873:
1874: if (sel.cancel(false))
1875: repaint(false);
1876:
1877: if (e.getClickCount() == 1) {
1878: left_down_point = (Point) e.getPoint().clone();
1879:
1880: } else if (e.getClickCount() == 2) {
1881: BCoord bcoord = toBufCoords(toViewCoord(e
1882: .getPoint()));
1883: BExtent word = buf.find_word(word_delineator,
1884: bcoord);
1885: sel.select_word(word.toExtent(firsta));
1886: repaint(false);
1887:
1888: } else if (e.getClickCount() == 3) {
1889: BCoord bcoord = toBufCoords(toViewCoord(e
1890: .getPoint()));
1891: sel.select_line(new Coord(bcoord, firsta));
1892: repaint(false);
1893:
1894: }
1895:
1896: fireSelectionExtentChanged();
1897: }
1898: }
1899:
1900: public void mouseReleased(MouseEvent e) {
1901: /* DEBUG
1902: System.out.println("mouseReleased"); // NOI18N
1903: */
1904: if (SwingUtilities.isLeftMouseButton(e)) {
1905:
1906: if (e.isShiftDown()) {
1907: // we're extending
1908: return;
1909: }
1910:
1911: if (scroller != null) {
1912: scroller.interrupt();
1913: scroller = null;
1914: }
1915:
1916: // Don't put in the selection if we didn't move
1917: // When left button goes down 'left_down_point' is set.
1918: // As soon as we move the mouse it gets reset.
1919: if (left_down_point == null)
1920: sel.done( /* OLD false */);
1921: left_down_point = null;
1922: }
1923: }
1924:
1925: /*
1926: * Implement follow-mouse focus
1927: */
1928: public void mouseEntered(MouseEvent e) {
1929: /* DEBUG
1930: System.out.println("mouseEntered"); // NOI18N
1931: */
1932: if (!click_to_type) {
1933: requestFocus();
1934: }
1935: }
1936:
1937: public void mouseExited(MouseEvent e) {
1938: /* DEBUG
1939: System.out.println("mouseExited"); // NOI18N
1940: */
1941: }
1942:
1943: });
1944:
1945: screen.addFocusListener(new FocusListener() {
1946: public void focusGained(FocusEvent e) {
1947: /* DEBUG
1948: System.out.println("Focus gained >>>>>>>>>>>>>>>>>>>>>>>"); // NOI18N
1949: */
1950: has_focus = true;
1951: repaint(false);
1952: }
1953:
1954: public void focusLost(FocusEvent e) {
1955: /* DEBUG
1956: Component o = e.getOppositeComponent();
1957: System.out.println("Focus lost <<<<<<<<<<<<<<<<<<<<<<<" + o);
1958: */
1959:
1960: has_focus = false;
1961: repaint(false);
1962: }
1963: });
1964: }
1965:
1966: /*
1967: * Print interesting statistics and facts about this Term
1968: */
1969: public void printStats(String message) {
1970: if (message != null)
1971: System.out.println(message);
1972:
1973: if (message != null)
1974: System.out.print("\t"); // NOI18N
1975: buf.printStats();
1976:
1977: if (message != null)
1978: System.out.print("\t"); // NOI18N
1979: System.out.println("rows " + st.rows + // NOI18N
1980: " v cols " + buf.visibleCols() + // NOI18N
1981: " t cols " + buf.totalCols() + // NOI18N
1982: " history " + history_size + // NOI18N
1983: " firstx " + st.firstx + // NOI18N
1984: " firsty " + st.firsty + // NOI18N
1985: " firsta " + firsta + // NOI18N
1986: " gutter " + glyph_gutter_width); // NOI18N
1987:
1988: if (message != null)
1989: System.out.print("\t"); // NOI18N
1990: System.out.println("Cursor " + st.cursor + // NOI18N
1991: " topMargin " + topMargin() + // NOI18N
1992: " botMargin " + botMargin()); // NOI18N
1993:
1994: if (message != null)
1995: System.out.print("\t"); // NOI18N
1996: System.out.println("putChar " + n_putchar + // NOI18N
1997: " putChars " + n_putchars + // NOI18N
1998: " linefeeds " + n_linefeeds + // NOI18N
1999: " repaint " + n_repaint + // NOI18N
2000: " paint " + n_paint); // NOI18N
2001: }
2002:
2003: public void paste() {
2004: pasteFromClipboard();
2005: }
2006:
2007: private void pasteHelp(Clipboard cb) {
2008: if (read_only)
2009: return;
2010:
2011: Transferable contents = cb.getContents(screen);
2012: if (contents == null)
2013: return;
2014:
2015: if (!contents.isDataFlavorSupported(DataFlavor.stringFlavor))
2016: return;
2017:
2018: try {
2019: String string;
2020: string = (String) contents
2021: .getTransferData(DataFlavor.stringFlavor);
2022: /* DEBUG
2023: System.out.println("System selection contains '" + string + "'"); // NOI18N
2024: */
2025: char ca[] = string.toCharArray();
2026: sendChars(ca, 0, ca.length);
2027: } catch (Exception e) {
2028: ;
2029: }
2030: }
2031:
2032: /**
2033: * Transfer contents of selection to Terms input stream.
2034: * <p>
2035: * The pasted content is sent through sendChars.
2036: * <br>
2037: * The paste will silently fail if:
2038: * <ul>
2039: * <li> The selection has null contents.
2040: * <li> The selections data flavor is not string.
2041: * </ul>
2042: */
2043: public void pasteFromSelection() {
2044: /* DEBUG
2045: System.out.println("Term: pasteFromSelection()"); // NOI18N
2046: */
2047: if (systemSelection == null)
2048: return;
2049: pasteHelp(systemSelection);
2050: }
2051:
2052: /**
2053: * Transfer contents of clipboard to Terms input stream.
2054: * <p>
2055: * The pasted content is sent through sendChars.
2056: * <br>
2057: * The paste will silently fail if:
2058: * <ul>
2059: * <li> The selection has null contents.
2060: * <li> The selections data flavor is not string.
2061: * </ul>
2062: */
2063: public void pasteFromClipboard() {
2064: pasteHelp(systemClipboard);
2065: }
2066:
2067: /**
2068: * Transfer selected text into clipboard.
2069: */
2070: public void copy() {
2071: copyToClipboard();
2072: }
2073:
2074: /**
2075: * Transfer selected text into clipboard.
2076: */
2077: public void copyToClipboard() {
2078: String text = sel.getSelection();
2079: if (text != null) {
2080: StringSelection ss = new StringSelection(text);
2081: systemClipboard.setContents(ss, sel);
2082: }
2083: }
2084:
2085: /**
2086: * Transfer selected text into selection.
2087: */
2088: public void copyToSelection() {
2089: /* DEBUG
2090: System.out.println("Term: copyToSelection()"); // NOI18N
2091: */
2092:
2093: if (systemSelection == null)
2094: return;
2095: String text = sel.getSelection();
2096: StringSelection ss = new StringSelection(text); // null 'text' is OK
2097: systemSelection.setContents(ss, sel);
2098: }
2099:
2100: private static Extent old_extent = null;
2101:
2102: void fireSelectionExtentChanged() {
2103: Extent new_extent = getSelectionExtent();
2104: firePropertyChange("selectionExtent", old_extent, new_extent); // NOI18N
2105: old_extent = new_extent;
2106: }
2107:
2108: /**
2109: * Set the number of character rows in the screen.
2110: * <p>
2111: * See setRowsColumns() for some additional important information.
2112: */
2113: public void setRows(int rows) {
2114: if (old_rows == -1)
2115: old_rows = st.rows;
2116: st.rows = rows;
2117:
2118: updateScreenSize();
2119: }
2120:
2121: /**
2122: * Get the number of character rows in the screen
2123: */
2124: public int getRows() {
2125: return st.rows;
2126: }
2127:
2128: /**
2129: * Set the number of character columns in the screen.
2130: * <p>
2131: * See setRowsColumns() for some additional important information.
2132: */
2133: public void setColumns(int cols) {
2134: buf.setVisibleCols(cols);
2135: updateScreenSize();
2136: }
2137:
2138: /**
2139: * Trampoline from Line.ensureCapacity() to Buffer.noteColumn()
2140: */
2141: void noteColumn(Line l, int capacity) {
2142: int vcapacity = l.bufToCell(metrics, capacity);
2143: buf.noteColumn(vcapacity);
2144: }
2145:
2146: /**
2147: * Trampoline from Line to MyFontMetrics.checkForMultiCell()
2148: */
2149: void checkForMultiCell(char c) {
2150: metrics.checkForMultiCell(c);
2151: }
2152:
2153: /**
2154: * Get the number of character columns in the screen
2155: */
2156: public int getColumns() {
2157: return buf.visibleCols();
2158: }
2159:
2160: /**
2161: * Simultaneously set the number of character rows and columns.
2162: * <p>
2163: * A Term is a composite widget made of a contained screen (getScreen())
2164: * and a scrollbar. It is not a JScrollPane. You're actually setting
2165: * the screen size here.
2166: * <p>
2167: * Setting the column size also sets the width of the buffer. This doesn't
2168: * alter the length (column at which lines were wrapped) of past lines,
2169: * but only new additional lines. For example, if you set columns to
2170: * 20, print a bunch of lines that wrap, then resize to 80 columns, all
2171: * the lines that were wrapped to 20 will stay wrapped that way. This is
2172: * consistent with xterm behaviour.
2173: * <p>
2174: * If this Term is embedded in a component with a layout manager that is
2175: * set up to accept child resizes gracefully this widget will be resized
2176: * as expected.
2177: * <p>
2178: * Alternatively if this Term is embedded in a Window (like JFrame)
2179: * the window will need to be re-pack()ed as it does not accomodate it's
2180: * childrens size changes. This has to be done by the application using
2181: * Term. The best way to do this is to add a TermListener() and call pack()
2182: * on the appropriate window when a resize notification is fired.
2183: */
2184:
2185: public void setRowsColumns(int rows, int columns) {
2186:
2187: // Combine code in setRows() and setColumns() so we factor
2188: // the calls to updateScreenSize().
2189:
2190: if (old_rows == -1)
2191: old_rows = st.rows;
2192: st.rows = rows;
2193: buf.setVisibleCols(columns);
2194:
2195: updateScreenSize();
2196: }
2197:
2198: /**
2199: * Governs whether Term will round down resize requests to character
2200: * cell size.
2201: * <p>
2202: * Sizes are usually set by containers' layout managers. If rounding is
2203: * enabled Term will attempt to adjust the size to an even multiple
2204: * of the character cell size.
2205: * <p>
2206: * The layout manager might not neccesarily honor the rounded size.
2207: * The situation can be somewhat improved by making sure that the ultimate
2208: * container is re-packed as described in {@link #setRowsColumns(int, int)}.
2209: */
2210: public void setSizeRounded(boolean size_rounded) {
2211: this .size_rounded = size_rounded;
2212: updateScreenSize();
2213: }
2214:
2215: /**
2216: * Returns true if Term will round down resize requests to character
2217: * cell size.
2218: * <p>
2219: * See {@link #setSizeRounded(boolean)} for more info.
2220: */
2221: public boolean isSizeRounded() {
2222: return size_rounded;
2223: }
2224:
2225: private boolean size_rounded = true;
2226:
2227: /*
2228: * Used to extract the dimensions of the terminal.
2229: * SHOULD replace with getRowsCols and getScreenSize
2230: */
2231: public void fillSizeInfo(Dimension cells, Dimension pixels) {
2232: cells.height = st.rows;
2233: cells.width = buf.visibleCols();
2234: Dimension cpixels = screen.getSize();
2235: pixels.width = cpixels.width - glyph_gutter_width
2236: - debug_gutter_width;
2237: pixels.height = cpixels.height;
2238: }
2239:
2240: /**
2241: * Once the terminal is connected to something, use this function to
2242: * send all Term Listener notifications.
2243: */
2244: protected void updateTtySize() {
2245: if (screen != null) {
2246: Dimension cells = new Dimension(buf.visibleCols(), st.rows);
2247: Dimension pixels = screen.getSize();
2248: fireSizeChanged(cells, pixels);
2249: }
2250: }
2251:
2252: /*
2253: * various coordinate conversion functions
2254: */
2255:
2256: BCoord toViewCoord(BCoord b) {
2257: /*
2258: * Convert from buffer coords to view coords
2259: */
2260: Line l = buf.lineAt(b.row);
2261: if (l != null) { //XXX Hotfix for issue 40189 - Buffer.lineAt() will
2262: //catch an AIOBEE and return null. Probably related
2263: //to Ivan's notes about clipping in Term.paint()
2264: int vc = buf.lineAt(b.row).bufToCell(metrics, b.col);
2265: BCoord v = new BCoord(b.row - st.firstx, vc - st.firsty);
2266: return v;
2267: } else {
2268: return null;
2269: }
2270: }
2271:
2272: Point toPixel(BCoord v) {
2273: /*
2274: * Convert from view coords to pixel coords
2275: */
2276: Point p = new Point(v.col * metrics.width + glyph_gutter_width
2277: + debug_gutter_width, v.row * metrics.height);
2278: return p;
2279: }
2280:
2281: /**
2282: * Convert row/column coords to pixel coords within the widgets
2283: * coordinate system.
2284: * It returns the pixel of the upper left corner of the target cell.
2285: */
2286: public Point toPixel(Coord target) {
2287: BCoord btarget = target.toBCoord(firsta);
2288: return toPixel(btarget);
2289: }
2290:
2291: /**
2292: * Convert pixel coords to view row/column coords (0/0-origin)
2293: */
2294: BCoord toViewCoord(Point p) {
2295: BCoord v = new BCoord(p.y / metrics.height, (p.x
2296: - glyph_gutter_width - debug_gutter_width)
2297: / metrics.width);
2298: v.clip(st.rows, buf.visibleCols());
2299: /* DEBUG
2300: System.out.println("toViewCoord() -> " + v); // NOI18N
2301: */
2302: return v;
2303: }
2304:
2305: BCoord toBufCoords(BCoord v) {
2306: /*
2307: * Convert view row/column coords to buffer row/column coords.
2308: * If the buffer is smaller than the view, map to the last line.
2309: */
2310: int brow = st.firstx + v.row;
2311: if (brow >= buf.nlines)
2312: brow = buf.nlines - 1;
2313: int bc = buf.lineAt(brow).cellToBuf(metrics, st.firsty + v.col);
2314: BCoord b = new BCoord(brow, bc);
2315: /* DEBUG
2316: System.out.println("toBufCoords(" + v + ") -> " + b); // NOI18N
2317: */
2318: return b;
2319: }
2320:
2321: /**
2322: * Convert pixel coords to view (visible area) row/column coords (both
2323: * o-origin).
2324: * <p>
2325: * In the returned Point, x represents the column, y the row.
2326: */
2327: public Point mapToViewRowCol(Point p) {
2328: BCoord c = toViewCoord(p);
2329: return new Point(c.col, c.row);
2330: }
2331:
2332: /**
2333: * Convert pixel coords to (history) buffer row/column coords (both
2334: * 0-origin).
2335: * <p>
2336: * In the returned Point, x represents the column, y the row.
2337: */
2338: public Point mapToBufRowCol(Point p) {
2339: BCoord c = toBufCoords(toViewCoord(p));
2340: return new Point(c.col, c.row);
2341: }
2342:
2343: private Color rendition_to_color(int rendition) {
2344: switch (rendition) {
2345: case 40:
2346: return standard_color[0];
2347: case 41:
2348: return standard_color[1];
2349: case 42:
2350: return standard_color[2];
2351: case 43:
2352: return standard_color[3];
2353: case 44:
2354: return standard_color[4];
2355: case 45:
2356: return standard_color[5];
2357: case 46:
2358: return standard_color[6];
2359: case 47:
2360: return standard_color[7];
2361:
2362: case 58:
2363: return custom_color[0];
2364: case 59:
2365: return custom_color[1];
2366: case 60:
2367: return custom_color[2];
2368: case 61:
2369: return custom_color[3];
2370: case 62:
2371: return custom_color[4];
2372: case 63:
2373: return custom_color[5];
2374: case 64:
2375: return custom_color[6];
2376: case 65:
2377: return custom_color[7];
2378:
2379: default:
2380: return null;
2381: }
2382: }
2383:
2384: private Color actual_foreground;
2385: private Color actual_background;
2386: private boolean check_selection;
2387: private int totcols;
2388:
2389: private void do_run(Graphics g, int yoff, int xoff, int baseline,
2390: int brow, char buf[], Line l, int attr, int rbegin, int rend) {
2391:
2392: /* DEBUG
2393: System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N
2394: */
2395:
2396: int x;
2397: int rlength;
2398: int xlength;
2399:
2400: if (metrics.isMultiCell()) {
2401: int vbegin = l.bufToCell(metrics, rbegin);
2402: int vend = l.bufToCell(metrics, rend + 1) - 1;
2403: x = xoff + (vbegin - st.firsty) * metrics.width;
2404: int vlength = vend - vbegin + 1;
2405: if (vlength <= 0) {
2406: /* DEBUG
2407: System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N
2408: */
2409: return;
2410: }
2411: rlength = rend - rbegin + 1;
2412: xlength = vlength * metrics.width;
2413:
2414: } else {
2415: x = xoff + (rbegin - st.firsty) * metrics.width;
2416: rlength = rend - rbegin + 1;
2417: if (rlength <= 0) {
2418: /* DEBUG
2419: System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N
2420: */
2421: return;
2422: }
2423: xlength = rlength * metrics.width;
2424: }
2425:
2426: boolean reverse = ((attr & Attr.REVERSE) == Attr.REVERSE);
2427: boolean active = ((attr & Attr.ACTIVE) == Attr.ACTIVE);
2428:
2429: // choose background color
2430: Color bg = null;
2431:
2432: if (active) {
2433: bg = active_color;
2434: } else {
2435: bg = backgroundColor(reverse, attr);
2436: }
2437:
2438: if (bg != null) {
2439: // Draw any background
2440: g.setColor(bg);
2441: g.fillRect(x, yoff, xlength, metrics.height
2442: - metrics.leading);
2443: }
2444:
2445: // Set foreground color
2446: Color fg = foregroundColor(reverse, attr);
2447: g.setColor(fg);
2448:
2449: // draw any underscores
2450: if ((attr & Attr.UNDERSCORE) == Attr.UNDERSCORE) {
2451: int h = metrics.height - metrics.leading - 1;
2452: g.drawLine(x, yoff + h, x + xlength, yoff + h);
2453: }
2454:
2455: // Draw the foreground character glyphs
2456: myDrawChars(g, buf, l, rbegin, rlength, x, baseline);
2457:
2458: // Draw fake bold characters by redrawing one pixel to the right
2459: if ((attr & Attr.BRIGHT) == Attr.BRIGHT) {
2460: myDrawChars(g, buf, l, rbegin, rlength, x + 1, baseline);
2461: }
2462: }
2463:
2464: private final Point newp = new Point();
2465:
2466: /*
2467: * Tweak glyph X positions so they fall on cell/grid/column boundries.
2468: */
2469: private void massage_glyphs(GlyphVector gv, int start, int n, Line l) {
2470: Point2D pos0 = gv.getGlyphPosition(0);
2471:
2472: // There's one big assumption here that in a monospaced font all the
2473: // Y placements are identical. So we use the placement for the first
2474: // glyph only.
2475: newp.y = (int) pos0.getY();
2476:
2477: int col = (int) pos0.getX();
2478: for (int gx = 0; gx < n; gx++) {
2479: newp.x = col;
2480: gv.setGlyphPosition(gx, newp);
2481: col += l.width(metrics, start + gx) * metrics.width;
2482: }
2483: }
2484:
2485: /**
2486: * Draw characters in cells.
2487: *
2488: * Fixed width or monospaced fonts implies that the glyphs of all characters
2489: * have the same width. Some non-latin characters (japanese) might have
2490: * glyph widths that are an _integer multiple_ of the latin glyphs. Thus
2491: * cellular (grid based) text widget like this termulator can still place
2492: * all characters nicely. There is a 'C' function wcwidth() which
2493: * ... determines the number of _column_ positions ... and CDE's DtTrem
2494: * ultimately depends on it to place things. (See also Tuthill & Smallberg,
2495: * "Creating worldwide software" PrenticeHall 2nd ed. p98)
2496: *
2497: * Unfortunately the fonts used by Java, even the "monospaced" fonts, do
2498: * not abide by the above convention. I measured a 10pt ja locale latin
2499: * character at 7 pixels wide and a japanese character at 12 pixels wide,
2500: * instead of 14. A similar problem existed with respect to the "unprintbale"
2501: * placeholder square. Until Java 1.4 it used to be 9 or 10 pixels wide!
2502: * The square is fixed, but I"m not sure the above will be anytime soon.
2503: *
2504: * What this means is that Graphics.drawString() when given a mix and match
2505: * of latin and japanese characters will not place them right. Selection
2506: * doesn't work etc.
2507: *
2508: * Nor does Java provide anything resembling wcwidth() so we're rolling
2509: * our own here. That's done in Line.width().
2510: *
2511: * So one approach would be to place each character individually, but it's
2512: * rather slow. Fortunately Java provides a GlyphVector class that allows
2513: * us to tweak the positions of the glyphs. The timing I"ve gotten are
2514: * 50 for one drawChars() per charactr. (SLOWER below)
2515: * 15 using the GlyphVector technique
2516: * 8 using plain drawChars
2517: * Unfortunately GlyphVector's interface leaves a bit to be desired.
2518: * - It does not take a (char [], offset, length) triple and depends
2519: * on the length of the char array passed in. Since our Line char arrays
2520: * have some slop in them we can't pass them directly. Hence the
2521: * "new char[]" and the "System.arraycopy".
2522: * - The interface for getting and setting positions is also a bit
2523: * awkward as you may notice from massage_glyphs().
2524: *
2525: * We SHOULD fall back on plain drawChars() if the host charset is an
2526: * 8 bit encoding like ASCII or ISO 8859. This encoding is available
2527: * via System.getProperty("file.encoding") but there are so many aliases
2528: * for each that I"m wary of hardcoding tests. See
2529: * http://www.iana.org/assignments/character-sets
2530: * Java 1.4 has class Charset that helps with the aliases but we can't
2531: * yet lock into 1.4.
2532: */
2533:
2534: private void myDrawChars(Graphics g, char buf[], Line l, int start,
2535: int howmany, int xoff, int baseline) {
2536: if (metrics.isMultiCell()) {
2537: // slow way
2538: // This looks expensive but it is in fact a whole lot faster
2539: // than issuing a g.drawChars() _per_ character
2540:
2541: Graphics2D g2 = (Graphics2D) g;
2542: FontRenderContext frc = g2.getFontRenderContext();
2543: // Gaaah, why doesn't createGlyphVector() take a (char[],offset,len)
2544: // triple?
2545: char[] tmp = new char[howmany];
2546: System.arraycopy(buf, start, tmp, 0, howmany);
2547: GlyphVector gv = getFont().createGlyphVector(frc, tmp);
2548: massage_glyphs(gv, start, howmany, l);
2549: g2.drawGlyphVector(gv, xoff, baseline);
2550: } else {
2551: // fast way
2552: g.drawChars(buf, start, howmany, xoff, baseline);
2553: }
2554: }
2555:
2556: /*
2557: * Render one line
2558: * Draw the line on this brow (buffer row 0-origin)
2559: */
2560:
2561: private void paint_line_new(Graphics g, Line l, int brow, int xoff,
2562: int yoff, int baseline, Extent selx) {
2563:
2564: int length = l.length();
2565: if (length == 0)
2566: return;
2567:
2568: int lastcol;
2569: int firstcol;
2570:
2571: if (metrics.isMultiCell()) {
2572:
2573: // Figure what buffer column is the first visible one (moral
2574: // equivalent of st.firsty)
2575:
2576: // SHOULD replace with something that does cellToBuf/bufToCell
2577: // all at once. There are a couple of other occurances of this
2578: // pattern.
2579:
2580: firstcol = l.cellToBuf(metrics, st.firsty);
2581: int inverse_firstcol = l.bufToCell(metrics, firstcol);
2582: int delta = st.firsty - inverse_firstcol;
2583: if (delta > 0) {
2584: /* This is what to do if we want to draw the right half of the
2585: * glyph. However the left half of it will end up in the glyph
2586: * gutter and to compensate for thet we'll need to tweak the
2587: * clip region. For now taking the easy way out>
2588:
2589: int pdelta = delta * metrics.width; // pixel delta
2590: xoff -= pdelta;
2591:
2592: */
2593:
2594: firstcol++;
2595: int pdelta = delta * metrics.width; // pixel delta
2596: xoff += pdelta;
2597: }
2598:
2599: lastcol = l.cellToBuf(metrics, st.firsty
2600: + buf.visibleCols() - 1);
2601:
2602: /* DEBUG
2603: System.out.print
2604: ("firstcol = " + firstcol + " for firsty " + st.firsty); // NOI18N
2605: System.out.print
2606: (" delta = " + delta); // NOI18N
2607: System.out.println
2608: (" lastcol = " + lastcol + // NOI18N
2609: " for visibleCols " + buf.visibleCols()); // NOI18N
2610: */
2611:
2612: } else {
2613: lastcol = st.firsty + buf.visibleCols() - 1;
2614: firstcol = st.firsty;
2615: }
2616:
2617: lastcol = Math.min(lastcol, length - 1);
2618: if (firstcol > lastcol)
2619: return;
2620: int howmany = lastcol - firstcol + 1;
2621:
2622: // 'length' is not used from here on down
2623:
2624: char buf[] = l.charArray();
2625:
2626: if (!l.hasAttributes()) {
2627:
2628: if (debugWrap()) {
2629: if (l.isWrapped() && l.isAboutToWrap())
2630: g.setColor(Color.red); // not a good state to be in
2631: else if (l.isAboutToWrap())
2632: g.setColor(Color.orange);
2633: else if (l.isWrapped())
2634: g.setColor(Color.magenta);
2635: }
2636:
2637: myDrawChars(g, buf, l, firstcol, howmany, xoff, baseline);
2638:
2639: return;
2640: }
2641:
2642: int attrs[] = l.attrArray();
2643:
2644: // find the extent of the selection on this line
2645: int sbegin = -1;
2646: int send = -1;
2647: if (check_selection && selx != null) {
2648: int arow = firsta + brow;
2649: Coord b = selx.begin;
2650: Coord e = selx.end;
2651: if (b.row <= arow && e.row >= arow) {
2652: if (b.row == e.row) {
2653: sbegin = b.col;
2654: send = e.col;
2655: } else if (arow == b.row) {
2656: sbegin = b.col;
2657: send = totcols;
2658: } else if (arow == e.row) {
2659: sbegin = 0;
2660: send = e.col;
2661: } else {
2662: sbegin = 0;
2663: send = totcols;
2664: }
2665: }
2666: }
2667:
2668: // iterate through runs
2669:
2670: int rbegin = firstcol;
2671: int rend = rbegin;
2672:
2673: while (true) {
2674:
2675: // find a "run"
2676: // A run, as in run-length-encoding, is a set of characters with
2677: // the same attributes.
2678:
2679: int attr = attrs[rbegin];
2680: rend = rbegin + 1;
2681: while (rend <= lastcol) {
2682: if (attrs[rend] != attr)
2683: break;
2684: rend++;
2685: }
2686: rend--;
2687:
2688: // render the run
2689: // need to do this with awareness of the selection
2690: // parts that fall under the selection are rendered with an
2691: // alternative attribute set.
2692:
2693: int alt_attr = (attr & ~(Attr.BGCOLOR | Attr.FGCOLOR
2694: | Attr.REVERSE | Attr.ACTIVE));
2695: if (sbegin == -1 || send < rbegin || sbegin > rend) {
2696: // run is not in selection
2697: do_run(g, yoff, xoff, baseline, brow, buf, l, attr,
2698: rbegin, rend);
2699:
2700: } else if (sbegin <= rbegin && send >= rend) {
2701: // run entirely in selection
2702: /* DEBUG
2703: System.out.println("run entirely in selection"); // NOI18N
2704: */
2705: do_run(g, yoff, xoff, baseline, brow, buf, l, alt_attr,
2706: rbegin, rend);
2707:
2708: } else if (sbegin > rbegin && send < rend) {
2709: // selection fully within run
2710: // split into three parts
2711: /* DEBUG
2712: System.out.println("run selection fully within run"); // NOI18N
2713: */
2714: do_run(g, yoff, xoff, baseline, brow, buf, l, attr,
2715: rbegin, sbegin - 1);
2716: do_run(g, yoff, xoff, baseline, brow, buf, l, alt_attr,
2717: sbegin, send);
2718: do_run(g, yoff, xoff, baseline, brow, buf, l, attr,
2719: send + 1, rend);
2720:
2721: } else if (sbegin <= rbegin) {
2722: // selection covers left portion of run
2723: /* DEBUG
2724: System.out.println("selection covers left portion of run"); // NOI18N
2725: */
2726: // split into two parts
2727: do_run(g, yoff, xoff, baseline, brow, buf, l, alt_attr,
2728: rbegin, send);
2729: do_run(g, yoff, xoff, baseline, brow, buf, l, attr,
2730: send + 1, rend);
2731:
2732: } else if (send >= rend) {
2733: // selection covers right portion of run
2734: // split into two parts
2735: /* DEBUG
2736: System.out.println("selection covers right portion of run"); // NOI18N
2737: */
2738: do_run(g, yoff, xoff, baseline, brow, buf, l, attr,
2739: rbegin, sbegin - 1);
2740: do_run(g, yoff, xoff, baseline, brow, buf, l, alt_attr,
2741: sbegin, rend);
2742:
2743: } else {
2744: /* DEBUG
2745: System.out.println("Odd run/selection overlap"); // NOI18N
2746: */
2747: }
2748:
2749: if (rend + 1 >= lastcol)
2750: break;
2751:
2752: // shift
2753: rbegin = rend + 1;
2754: }
2755: }
2756:
2757: /* OLD NPE-x synchronized */void do_paint(Graphics g) {
2758: ckEventDispatchThread();
2759:
2760: /*
2761: * Render the buffer unto the screen
2762: * SHOULD try theses:
2763: * - use drawChars?
2764: * - make passes first the glyphs and BG, then chars, then cursor
2765: * - use drawString(AttributedCharacterIterator iterator, ...)
2766: * - precompute any metrics related stuff
2767: */
2768: if (st.firstx == -1) {
2769: /* DEBUG
2770: System.out.println("Term.paint() no lines"); // NOI18N
2771: */
2772: return;
2773: }
2774:
2775: /* DEBUG
2776: long paint_start_time = System.currentTimeMillis();
2777: */
2778:
2779: // If Screen is opaque it seems that there is a bug in Swing where
2780: // the Graphics that we get here ends up with fonts other than what
2781: // we assigned to Term. So we make doubly sure here.
2782: g.setFont(getFont());
2783:
2784: n_paint++;
2785:
2786: if (reverse_video) {
2787: actual_foreground = getBackground();
2788: actual_background = getForeground();
2789: } else {
2790: actual_foreground = getForeground();
2791: actual_background = getBackground();
2792: }
2793:
2794: // clear the screen
2795: g.setColor(actual_background);
2796: g.fillRect(0, 0, screen.getSize().width,
2797: screen.getSize().height);
2798:
2799: // draw any BG stripes
2800: // do this before the selection
2801:
2802: int xoff = debug_gutter_width + glyph_gutter_width;
2803:
2804: int lx = st.firstx;
2805:
2806: for (int vrow = 0; vrow < st.rows; vrow++) {
2807: Line l = buf.lineAt(lx);
2808: if (l == null)
2809: break; // don't make a big fuss the loop below will
2810:
2811: int yoff = metrics.height * vrow;
2812:
2813: Color background = rendition_to_color(l.glyph_rendition);
2814: if (background != null) {
2815: int rect_height = metrics.height - metrics.leading;
2816: g.setColor(background);
2817: g.fillRect(xoff, yoff, screen.getWidth(), rect_height);
2818: }
2819:
2820: lx++;
2821: }
2822:
2823: if (!selection_xor) {
2824: // The screen is clear, draw any selections
2825: //
2826: // If most of the lines are w/o attributes then the text just gets
2827: // draw over this. Lines that are attributed end up doing some
2828: // redundant work repainting.
2829:
2830: sel.paint(g);
2831: }
2832:
2833: g.setColor(actual_foreground);
2834:
2835: Extent selx = sel.getExtent();
2836: check_selection = (selx != null && !selection_xor);
2837: totcols = buf.totalCols();
2838:
2839: /* DEBUG
2840: System.out.println("=========================================="); // NOI18N
2841: */
2842: lx = st.firstx;
2843:
2844: for (int vrow = 0; vrow < st.rows; vrow++) {
2845: Line l = buf.lineAt(lx);
2846: if (l == null) {
2847: /* DEBUG
2848: System.out.println("vrow " + vrow + " lx " + lx); // NOI18N
2849: */
2850: printStats(null);
2851: break;
2852: }
2853:
2854: xoff = 0;
2855: int yoff = metrics.height * vrow;
2856: int baseline = yoff + metrics.ascent;
2857:
2858: if (debug_gutter_width > 0) {
2859: String buf = "" + (firsta + st.firstx + vrow); // NOI18N
2860: g.drawString(buf, xoff, baseline);
2861: }
2862: xoff += debug_gutter_width;
2863:
2864: // draw any glyphs that we might have
2865: if (glyph_gutter_width > 0) {
2866: Image image = glyph_images[l.glyph_glyph];
2867: if (image != null) {
2868: // xy passed to drawImage() is the top-left of the image
2869: int gyoff = yoff;
2870: g.drawImage(image, xoff, gyoff, Color.white, null);
2871: }
2872: }
2873: xoff += glyph_gutter_width;
2874:
2875: paint_line_new(g, l, vrow + st.firstx, xoff, yoff,
2876: baseline, selx);
2877:
2878: // restore 'g' to something reasonable
2879: g.setColor(actual_foreground);
2880:
2881: lx++;
2882: }
2883:
2884: paint_cursor(g);
2885:
2886: if (selection_xor)
2887: sel.paint(g);
2888:
2889: if (debugMargins())
2890: paint_margins(g);
2891:
2892: /* DEBUG
2893: long paint_stop_time = System.currentTimeMillis();
2894: long paint_time = paint_stop_time - paint_start_time;
2895: System.out.println("paint_time = " + paint_time); // NOI18N
2896: */
2897: }
2898:
2899: private void paint_margins(Graphics g) {
2900: }
2901:
2902: private void paint_cursor(Graphics g) {
2903: if (!cursor_visible)
2904: return;
2905:
2906: // figure what row the cursor is on
2907: if (st.cursor.row == -1) {
2908: // System.out.println("Term.paint_cursor: " + // NOI18N
2909: // "cursor doesn't point to any line"); // NOI18N
2910: return;
2911: }
2912:
2913: int cursor_row = st.cursor.row - st.firstx;
2914: if (cursor_row >= st.rows) {
2915: return; // cursor not visible
2916: }
2917:
2918: int cursor_col = st.cursor.col - st.firsty;
2919: if (cursor_col >= buf.visibleCols()) {
2920: return; // cursor not visible
2921: } else if (cursor_col < 0) {
2922: return; // cursor not visible
2923: }
2924:
2925: g.setXORMode(actual_background);
2926: int rect_x = cursor_col * metrics.width + glyph_gutter_width
2927: + debug_gutter_width;
2928: int rect_y = cursor_row * metrics.height;
2929: // we _don't_ make cursor as wide as underlying character
2930: int rect_width = metrics.width;
2931: int rect_height = metrics.height - metrics.leading;
2932: if (has_focus)
2933: g.fillRect(rect_x, rect_y, rect_width, rect_height);
2934: else
2935: g.drawRect(rect_x, rect_y, rect_width, rect_height);
2936: }
2937:
2938: private final boolean do_margins = true;
2939:
2940: private boolean possiblyScrollDown() {
2941: /*
2942: * If cursor has moved below the scrollable region scroll down.
2943: * Buffer manipulation is partly done here or at the callsite if
2944: * 'true' is returned.
2945: */
2946:
2947: if (!do_margins) {
2948: if (st.cursor.row >= st.firstx + st.rows) {
2949: // scroll down
2950: st.firstx++;
2951: return true;
2952: }
2953: return false;
2954: } else {
2955: // new margin based scrolling
2956: if (st.cursor.row >= st.firstx + botMargin() + 1) {
2957: // scroll down
2958: if (topMargin() == 0) {
2959: if (scroll_on_output || cursor_was_visible()
2960: && track_cursor)
2961: st.firstx++;
2962: return true;
2963: } else {
2964: st.cursor.row = st.firstx + botMargin();
2965: Line l = buf.moveLineFromTo(
2966: st.firstx + topMargin(), st.cursor.row);
2967: l.reset();
2968: return false;
2969: }
2970: }
2971: return false;
2972: }
2973: }
2974:
2975: /**
2976: * Send a character to the terminal screen.
2977: */
2978: public void putChar(char c) {
2979: dce_end.putChar(c);
2980: }
2981:
2982: /**
2983: * Send several characters to the terminal screen.
2984: * <br>
2985: * While 'buf' will have a size it may not be fully filled, hence
2986: * the explicit 'nchar'.
2987: */
2988: public void putChars(char buf[], int offset, int count) {
2989: dce_end.putChars(buf, offset, count);
2990: }
2991:
2992: /**
2993: * Force a repaint.
2994: * <p>
2995: * Normally a putChar() or putChars() will call repaint, unless ...
2996: * setRepaintEnabled() has been called with false. This function
2997: * allows for some flexibility wrt to buffering and flushing:
2998: * <pre>
2999: * term.setRefreshEnabled(false);
3000: * for (cx = 0; cx < buf.length; cx++) {
3001: * term.putChar(c);
3002: * if (c % 10 == 0)
3003: * term.flush();
3004: * }
3005: * term.setRefreshEnabled(true);
3006: * </pre>
3007: */
3008: public void flush() {
3009: dce_end.flush();
3010: }
3011:
3012: /**
3013: * Send a message back to DCE.
3014: * <p>
3015: * Perhaps SHOULD lock out sendChar() so user input doesn't interfere.
3016: */
3017: private void reply(String str) {
3018: /* DEBUG
3019: System.out.println("replying " + str); // NOI18N
3020: */
3021: for (int sx = 0; sx < str.length(); sx++)
3022: sendChar(str.charAt(sx));
3023: }
3024:
3025: /*
3026: * Term used to implement Ops but then that forces Ops to be public
3027: * which we don't want. So we do it this way.
3028: */
3029: private class OpsImpl implements Ops {
3030:
3031: public void op_pause() {
3032:
3033: // This yields slighlty more reasonable results.
3034: Thread.currentThread().yield();
3035:
3036: /*
3037:
3038: DtTerm sends ~240 NUL's between reverse video switching to
3039: simulate a flash. The resolution of Thread.sleep() isn't
3040: really one millisecond so the flash ends up being too long.
3041:
3042: try {
3043: Thread.currentThread().sleep(0, 500);
3044: } catch (InterruptedException x) {
3045: ;
3046: }
3047: */
3048: }
3049:
3050: public void op_char(char c) {
3051: if (debugOps())
3052: System.out.println("op_char('" + c + "') = " + (int) c); // NOI18N
3053:
3054: // generic character printing
3055: Line l = cursor_line();
3056:
3057: int insertion_col = l.cellToBuf(metrics, st.cursor.col);
3058: if (debugOps()) {
3059: System.out.println("op_char(): st.cursor.col "
3060: + st.cursor.col + // NOI18N
3061: " insertion_col " + insertion_col); // NOI18N
3062: }
3063: if (!st.overstrike) {
3064: // This just shifts stuff the actual character gets put in below.
3065: l.insertCharAt(Term.this , ' ', insertion_col, st.attr);
3066: }
3067:
3068: int cwidth = metrics.wcwidth(c);
3069: if (l.isAboutToWrap()
3070: || (cwidth > 1
3071: && st.cursor.col + cwidth > buf
3072: .visibleCols() && !horizontally_scrollable)) {
3073:
3074: // 'wrap' the line
3075: if (debugOps())
3076: System.out.println("\twrapping it"); // NOI18N
3077: l.setWrapped(true);
3078: l.setAboutToWrap(false);
3079: op_line_feed();
3080: op_carriage_return();
3081: l = cursor_line();
3082: insertion_col = 0;
3083: // Fall thru
3084: }
3085:
3086: l.setCharAt(Term.this , c, insertion_col, st.attr); // overstrike
3087: st.cursor.col += cwidth;
3088:
3089: if (st.cursor.col >= buf.visibleCols()
3090: && !horizontally_scrollable) {
3091: if (debugOps())
3092: System.out.println("\tabout to wrap"); // NOI18N
3093: l.setAboutToWrap(true);
3094: st.cursor.col -= cwidth;
3095: }
3096: }
3097:
3098: public void op_attr(int attr) {
3099: if (debugOps())
3100: System.out.println("op_attr(" + attr + ")"); // NOI18N
3101: setAttribute(attr);
3102: }
3103:
3104: public void op_bel() {
3105: // ring the bell
3106: // SHOULD implement
3107: }
3108:
3109: public void op_back_space() {
3110: // back-space
3111: if (debugOps())
3112: System.out.println("op_back_space"); // NOI18N
3113:
3114: if (st.cursor.col > 0) {
3115: if (!cursor_line().isAboutToWrap()) {
3116: st.cursor.col--;
3117: }
3118: cursor_line().setAboutToWrap(false);
3119:
3120: // If we' backed up to column 0, maybe we need to consider
3121: // whether the previous line was wrapped. Older xterms aren't
3122: // this clever, newer ones (Solaris 8+?) are.
3123:
3124: if (st.cursor.col == 0) {
3125: if (st.cursor.row > 0) {
3126: // maybe we had wrapped on the previous line?
3127: if (debugOps())
3128: System.out
3129: .println("\tchecking if prev is wrapped"); // NOI18N
3130: Line prev = buf.lineAt(st.cursor.row - 1);
3131: if (prev.isWrapped()) {
3132: if (debugOps())
3133: System.out.println("\tit is"); // NOI18N
3134: st.cursor.row--;
3135:
3136: // The below is done in a roundabout way because BS doesn't
3137: // really reduce length. So, suppose we went to the end with
3138: // latin chars that makes the line 80 long. Then we backspace
3139: // to column 78 and enter one 2-cell japanese character. Now
3140: // the line is conceptually 79 long, but it still remembers
3141: // the 80. So we don't use 'prev.length()' directly.
3142:
3143: // st.cursor.col = prev.bufToCell(metrics, prev.length()-1);
3144:
3145: int last_col = prev.cellToBuf(metrics, buf
3146: .visibleCols() - 1);
3147: st.cursor.col = prev.bufToCell(metrics,
3148: last_col);
3149:
3150: prev.setWrapped(false);
3151:
3152: // The following isn't entirely correct when we backspaced
3153: // over a multi-celled character. SHOULD either note
3154: // what we BS'ed over or note the slop at the end of the line.
3155: prev.setAboutToWrap(true);
3156: }
3157: }
3158: }
3159: }
3160: }
3161:
3162: public void op_line_feed() {
3163: // NL line feed ctrl-J
3164: // move cursor down one line and if goes past the screen
3165: // add a new line.
3166: if (debugOps())
3167: System.out.println("op_line_feed"); // NOI18N
3168: Line last_line = cursor_line();
3169: /* DEBUG
3170: if (last_line == null) {
3171: Thread.dumpStack();
3172: printStats("last_line == null in op_line_feed()");// NOI18N
3173: }
3174: */
3175: st.cursor.row++;
3176: if (possiblyScrollDown()) {
3177: buf.addLineAt(st.cursor.row);
3178: limit_lines();
3179: if (debugOps())
3180: System.out.println("op_line_feed ADJUSTED"); // NOI18N
3181: }
3182: // have new line inherit cursorAtEnd
3183: boolean atw = last_line.isAboutToWrap();
3184: cursor_line().setAboutToWrap(atw);
3185: last_line.setAboutToWrap(false);
3186:
3187: n_linefeeds++;
3188:
3189: // See repaint() for an explanation of this.
3190: // repaint(false);
3191: }
3192:
3193: public void op_tab() {
3194: // TAB/HT
3195: // SHOULD do something better with tabs near the end of the line
3196: // On the other hand, that's how ANSI terminals are supposed
3197: // to behave
3198:
3199: if (debugOps())
3200: System.out.println("op_tab"); // NOI18N
3201:
3202: if (st.cursor.col == buf.visibleCols() - 1
3203: && !horizontally_scrollable)
3204: return;
3205:
3206: Line l = cursor_line();
3207: int insert_col = l.cellToBuf(metrics, st.cursor.col);
3208: l.setCharAt(Term.this , ' ', insert_col, st.attr);
3209: st.cursor.col++;
3210: insert_col++;
3211: // no need to re-apply cellToBuf to cursor since we're only adding 1-wide ' '
3212: while ((st.cursor.col < buf.visibleCols() - 1 || horizontally_scrollable)
3213: && (st.cursor.col % tab_size) != 0) {
3214: cursor_line().setCharAt(Term.this , ' ', insert_col,
3215: st.attr);
3216: st.cursor.col++;
3217: insert_col++;
3218: }
3219: }
3220:
3221: public void op_carriage_return() {
3222: if (debugOps())
3223: System.out.println("op_carriage_return"); // NOI18N
3224: st.cursor.col = 0;
3225: cursor_line().setAboutToWrap(false);
3226: }
3227:
3228: public void op_al(int count) {
3229: // add new blank line
3230: if (debugOps())
3231: System.out.println("op_al(" + count + ")"); // NOI18N
3232:
3233: Line l;
3234: while (count-- > 0) {
3235: boolean old_atw = cursor_line().setAboutToWrap(false);
3236:
3237: // reverse of op_dl()
3238: // Rotate a line from bottom to top
3239: if (!do_margins) {
3240: l = buf.moveLineFromTo(buf.nlines - 1,
3241: st.cursor.row);
3242: } else {
3243: l = buf.moveLineFromTo(st.firstx + botMargin(),
3244: st.cursor.row);
3245: }
3246: l.reset();
3247:
3248: cursor_line().setAboutToWrap(old_atw);
3249: }
3250:
3251: switch (sel.intersection(st.cursor.row - 1)) {
3252: case Sel.INT_NONE:
3253: case Sel.INT_ABOVE:
3254: case Sel.INT_ON:
3255: // nothing to do
3256: break;
3257: case Sel.INT_STRADDLES:
3258: sel.cancel(true); // DtTerm behaviour
3259: break;
3260: case Sel.INT_BELOW:
3261: sel.adjust(firsta, +1, firsta + buf.nlines);
3262: break;
3263: }
3264: }
3265:
3266: public void op_bc(int count) {
3267: // back cursor/column
3268: if (debugOps())
3269: System.out.println("op_bc(" + count + ")"); // NOI18N
3270:
3271: while (count-- > 0) {
3272: if (st.cursor.col <= 0)
3273: return;
3274: st.cursor.col--;
3275: }
3276: cursor_line().setAboutToWrap(false);
3277: }
3278:
3279: public void op_cm(int row, int col) {
3280: // cursor motion row and col come in as 1-origin)
3281: if (debugOps())
3282: System.out.println("op_cm(row " + row + ", col " + col
3283: + ")"); // NOI18N
3284:
3285: // "xemacs -nw" seems to overflow and underflow often.
3286:
3287: // 0 is allowed
3288: if (row == 0)
3289: row = 1;
3290: if (col == 0)
3291: col = 1;
3292:
3293: // deal with overflow
3294: if (row > st.rows)
3295: row = st.rows;
3296: if (col > buf.visibleCols())
3297: col = buf.visibleCols();
3298:
3299: cursor_line().setAboutToWrap(false);
3300: st.cursor.row = beginx() + row - 1;
3301: st.cursor.col = col - 1;
3302: // Maybe SHOULD setAboutToWrap(true) if on last column?
3303: }
3304:
3305: public void op_cl() {
3306: // clear screen and home cursor
3307: if (debugOps())
3308: System.out.println("op_cl"); // NOI18N
3309: cursor_line().setAboutToWrap(false);
3310: clear();
3311: st.cursor.row = beginx();
3312: st.cursor.col = 0;
3313: }
3314:
3315: public void op_ce() {
3316: // clear to end of line
3317: if (debugOps())
3318: System.out.println("op_ce"); // NOI18N
3319:
3320: Line l = cursor_line();
3321: l.clearToEndFrom(Term.this , l.cellToBuf(metrics,
3322: st.cursor.col));
3323:
3324: switch (sel.intersection(st.cursor.row)) {
3325: case Sel.INT_NONE:
3326: case Sel.INT_ABOVE:
3327: case Sel.INT_BELOW:
3328: // nothing to do
3329: break;
3330: case Sel.INT_ON:
3331: case Sel.INT_STRADDLES:
3332: sel.cancel(true); // DtTerm behaviour
3333: break;
3334: }
3335: }
3336:
3337: public void op_cd() {
3338: // clear to end of screen
3339: if (debugOps())
3340: System.out.println("op_cd -- clear to end of screen"); // NOI18N
3341:
3342: for (int lx = st.cursor.row; lx < beginx() + st.rows; lx++) {
3343: Line l = buf.lineAt(lx);
3344: l.reset();
3345: }
3346:
3347: switch (sel.intersection(st.cursor.row)) {
3348: case Sel.INT_NONE:
3349: case Sel.INT_ABOVE:
3350: // nothing to do
3351: break;
3352: case Sel.INT_BELOW:
3353: case Sel.INT_ON:
3354: case Sel.INT_STRADDLES:
3355: sel.cancel(true); // DtTerm behaviour
3356: break;
3357: }
3358: }
3359:
3360: public void op_dc(int count) {
3361: // delete character
3362: if (debugOps())
3363: System.out.println("op_dc(" + count + ")"); // NOI18N
3364: if (count == 0)
3365: count = 1;
3366: Line l = cursor_line();
3367: while (count-- > 0)
3368: l.deleteCharAt(l.cellToBuf(metrics, st.cursor.col));
3369: }
3370:
3371: public void op_dl(int count) {
3372: // delete line
3373: // and scroll everything under it up
3374: if (debugOps())
3375: System.out.println("op_dl(" + count + ")"); // NOI18N
3376:
3377: Line l;
3378: while (count-- > 0) {
3379: boolean old_atw = cursor_line().setAboutToWrap(false);
3380:
3381: // reverse of op_al()
3382: // Rotate a line from top to bottom
3383: if (!do_margins) {
3384: l = buf.moveLineFromTo(st.cursor.row, (beginx()
3385: + st.rows - 1) - 1);
3386: } else {
3387: l = buf.moveLineFromTo(st.cursor.row,
3388: (beginx() + botMargin()) - 1);
3389: }
3390: l.reset();
3391:
3392: cursor_line().setAboutToWrap(old_atw);
3393: }
3394:
3395: switch (sel.intersection(st.cursor.row)) {
3396: case Sel.INT_NONE:
3397: case Sel.INT_ABOVE:
3398: // nothing to do
3399: break;
3400: case Sel.INT_ON:
3401: case Sel.INT_STRADDLES:
3402: sel.cancel(true); // DtTerm behaviour
3403: break;
3404: case Sel.INT_BELOW:
3405: sel.adjust(firsta, -1, firsta + buf.nlines);
3406: break;
3407: }
3408: }
3409:
3410: public void op_do(int count) {
3411: // down count lines
3412: // SHOULD add a mode: {scroll, warp, stay} for cases where
3413: // cursor is on the bottom line.
3414:
3415: if (debugOps())
3416: System.out.println("op_do(" + count + ") -- down"); // NOI18N
3417:
3418: boolean old_atw = cursor_line().setAboutToWrap(false);
3419:
3420: while (count-- > 0) {
3421: st.cursor.row++;
3422: if (st.cursor.row >= buf.nlines) {
3423:
3424: // equivalent of op_newline:
3425: if (possiblyScrollDown()) {
3426: buf.addLineAt(st.cursor.row);
3427: limit_lines();
3428: if (debugOps())
3429: System.out.println("op_do ADJUSTED"); // NOI18N
3430: }
3431: }
3432: }
3433: cursor_line().setAboutToWrap(old_atw);
3434: }
3435:
3436: public void op_ho() {
3437: // cursor home (upper left of the screen)
3438: if (debugOps())
3439: System.out.println("op_ho -- home"); // NOI18N
3440: cursor_line().setAboutToWrap(false);
3441: st.cursor.row = beginx();
3442: st.cursor.col = 0;
3443: }
3444:
3445: public void op_ic(int count) {
3446: // insert character
3447: if (debugOps())
3448: System.out.println("op_ic(" + count + ")"); // NOI18N
3449:
3450: Line l = cursor_line();
3451: int insertion_col = l.cellToBuf(metrics, st.cursor.col);
3452: while (count-- > 0) {
3453: l.insertCharAt(Term.this , ' ', insertion_col, st.attr);
3454: }
3455: // SHOULD worry about line wrapping
3456: }
3457:
3458: public void op_nd(int count) {
3459: // cursor right (non-destructive space)
3460: if (debugOps())
3461: System.out.println("op_nd(" + count + ")"); // NOI18N
3462:
3463: int vc = st.cursor.col;
3464: while (count-- > 0) {
3465: vc++;
3466: if (vc >= buf.visibleCols()) {
3467: if (debugOps())
3468: System.out.println("\tbailing out at count "
3469: + count); // NOI18N
3470: vc--;
3471: break;
3472: }
3473: }
3474: st.cursor.col = vc;
3475: }
3476:
3477: public void op_up(int count) {
3478: // cursor up
3479: if (debugOps())
3480: System.out.println("op_up(" + count + ")"); // NOI18N
3481:
3482: boolean old_atw = cursor_line().setAboutToWrap(false);
3483: Line l;
3484: while (count-- > 0) {
3485: st.cursor.row--;
3486: if (st.cursor.row < st.firstx) {
3487: st.cursor.row = st.firstx;
3488: // scroll down, Rotate a line from bottom to top
3489: if (!do_margins) {
3490: l = buf.moveLineFromTo(buf.nlines - 1,
3491: st.cursor.row);
3492: } else {
3493: l = buf.moveLineFromTo(st.firstx + botMargin(),
3494: st.cursor.row);
3495: }
3496: l.reset();
3497: // SHOULD note and do something about the selection?
3498: }
3499: }
3500: cursor_line().setAboutToWrap(old_atw);
3501: }
3502:
3503: public void op_sc() {
3504: // save cursor position
3505: if (debugOps())
3506: System.out.println("op_sc()"); // NOI18N
3507: st.saveCursor();
3508: // SHOULD defeat repaint?
3509: }
3510:
3511: public void op_rc() {
3512: // restore saved cursor position
3513: if (debugOps())
3514: System.out.println("op_rc()"); // NOI18N
3515: st.restoreCursor();
3516: }
3517:
3518: public void op_glyph(int glyph, int rendition) {
3519: if (debugOps()) {
3520: System.out.println("op_glyph(glyph " + glyph + // NOI18N
3521: ", rendition " + rendition + ")"); // NOI18N
3522: }
3523: setGlyph(glyph, rendition);
3524: }
3525:
3526: public void op_reverse(boolean reverse_video) {
3527: setReverseVideo(reverse_video);
3528: }
3529:
3530: public void op_cursor_visible(boolean visible) {
3531: setCursorVisible(visible);
3532: }
3533:
3534: public void op_margin(int from, int to) {
3535: if (debugOps()) {
3536: System.out.println("op_margin(" + from + ", " + // NOI18N
3537: to + ")"); // NOI18N
3538: }
3539:
3540: if (from < 0)
3541: top_margin = 0;
3542: else if (from > st.rows)
3543: top_margin = st.rows;
3544: else
3545: top_margin = from;
3546:
3547: if (to < 0)
3548: bot_margin = 0;
3549: else if (to > st.rows)
3550: bot_margin = st.rows;
3551: else
3552: bot_margin = to;
3553:
3554: if (top_margin > bot_margin) {
3555: int tmp = top_margin;
3556: top_margin = bot_margin;
3557: bot_margin = tmp;
3558: }
3559: }
3560:
3561: long last_time = System.currentTimeMillis();
3562:
3563: public void op_time(boolean repaint) {
3564: long time = System.currentTimeMillis();
3565: long elapsed = time - last_time;
3566: Date d = new Date(time);
3567: String date_str = d.toString();
3568: String elapsed_str = "" + elapsed / 1000 + "." + elapsed
3569: % 1000;// NOI18N
3570: String output1 = date_str + " Elapsed (sec): "
3571: + elapsed_str;// NOI18N
3572: String output2 = "putChar " + n_putchar + // NOI18N
3573: " putChars " + n_putchars + // NOI18N
3574: " linefeeds " + n_linefeeds + // NOI18N
3575: " repaint " + n_repaint + // NOI18N
3576: " paint " + n_paint; // NOI18N
3577:
3578: setAttribute(41); // Red Bg
3579:
3580: // can't use appendText from within ops.
3581:
3582: for (int sx = 0; sx < output1.length(); sx++)
3583: op_char(output1.charAt(sx));
3584: op_line_feed();
3585: op_carriage_return();
3586: for (int sx = 0; sx < output2.length(); sx++)
3587: op_char(output2.charAt(sx));
3588:
3589: setAttribute(0);
3590:
3591: last_time = time;
3592: n_putchar = 0;
3593: n_putchars = 0;
3594: n_linefeeds = 0;
3595: n_paint = 0;
3596: n_repaint = 0;
3597:
3598: repaint(true);
3599: // TMP setRefreshEnabled(repaint);
3600: }
3601:
3602: public int op_get_width() {
3603: return horizontally_scrollable ? buf.totalCols() : buf
3604: .visibleCols();
3605: }
3606:
3607: public int op_get_column() {
3608: return st.cursor.col;
3609: }
3610:
3611: public void op_soft_reset() {
3612: st.overstrike = true;
3613: top_margin = 0; // 0 means default (see topMargin())
3614: bot_margin = 0;
3615: st.attr = 0;
3616: repaint(false);
3617: }
3618:
3619: public void op_full_reset() {
3620: op_soft_reset();
3621: op_cl(); // clear screen, home cursor
3622: reverse_video = false;
3623: repaint(false);
3624: }
3625:
3626: public void op_set_mode(int mode) {
3627: switch (mode) {
3628: case 4: // insert mode
3629: st.overstrike = false;
3630: break;
3631: case 2: // keyboard lock
3632: case 12: // local echo off
3633: case 20: // newline
3634: // Currently unsupported
3635: break;
3636: }
3637: }
3638:
3639: public void op_reset_mode(int mode) {
3640: switch (mode) {
3641: case 4: // replace mode
3642: st.overstrike = true;
3643: break;
3644: case 2: // keyboard unlock
3645: case 12: // local echo on
3646: case 20: // newline
3647: // Currently unsupported
3648: break;
3649: }
3650: }
3651:
3652: public void op_status_report(int code) {
3653: switch (code) {
3654: case 5:
3655: reply((char) 27 + "[0n"); // NOI18N
3656: break;
3657: case 6:
3658: reply((char) 27 + "[" + // NOI18N
3659: (st.cursor.row - st.firstx) + ";" + // NOI18N
3660: st.cursor.col + "R"); // NOI18N
3661: break;
3662: }
3663: }
3664: }
3665:
3666: private void putc_work(char c) {
3667: interp.processChar(c);
3668: possiblyHScroll();
3669: screen.possiblyUpdateCaretText();
3670: }
3671:
3672: private void on_char(char c) {
3673: sendChar(c);
3674: }
3675:
3676: private void sendChars(char c[], int offset, int count) {
3677: dte_end.sendChars(c, offset, count);
3678: }
3679:
3680: private void sendChar(char c) {
3681: dte_end.sendChar(c);
3682: }
3683:
3684: /**
3685: * Adjust vertical scrollbar range
3686: */
3687: private void adjust_scrollbar() {
3688:
3689: // JScrollBar is weird.
3690: // The visible range is 1 (for value) + extent.
3691: // So extent has to be set to visible-range - 1:
3692:
3693: adjust_scrollbar_impl();
3694:
3695: /* OLD NPE-x
3696: // It's important that we do this from within the AWT event thread.
3697:
3698: if (SwingUtilities.isEventDispatchThread()) {
3699: adjust_scrollbar_impl();
3700: }
3701: else {
3702: SwingUtilities.invokeLater(new Runnable() {
3703: public void run() {
3704: adjust_scrollbar_impl();
3705: }
3706: });
3707: }
3708: */
3709: }
3710:
3711: private void adjust_scrollbar_impl() {
3712: if (vscroll_bar != null) {
3713: int value = st.firstx;
3714: int extent = st.rows - 1;
3715: int min = 0;
3716: int max;
3717: if (buf.nlines <= st.rows)
3718: max = st.rows - 1;
3719: else
3720: max = buf.nlines - 1;
3721: vscroll_bar.setValues(value, extent, min, max);
3722: }
3723:
3724: if (hscroll_bar != null && horizontally_scrollable) {
3725: int value = st.firsty;
3726: int extent = buf.visibleCols() - 1;
3727: int min = 0;
3728: int max;
3729: if (buf.totalCols() <= buf.visibleCols())
3730: max = buf.visibleCols() - 1;
3731: else
3732: max = buf.totalCols() - 1;
3733:
3734: /* DEBUG
3735: System.out.println("HSCROLL " + min + " <= " + value + // NOI18N
3736: "[" + extent + "] " + max); // NOI18N
3737: */
3738:
3739: hscroll_bar.setValues(value, extent, min, max);
3740:
3741: /* DEBUG
3742: System.out.println("HSCROLL " + hscroll_bar.getMinimum() + // NOI18N
3743: " <= " + hscroll_bar.getValue() + // NOI18N
3744: "[" + hscroll_bar.getModel().getExtent() + "] " + hscroll_bar.getMaximum()); // NOI18N
3745: */
3746: }
3747: }
3748:
3749: /**
3750: * Figure the pixel size of the screen based on various properties.
3751: */
3752: private Dimension calculateSize() {
3753: int dx = buf.visibleCols() * metrics.width + glyph_gutter_width
3754: + debug_gutter_width;
3755: int dy = st.rows * metrics.height;
3756: Dimension d = new Dimension(dx, dy);
3757: return d;
3758: }
3759:
3760: /**
3761: * To be called as the result of programmatic changes in properties
3762: * that affect the size of the screen: font, rows & columns, glyph
3763: * gutter width etc.
3764: * Applies the newly calculated size via sizeChanged().
3765: *
3766: * We used to call screen.setSize() which would eventually call
3767: * sizeChanged() through the notification mechanism. That worked well
3768: * for row column size changes etc, but not so well for font changes.
3769: * What would happen is that he cause of size changes would be lost
3770: * by the time we got to sizeChanged() and for example the screen
3771: * wouldn't resize as a result of font pt-size changes.
3772: */
3773: private void updateScreenSize() {
3774: /* DEBUG
3775: System.out.println("updateScreenSize("+buf.cols+", "+st.rows+")"); // NOI18N
3776: */
3777: if (screen != null) {
3778: Dimension d = calculateSize();
3779: sizeChanged(d.width, d.height);
3780: }
3781: }
3782:
3783: // HACK:
3784: // Helper variable to remember the original value of rows across
3785: // various different control flows.
3786:
3787: private int old_rows = -1;
3788:
3789: /**
3790: * Called whenver the screens size is to be changed, either by
3791: * us via updateScreenSize(), or thru user action and the Screen
3792: * componentResized() notification.
3793: *
3794: * Adjust the state and buffer, commit to the size by setting
3795: * preferredSize and notify any interested parties.
3796: */
3797:
3798: void sizeChanged(int newWidth, int newHeight) {
3799: /* DEBUG
3800: System.out.println("sizeChanged(newheight " + newHeight + // NOI18N
3801: ", newWidth " + newWidth + ")");
3802: */
3803:
3804: // Do columns first ... they're easy
3805: int newcols = (newWidth - glyph_gutter_width - debug_gutter_width)
3806: / metrics.width;
3807: buf.setVisibleCols(newcols);
3808:
3809: if (old_rows == -1) {
3810: // st.rows hasn't changed yet, so remember it before changing it.
3811: old_rows = st.rows;
3812: }
3813:
3814: st.rows = newHeight / metrics.height;
3815:
3816: // akemr - hack to fix #17807
3817: if (st.rows < 1)
3818: st.rows = 1;
3819:
3820: /* DEBUG
3821: System.out.println(">>>>>>> rows from "+old_rows+" to "+st.rows); // NOI18N
3822: */
3823:
3824: int row_delta = st.rows - old_rows; // negative => we shrunk
3825: old_rows = -1;
3826:
3827: adjust_lines(row_delta);
3828:
3829: limit_lines();
3830:
3831: // Commit to the size
3832: //
3833: // Setting setPreferredSize() is where the commitment is. If we
3834: // don't do it our layout manager containers won't honor the resizing
3835: // and snap us back.
3836:
3837: Dimension new_size = isSizeRounded() ? calculateSize()
3838: : new Dimension(newWidth, newHeight);
3839:
3840: /* DEBUG
3841: System.out.println("but I want "+new_size.height+" "+new_size.width); // NOI18N
3842: */
3843:
3844: if (false) {
3845: // Setting size is a bad idea. the potential for getting into
3846: // a looping tug-of-war with our containers' layout manager
3847: // is too high and unpredictable. One nasty example we ran
3848: // into was JTabbedPane.
3849: screen.setSize(new_size);
3850:
3851: } else {
3852: screen.setPreferredSize(new_size);
3853:
3854: // Do we really need these?
3855: invalidate();
3856: if (getParent() != null)
3857: getParent().validate();
3858: }
3859:
3860: // Notify any interested parties.
3861: // Normally we'd inline the code in updateTtySize() here but factoring
3862: // it has it's uses as explained in updateTtySize().
3863: updateTtySize();
3864: }
3865:
3866: protected void possibly_repaint(boolean adjust_scrollbar) {
3867: if (!refresh_enabled)
3868: return;
3869: repaint(adjust_scrollbar);
3870: }
3871:
3872: /**
3873: * Model and or view settings have changed, redraw everything.
3874: */
3875: protected void repaint(boolean adjust_scrollbar) {
3876:
3877: /*
3878: * A long discussion on performance and smooth vs jump vs jerky
3879: * scrolling ... (note: a lot of this is based on experiments with
3880: * Term as a unix terminal emulator application as opposed to
3881: * within the context of NetBeans).
3882: *
3883: * Term spends it's time between collecting and deciphering input
3884: * and repainting the screen. Input processing always goes on, but
3885: * screen repainitng can be done more or less often to trade off
3886: * smoothness of scrolling vs speed.
3887: *
3888: * At one end is so-called smooth scrolling. This is where the
3889: * screen is redrawn on every linefeed. That's a lot of painting.
3890: * To get into that mode use the paintImmediately() below and
3891: * uncomment the call to us in op_line_feed(). Also
3892: * paintImmediately() doesn't really work unless the Screen is
3893: * opaque. I think that is because the paint request comes
3894: * to us and we don't forward it to screen; but it could be a
3895: * Swing bug too. Term is very slow in this. For example I"ve
3896: * time xterm and DtTerm dealing with "cat /etc/termcap" in 2-3
3897: * seconds while Term takes 20-25 seconds. Part of this is
3898: * attributed to the fact that Term doesn't take advantage of
3899: * bitBlitting when it's adding one line at a time and still
3900: * redraws everything. However I'll make a case below that this
3901: * isn't that important.
3902: *
3903: * Then there is so-called jump scrolling. In this regime terminal
3904: * emulators redraw the screen "as time permits". This is in effect
3905: * what the swing repaint manager helps with. Multiple repaint()
3906: * requests translate to one actual paint(). With todays computers
3907: * it's very hard to tell visually that you're jump scrolling
3908: * things go by so fast (yes, even under Swing), so this is the
3909: * preferred setup.
3910: * Here term does a bit better. To deal with a cat'ed 100,000
3911: * line file DtTerm takes 8 seconds, while Term takes 22 seconds.
3912: * (That's 3 times slower vs 8 times). From some measurements
3913: * I've made the number of linefeeds per actual paints has
3914: * ranged from > 100 to upper 30's. These numbers are sufficiently
3915: * high that the whole screen has to be repained everytime.
3916: * I.e. blitting to scroll and drawing only what's new isn't
3917: * going to help here. To get reasonable jump-scrolling, you need
3918: * to make sure that the Screen is opaque because if you don't
3919: * you will get ...
3920: *
3921: * Jerky scrolling. If Term is not opaque, the number of actual
3922: * paints per repaint() requests diminishes drastically. 'cat' of
3923: * etc/termcap (once the code has been warmed up) sometimes causes
3924: * a single refresh at the end in contrast to ~100 when Screen
3925: * is opaque. Naturally Term in this mode can eat up input at
3926: * a rate comparable to dtterm etc, but the jerkiness is very
3927: * ugly.
3928: * Opacity isn't the only criterion. Term, when embeded inside a
3929: * tabbed pane (like it is in NetBeans) will also act as if it's
3930: * opaque and you get more frequent refreshes, as in the
3931: * jump-scrolling regime. But that was way too slow for the
3932: * taste of NB users which is why OutputTab window calls us on a
3933: * timer. That brings it's own jerkiness of a different sort.
3934: *
3935: * There is a third factor that contributes to slowness. If you
3936: * just 'cat' a file you get the numbers I presneted above. But
3937: * if you run an app that actually puts out the 100,000 lines
3938: * some sort of timing interaction forces Term into near smooth
3939: * scrolling and as a result things slow down a lot! For example,
3940: * $ generate_100K_lines > /tmp/bag 00:08 sec
3941: * $ cat /tmp/bag 00:20 sec
3942: * $ generate_100K_lines 03:42 sec (opaque)
3943: * $ generate_100K_lines 01:58 sec (!opaque)
3944: * This happens even if the generating program is a lightweight
3945: * native application. In fact I believe it is this effect that
3946: * forced NB's OutputTab to adopt the timer. I believe there are two
3947: * factors that contrinute to this.
3948: * a) Running applications are line buffered so putChars(), with
3949: * it's attendant repaint(), gets called once per line pushing
3950: * us into the smooth scrolling regime. (But why then doesn't
3951: * DtTerm suffer from this?)
3952: * b) timeslicing gives enough time to the repaint manager such
3953: * that it converts evey repaint() to a paint.
3954: * I know (b) is a factor since if I "simulate" (a) by issueing
3955: * repaints() from op_line_feed() while keeping this function from
3956: * using paintImmediately() I don't get that many paints.
3957: * The combined case has 44 paints per repaint as does simulated (a).
3958: * So ain increased number of paints per repaint doesn't
3959: * explain this.
3960: *
3961: * In the end, currently since jump scrolling is still not very
3962: * fast and since NB has the timer anyway, Screen is not opaque.
3963: *
3964: * A useful quantitative measure is the number of linefeeds vs
3965: * the number of repaint requests vs the number of actual paints.
3966: * All these are collected and can be dumped via op_time() or
3967: * printStats().
3968: */
3969:
3970: n_repaint++;
3971:
3972: if (adjust_scrollbar)
3973: adjust_scrollbar();
3974:
3975: // The following causes Screen.paint() to get called by the Swing
3976: // repaint manager which in turn calls back to term.paint(Graphics).
3977: screen.repaint(20);
3978:
3979: // The following should cause an immediate paint. It doesn't
3980: // always though!
3981: // I've found that for it to be effective Screen needs to be opaque.
3982:
3983: /*
3984: NOTE: paintImmediately() is probably not the best thing to use.
3985: // RepaintManager.currentManager(screen).paintDirtyRegions();
3986: screen.paintImmediately(0, 0, screen.getWidth(), screen.getHeight());
3987: */
3988: }
3989:
3990: /*
3991: * Term-specific properties
3992: */
3993:
3994: /**
3995: * Control whether the default foreground and background colors will
3996: * be reversed.
3997: * <p>
3998: * Note: This is independent of characters' reverse attribute.
3999: */
4000: public void setReverseVideo(boolean reverse_video) {
4001: this .reverse_video = reverse_video;
4002: repaint(false);
4003: }
4004:
4005: /**
4006: * Return the value set by setReverseVideo().
4007: */
4008: public boolean isReverseVideo() {
4009: return reverse_video;
4010: }
4011:
4012: private boolean reverse_video = false;
4013:
4014: /**
4015: * Set the color of the hilite (selection) - for non XOR mode
4016: */
4017: public void setHighlightColor(Color color) {
4018: sel.setColor(color);
4019: repaint(false);
4020: }
4021:
4022: /**
4023: * Get the color of the hilite (selection) - for non XOR mode
4024: */
4025: public Color getHighlightColor() {
4026: return sel.getColor();
4027: }
4028:
4029: /**
4030: * Set the color of the hilite (selection) - for XOR mode
4031: */
4032: public void setHighlightXORColor(Color color) {
4033: sel.setXORColor(color);
4034: repaint(false);
4035: }
4036:
4037: /**
4038: * Get the color of the hilite (selection) - for XOR mode
4039: */
4040: public Color getHighlightXORColor() {
4041: return sel.getXORColor();
4042: }
4043:
4044: /**
4045: * Set the feedback color of active regions.
4046: */
4047: public void setActiveColor(Color color) {
4048: // SHOULD check for null color
4049: active_color = color;
4050: repaint(false);
4051: }
4052:
4053: /**
4054: * Get the feedback color of active regions.
4055: */
4056: public Color getActiveColor() {
4057: // SHOULD clone? but Color is not clonable and has no simple
4058: // Color(COlor) constructor. What does JComponent do?
4059: return active_color;
4060: }
4061:
4062: private Color active_color = Color.lightGray;
4063:
4064: /**
4065: * Control whether an anchor is set.
4066: * <p>
4067: * Setting an anchor will automatically cause the buffer to grow, in
4068: * excess of what was set by setHistorySize(), to ensure that whatever
4069: * is displayed and in the current history will still be accessible.
4070: * <p>
4071: * Also, if you're working with Extents, Coords and ActiveRegions, or
4072: * visiting logical lines, you might want to anchor the text so that
4073: * your coordinates don't get invalidated by lines going out of the buffer.
4074: * <p>
4075: * Repeated enabling of the anchor will discard all text that
4076: * doesn't fit in history and start a new anchor.
4077: * <p>
4078: * When anchoring is disabled any text in excess of setHistorySize()
4079: * is trimmed and the given history size comes into effect again.
4080: */
4081: public void setAnchored(boolean anchored) {
4082: ckEventDispatchThread();
4083: // OLD NPE-x synchronized(this)
4084: {
4085: if (anchored) {
4086: this .anchored = false;
4087: limit_lines();
4088: this .anchored = true;
4089: } else {
4090: this .anchored = false;
4091: limit_lines();
4092: repaint(false); // limit_lines() already adjusted scrollbar
4093: }
4094: }
4095: }
4096:
4097: /**
4098: * Return true if the text is currently anchored.
4099: */
4100: public boolean isAnchored() {
4101: return anchored;
4102: }
4103:
4104: private boolean anchored = false;
4105:
4106: /**
4107: * Returns the actual drawing area so events can be interposed upon,
4108: * like context menus.
4109: * @deprecated
4110: */
4111: public JComponent getCanvas() {
4112: return screen;
4113: }
4114:
4115: /**
4116: * Returns the actual drawing area so events can be interposed upon,
4117: * like context menus.
4118: */
4119: public JComponent getScreen() {
4120: return screen;
4121: }
4122:
4123: /**
4124: * Return the terminal operations implementation.
4125: * <b>WARNING! This is temporary</b>
4126: */
4127: public Ops ops() {
4128: return ops;
4129: }
4130:
4131: /**
4132: * Set the Interpreter type by name.
4133: * @see Term#setInterp
4134: */
4135: public void setEmulation(String emulation) {
4136: Interp new_interp = InterpKit.forName(emulation, ops);
4137: if (new_interp == null)
4138: return;
4139: interp = new_interp;
4140: }
4141:
4142: /**
4143: * Returns the termcap string that best describes what this Term
4144: * emulates.
4145: */
4146: public String getEmulation() {
4147: return getInterp().name();
4148: }
4149:
4150: /**
4151: * Set the emulation interpreter.
4152: * <p>
4153: * It is not advisable to change the emulation after Term has been
4154: * connected to a process, since it's often impossible to advise
4155: * the process of the new terminal type.
4156: */
4157: void setInterp(Interp interp) {
4158: this .interp = interp;
4159: }
4160:
4161: /**
4162: * Return the Interpreter assigned to this.
4163: */
4164: Interp getInterp() {
4165: return interp;
4166: }
4167:
4168: private Interp interp = new InterpDumb(ops); // used to InterpANSI
4169:
4170: /**
4171: * Set how many lines of history will be available.
4172: * <p>
4173: * If an anchor is in effect the history size will only have an
4174: * effect when the anchor is reset.
4175: */
4176: public void setHistorySize(int new_size) {
4177: history_size = new_size;
4178: limit_lines();
4179: repaint(true);
4180: }
4181:
4182: /**
4183: * Return the number of lines in history
4184: */
4185: public int getHistorySize() {
4186: return history_size;
4187: }
4188:
4189: private int history_size = 20;
4190:
4191: /**
4192: * Set the width of the glyph gutter in pixels
4193: */
4194: public void setGlyphGutterWidth(int pixels) {
4195:
4196: glyph_gutter_width = pixels;
4197:
4198: // protect against client mistakes?
4199: if (glyph_gutter_width > 30)
4200: glyph_gutter_width = 30;
4201:
4202: updateScreenSize();
4203: }
4204:
4205: private int glyph_gutter_width;
4206:
4207: /**
4208: * Associate an Image with a glyph id, or clear it if image is null.
4209: *
4210: * Numbering the glyphs is confusing. They start with 48. That is,
4211: * if you register glyph #0 using hbvi/vim :K command the escape
4212: * sequence emitted is 48. 48 is ascii '0'.
4213: */
4214: public void setGlyphImage(int glyph_number, Image image) {
4215: if (glyph_number > 256)
4216: return; // SHOULD throw an exception?
4217: glyph_images[glyph_number] = image;
4218: }
4219:
4220: private Image glyph_images[] = new Image[256];;
4221:
4222: /**
4223: * Get the usable area for drawing glyphs
4224: *
4225: * This value changes when the gutter width or the font changes
4226: */
4227: public Dimension getGlyphCellSize() {
4228: return new Dimension(glyph_gutter_width, metrics.height);
4229: }
4230:
4231: /**
4232: * Register up to 8 new custom colors.
4233: *
4234: * Unlike glyph id's you can start the numbers from 0.
4235: * hbvi/vim's :K command will add a 58 to the number, but that
4236: * is the code we interpret as custom color #0.
4237: */
4238: public void setCustomColor(int number, Color c) {
4239: custom_color[number] = c;
4240: }
4241:
4242: private final Color custom_color[] = new Color[8];
4243: private final Color standard_color[] = new Color[8];
4244:
4245: /**
4246: * Get cursor row in buffer coordinates (0-origin).
4247: */
4248: public int getCursorRow() {
4249: return st.cursor.row;
4250: }
4251:
4252: /**
4253: * Get cursor column in buffer coordinates (0-origin)
4254: */
4255: public int getCursorCol() {
4256: return cursor_line().cellToBuf(metrics, st.cursor.col);
4257: }
4258:
4259: /**
4260: * Get (absolute) cursor coordinates.
4261: * <p>
4262: * The returned Coord is newly allocated and need not be cloned.
4263: */
4264: public Coord getCursorCoord() {
4265: Line l = buf.lineAt(st.cursor.row);
4266: return new Coord(new BCoord(st.cursor.row, l.cellToBuf(metrics,
4267: st.cursor.col)), firsta);
4268: }
4269:
4270: /*
4271: *
4272: * Move the cursor to the given (absolute) coordinates
4273: *
4274: * @deprecated, replaced by{@link #setCursorCoord(Coord)}
4275: */
4276: public void goTo(Coord coord) {
4277: setCursorCoord(coord);
4278: }
4279:
4280: /**
4281: * Move the cursor to the given (absolute) coordinates
4282: * SHOULD be setCursorCoord!
4283: */
4284: public void setCursorCoord(Coord coord) {
4285: Coord c = (Coord) coord.clone();
4286: c.clip(st.rows, buf.visibleCols(), firsta);
4287: st.cursor = c.toBCoord(firsta);
4288: st.cursor.col = cursor_line().bufToCell(metrics, st.cursor.col);
4289: repaint(true);
4290: }
4291:
4292: /**
4293: * Control whether the cursor is visible or not.
4294: * <p>
4295: * We don't want a visible cursor when we're using Term in
4296: * non-interactve mode.
4297: */
4298: public void setCursorVisible(boolean cursor_visible) {
4299: this .cursor_visible = cursor_visible;
4300: }
4301:
4302: /**
4303: * Find out if cursor is visible.
4304: */
4305: public boolean isCursorVisible() {
4306: return cursor_visible;
4307: }
4308:
4309: private boolean cursor_visible = true;
4310:
4311: /**
4312: * Back up the coordinate by one character and return new Coord.
4313: * <p>
4314: * Travels back over line boundaries
4315: * <br>
4316: * Returns null if 'c' is the first character in the buffer.
4317: */
4318:
4319: public Coord backup(Coord c) {
4320: BCoord bRow = buf.backup(c.toBCoord(firsta));
4321: if (bRow == null)
4322: return null;
4323: return new Coord(bRow, firsta);
4324: }
4325:
4326: /**
4327: * Advance the coordinate by one charater and return new coord.
4328: * <p>
4329: * Travels forward over line boundaries.
4330: * <br>
4331: * Returns null if 'c' is the last character in the buffer.
4332: */
4333: public Coord advance(Coord c) {
4334: return new Coord(buf.advance(c.toBCoord(firsta)), firsta);
4335: }
4336:
4337: /**
4338: * Get contents of current selection.
4339: * <p>
4340: * Returns 'null' if there is no current selection.
4341: */
4342: public String getSelectedText() {
4343: return sel.getSelection();
4344: }
4345:
4346: /**
4347: * Get the extent of the current selection.
4348: * <p>
4349: * If there is no selection returns 'null'.
4350: */
4351: public Extent getSelectionExtent() {
4352: return sel.getExtent();
4353: }
4354:
4355: /**
4356: * Set the extent of the selection.
4357: */
4358: public void setSelectionExtent(Extent extent) {
4359: extent.begin.clip(buf.nlines, buf.totalCols(), firsta);
4360: extent.end.clip(buf.nlines, buf.totalCols(), firsta);
4361: sel.setExtent(extent);
4362: repaint(false);
4363: }
4364:
4365: /**
4366: * Clear the selection.
4367: */
4368: public void clearSelection() {
4369: sel.cancel(true);
4370: repaint(false);
4371: }
4372:
4373: /**
4374: * Set whether slections automatically get copied to the systemSelection
4375: * when the selection is completed (the button is released).
4376: * <p>
4377: * This is how xterm and other X-windows selections work.
4378: * <p>
4379: * This property can probably be deprecated. It was neccessary in the
4380: * pre-1.4.2 days when we didn't have a systemSelection and we wanted
4381: * to have the option of not cloberring the systemClipboard on text
4382: * selection.
4383: *
4384: * @deprecated selections now always get copied to systemSelection if
4385: * it exists.
4386: */
4387: public void setAutoCopy(boolean auto_copy) {
4388: // no-op
4389: }
4390:
4391: /**
4392: * Return the value set by setAutoCopy()
4393: *
4394: * @deprecated Now always returns 'true'.
4395: */
4396: public boolean isAutoCopy() {
4397: return true;
4398: }
4399:
4400: /*
4401: * Control whether refreshes are enabled.
4402: * <p>
4403: * Turn refresh off if you're about to add a lot of text to the
4404: * terminal. Another way is to use appendText("stuff", false)
4405: */
4406: public void setRefreshEnabled(boolean refresh_enabled) {
4407: this .refresh_enabled = refresh_enabled;
4408: if (refresh_enabled)
4409: repaint(true);
4410: }
4411:
4412: public boolean isRefreshEnabled() {
4413: return refresh_enabled;
4414: }
4415:
4416: private boolean refresh_enabled = true;
4417:
4418: /**
4419: * Sets whether the selection highlighting is XOR style or normal
4420: * Swing style.
4421: */
4422: public void setSelectionXOR(boolean selection_xor) {
4423: this .selection_xor = selection_xor;
4424: repaint(false);
4425: }
4426:
4427: /*
4428: * If returns 'true' then selections are drawn using the xor mode,
4429: * Otherwise they are drawn in regular swing fashion.
4430: */
4431: public boolean isSelectionXOR() {
4432: return selection_xor;
4433: }
4434:
4435: // make accessible to Sel
4436: boolean selection_xor = false;
4437:
4438: /**
4439: * Set the TAB size.
4440: * <p>
4441: * The cursor is moved to the next column such that
4442: * <pre>
4443: * column (0-origin) modulo tab_size == 0
4444: * </pre>
4445: * The cursor will not go past the last column.
4446: * <p>
4447: * Note that the conventional assumption of what a tab is, is not
4448: * entirely accurate. ANSI does not define TABs as above but rather
4449: * as a directive to move to the next "tabstop" which has to have been
4450: * set previously. In fact on unixes it is the terminal line discipline
4451: * that expands tabs to spaces in the conventional way. That in,
4452: * turn explains why TAB information doesn't make it into selections and
4453: * why copying and pasting Makefile instructions is liable to lead
4454: * to hard-to-diagnose make rpoblems, which, in turn drove the ANT people
4455: * to reinvent the world.
4456: */
4457: public void setTabSize(int tab_size) {
4458: this .tab_size = tab_size;
4459: }
4460:
4461: /**
4462: * Get the TAB size.
4463: */
4464: public int getTabSize() {
4465: return tab_size;
4466: }
4467:
4468: private int tab_size = 8;
4469:
4470: /**
4471: * Control whether Term scrolls to the bottom on keyboard input.
4472: * <p>
4473: * This is analogous to the xterm -sk/+sk option.
4474: */
4475: public void setScrollOnInput(boolean scroll_on_input) {
4476: this .scroll_on_input = scroll_on_input;
4477: }
4478:
4479: /**
4480: * Return whether Term scrolls to the bottom on keyboard input.
4481: */
4482: public boolean isScrollOnInput() {
4483: return scroll_on_input;
4484: }
4485:
4486: private boolean scroll_on_input = true;
4487:
4488: /**
4489: * Control whether Term scrolls on any output.
4490: * <p>
4491: * When set to false, if the user moves the scrollbar to see some
4492: * text higher up in history, the view will not change even if more
4493: * output is produced. But if the cursor is visible, scrolling will
4494: * happen. This is so that in an interactive session any prompt will
4495: * be visible etc.
4496: * <p>
4497: * However, the tracking of the cursor is controlled by the 'trackCursor'
4498: * property which by default is set to 'true'.
4499: * <p>
4500: * This property is analogous to the xterm -si/+si option.
4501: */
4502: public void setScrollOnOutput(boolean scroll_on_output) {
4503: this .scroll_on_output = scroll_on_output;
4504: }
4505:
4506: /**
4507: * Return whether Term scrolls on any output.
4508: */
4509: public boolean isScrollOnOutput() {
4510: return scroll_on_output;
4511: }
4512:
4513: private boolean scroll_on_output = true;
4514:
4515: /**
4516: * Control whether Term will scroll to track the cursor as text is added.
4517: * <p>
4518: * If set to true, as output is being generated Term will try to keep
4519: * the cursor in view.
4520: * <p>
4521: * This property is only relevant when scrollOnOutput is set to false.
4522: * If scrollOnOutput is true, this property is also implicitly true.
4523: */
4524: public void setTrackCursor(boolean track_cursor) {
4525: this .track_cursor = track_cursor;
4526: }
4527:
4528: /**
4529: * Return whether Term will scroll to track the cursor as text is added.
4530: */
4531: public boolean isTrackCursor() {
4532: return track_cursor;
4533: }
4534:
4535: private boolean track_cursor = true;
4536:
4537: /**
4538: * Controls horizontal scrolling and line wrapping.
4539: * <p>
4540: * When enabled a horizontal scrollbar becomes visible and line-wrapping
4541: * is disabled.
4542: */
4543: public void setHorizontallyScrollable(
4544: boolean horizontally_scrollable) {
4545: this .horizontally_scrollable = horizontally_scrollable;
4546: // hscroll_bar.setVisible(horizontally_scrollable);
4547: hscroll_wrapper.setVisible(horizontally_scrollable);
4548: }
4549:
4550: /*
4551: * Returns whether horizontal scrolling is enabled.
4552: * @see Term.setHorizontallyScrollable
4553: */
4554: public boolean isHorizontallyScrollable() {
4555: return this .horizontally_scrollable;
4556: }
4557:
4558: private boolean horizontally_scrollable = true;
4559:
4560: /**
4561: * Clear everything and assign new text.
4562: * <p>
4563: * If the size of the text exceeds history early parts of it will get
4564: * lost, unless an anchor was set using setAnchor().
4565: */
4566: public void setText(String text) {
4567: // SHOULD make a bit more efficient
4568: clearHistoryNoRefresh();
4569: appendText(text, true);
4570: }
4571:
4572: /**
4573: * Add new text at the current cursor position.
4574: * <p>
4575: * Doesn't repaint the view unless 'repaint' is set to 'true'.
4576: * <br>
4577: * Doesn't do anything if 'text' is 'null'.
4578: */
4579: public void appendText(String text, boolean repaint) {
4580:
4581: if (text == null)
4582: return;
4583:
4584: ckEventDispatchThread();
4585: // OLD NPE-x synchronized(this)
4586: {
4587: for (int cx = 0; cx < text.length(); cx++) {
4588: putc_work(text.charAt(cx));
4589: if (text.charAt(cx) == '\n')
4590: putc_work('\r');
4591: }
4592: }
4593: if (repaint)
4594: repaint(true);
4595: }
4596:
4597: /**
4598: * Scroll the view 'n' pages up.
4599: * <p>
4600: * A page is the height of the view.
4601: */
4602:
4603: public void pageUp(int n) {
4604: ckEventDispatchThread();
4605: // OLD NPE-x synchronized(this)
4606: {
4607: st.firstx -= n * st.rows;
4608: if (st.firstx < 0)
4609: st.firstx = 0;
4610: }
4611: repaint(true);
4612: }
4613:
4614: /**
4615: * Scroll the view 'n' pages down.
4616: * <p>
4617: * A page is the height of the view.
4618: */
4619:
4620: public void pageDown(int n) {
4621: ckEventDispatchThread();
4622: // OLD NPE-x synchronized(this)
4623: {
4624: st.firstx += n * st.rows;
4625:
4626: if (st.firstx + st.rows > buf.nlines)
4627: st.firstx = buf.nlines - st.rows;
4628: }
4629: repaint(true);
4630: }
4631:
4632: /**
4633: * Scroll the view 'n' lines up.
4634: */
4635: public void lineUp(int n) {
4636: ckEventDispatchThread();
4637: // OLD NPE-x synchronized(this)
4638: {
4639: st.firstx -= n;
4640: if (st.firstx < 0)
4641: st.firstx = 0;
4642: }
4643: repaint(true);
4644: }
4645:
4646: /**
4647: * Scroll the view 'n' lines down.
4648: */
4649: public void lineDown(int n) {
4650: ckEventDispatchThread();
4651: // OLD NPE-x synchronized(this)
4652: {
4653: st.firstx += n;
4654: if (st.firstx + st.rows > buf.nlines)
4655: st.firstx = buf.nlines - st.rows;
4656: }
4657: repaint(true);
4658: }
4659:
4660: /**
4661: * Scroll the view 'n' pages to the left.
4662: */
4663: public void pageLeft(int n) {
4664: columnLeft(n * buf.visibleCols());
4665: }
4666:
4667: /**
4668: * Scroll the view 'n' pages to the right.
4669: */
4670: public void pageRight(int n) {
4671: columnRight(n * buf.visibleCols());
4672: }
4673:
4674: /**
4675: * Scroll the view 'n' columns to the right.
4676: */
4677: public void columnRight(int n) {
4678: ckEventDispatchThread();
4679: // OLD NPE-x synchronized(this)
4680: {
4681: st.firsty += n;
4682: if (st.firsty + buf.visibleCols() > buf.totalCols())
4683: st.firsty = buf.totalCols() - buf.visibleCols();
4684: }
4685: repaint(true);
4686: }
4687:
4688: /**
4689: * Scroll the view 'n' columns to the left.
4690: */
4691: public void columnLeft(int n) {
4692: ckEventDispatchThread();
4693: // OLD NPE-x synchronized(this)
4694: {
4695: st.firsty -= n;
4696: if (st.firsty < 0)
4697: st.firsty = 0;
4698: }
4699: repaint(true);
4700: }
4701:
4702: /**
4703: * Return the cell width of the given character.
4704: */
4705: public int charWidth(char c) {
4706: return metrics.wcwidth(c);
4707: }
4708:
4709: /*
4710: * The following are overrides of JComponent/Component
4711: */
4712:
4713: /**
4714: * Override of JComponent.
4715: * <p>
4716: * We absolutely require fixed width fonts, so if the font is changed
4717: * we create a monospaced version of it with the same style and size.
4718: */
4719:
4720: public void setFont(Font new_font) {
4721:
4722: Font font = new Font("Monospaced", // NOI18N
4723: new_font.getStyle(), new_font.getSize());
4724:
4725: super .setFont(font); // This should invalidate us, which
4726: // ultimately will cause a repaint
4727:
4728: /* DEBUG
4729: System.out.println("Font info:"); // NOI18N
4730: System.out.println("\tlogical name: " + font.getName()); // NOI18N
4731: System.out.println("\tfamily name: " + font.getFamily()); // NOI18N
4732: System.out.println("\tface name: " + font.getFontName()); // NOI18N
4733: */
4734:
4735: // cache the metrics
4736: metrics = new MyFontMetrics(this , font);
4737:
4738: updateScreenSize();
4739: }
4740:
4741: /**
4742: * Override of JComponent.
4743: * <p>
4744: * Pass on the request to the screen where all the actual focus
4745: * management happens.
4746: */
4747: public void requestFocus() {
4748: screen.requestFocus();
4749: }
4750:
4751: public boolean requestFocusInWindow() {
4752: return screen.requestFocusInWindow();
4753: }
4754:
4755: /**
4756: * Override of JComponent.
4757: * <p>
4758: * Pass on enabledness to sub-components (scrollbars and screen)
4759: */
4760: public void setEnabled(boolean enabled) {
4761: // This was done as a result of issue 24824
4762:
4763: super .setEnabled(enabled);
4764:
4765: hscroll_bar.setEnabled(enabled);
4766: vscroll_bar.setEnabled(enabled);
4767: screen.setEnabled(enabled);
4768: }
4769:
4770: //..........................................................................
4771: // Accessibility stuff is all here
4772: // Not just the required interfaces but also all the helpers.
4773: //..........................................................................
4774:
4775: /**
4776: * Since Term is a composite widget the main accessible JComponent is
4777: * not Term but an internal JComponent. We'll speak of Term accessibility
4778: * when we in fact are referring to the that inner component.
4779: * <p>
4780: * Accessibility for Term is tricky because it doesn't fit into the
4781: * roles delineated by Swing. The closest role is that of TEXT and that
4782: * is too bound to how JTextComponent works. To wit ...
4783: <p>
4784: <dl>
4785: <dt>2D vs 1D coordinates
4786: <dd>
4787: Term has a 2D coordinate system while AccessibleText works with 1D
4788: locations. So Term actually has code which translates between the two.
4789: This code is not exactly efficient but only kicks in when assistive
4790: technology latches on.
4791: <br>
4792: Line breaks ('\n's) count as characters! However we only count
4793: logical line breaks ('\n's appearing in the input stream) as opposed to
4794: wrapped lines!
4795: <p>
4796: The current implementation doesn't cache any of the mappings because
4797: that would require a word per line extra storage for the cumulative
4798: char count. The times actually we're pretty fast with a 4000 line
4799: histroy.
4800:
4801: <dt>WORDs and SENTENCEs
4802: <dd>
4803: For AccessibleText.get*Index() functions WORD uses the regular
4804: Term WordDelineator. SENTENCE translates to just a line.
4805:
4806: <dt>Character attributes
4807: <dd>
4808: Term uses the ANSI convention of character attributes so when
4809: AccessibleText.getCharacterAttribute() is used a rough translation
4810: is made as follows:
4811: <ul>
4812: <li> ANSI underscore -> StyleConstants.Underline
4813: <li> ANSI bright/bold -> StyleConstants.Bold
4814: <li> Non-black foreground color -> StyleConstants.Foreground
4815: <li> Explicitly set background color -> StyleConstants.Background
4816: </ul>
4817: Font related information is always constant so it is not provided.
4818:
4819: <dt>History
4820: <dd>
4821: Term has history and lines wink out. If buffer coordinates were
4822: used to interact with accessibility, caretPosition and charCount
4823: would be dancing around. Fortunately Term has absolute coordinates.
4824: So positions returned via AccessibleText might eventually refer to
4825: text that has gone by.
4826:
4827: <dt>Caret and Mark vs Cursor and Selection
4828: <dd>
4829: While Term keeps the selection and cursor coordinates independent,
4830: JTextComponent merges them and AccessibleText inherits this view.
4831: With Term caretPosition is the position of the cursor and selection
4832: ends will not neccessarily match with the caret position.
4833: </dl>
4834: <p>
4835: Currently only notifications of ACCESSIBLE_CARET_PROPERTY and
4836: ACCESSIBLE_TEXT_PROPERTY are fired and that always in pairs.
4837: They are fired on the receipt of any character to be processed.
4838: <p>
4839: IMPORTANT: It is assumed that under assistive technology Term will be
4840: used primarily as a continuous text output device or a readonly document.
4841: Therefore ANSI cursor motion and text editing commands or anything that
4842: mutates the text will completely invalidate all of AccessibleTexts
4843: properties. (Perhaps an exception SHOULD be made for backspace)
4844: */
4845:
4846: public AccessibleContext getAccessibleContext() {
4847: if (accessible_context == null) {
4848: accessible_context = new AccessibleTerm();
4849: }
4850: return accessible_context;
4851: }
4852:
4853: private AccessibleContext accessible_context;
4854:
4855: /*
4856: * Term is really a container. Screen is where things get drawn and
4857: * where focus is set to, so all real accessibility work is done there.
4858: * We just declare us to have a generic role.
4859: */
4860: protected class AccessibleTerm extends AccessibleJComponent {
4861: public AccessibleRole getAccessibleRole() {
4862: return AccessibleRole.PANEL;
4863: }
4864:
4865: public void setAccessibleName(String name) {
4866: screen.getAccessibleContext().setAccessibleName(name);
4867: }
4868: }
4869:
4870: /**
4871: * [DO NOT USE] Convert a 2D Coord to a 1D linear position.
4872: * <p>
4873: * This function really should be private but I need it to be public for
4874: * unit-testing purposes.
4875: */
4876: public int CoordToPosition(Coord c) {
4877: BCoord b = c.toBCoord(firsta);
4878: int nchars = charsInPrehistory;
4879: for (int r = 0; r < b.row; r++) {
4880: Line l = buf.lineAt(r);
4881: nchars += l.length();
4882: if (!l.isWrapped())
4883: nchars += 1;
4884: }
4885:
4886: nchars += c.col;
4887:
4888: return nchars;
4889: }
4890:
4891: /**
4892: * [DO NOT USE] Convert a 1D linear position to a 2D Coord.
4893: * <p>
4894: * This function really should be private but I need it to be public for
4895: * unit-testing purposes.
4896: */
4897: public Coord PositionToCoord(int position) {
4898: int nchars = charsInPrehistory;
4899: for (int r = 0; r < buf.nlines; r++) {
4900: Line l = buf.lineAt(r);
4901: nchars += l.length();
4902: if (!l.isWrapped())
4903: nchars += 1;
4904: if (nchars > position) {
4905: BCoord b = new BCoord();
4906: b.row = r;
4907: b.col = buf.lineAt(r).length() + 1
4908: - (nchars - position);
4909: return new Coord(b, firsta);
4910: }
4911: }
4912: return null;
4913: }
4914:
4915: /**
4916: * Return the number of characters stored.
4917: * <p>
4918: * Include logical newlines for now to match the above conversions.
4919: * Hmm, do we include chars in prehistory?
4920: */
4921:
4922: int getCharCount() {
4923: int nchars = charsInPrehistory;
4924: for (int r = 0; r < buf.nlines; r++) {
4925: Line l = buf.lineAt(r);
4926: nchars += l.length();
4927: if (!l.isWrapped())
4928: nchars += 1;
4929: }
4930: return nchars;
4931: }
4932:
4933: /**
4934: * Return the bounding rectangle of the character at the given coordinate
4935: */
4936: Rectangle getCharacterBounds(Coord c) {
4937: if (c == null)
4938: return null;
4939: BCoord b = c.toBCoord(firsta);
4940:
4941: char ch = '\0';
4942: try {
4943: Line l = buf.lineAt(b.row);
4944: ch = l.charArray()[b.col];
4945: } catch (Exception x) {
4946: ;
4947: }
4948:
4949: Point p1 = toPixel(b);
4950: Rectangle rect = new Rectangle();
4951: rect.x = p1.x;
4952: rect.y = p1.y;
4953: rect.width = metrics.width * charWidth(ch);
4954: rect.height = metrics.height;
4955: return rect;
4956: }
4957:
4958: Color backgroundColor(boolean reverse, int attr) {
4959: Color bg = null;
4960: if (reverse) {
4961: int fcx = Attr.foregroundColor(attr);
4962: if (fcx != 0 && fcx <= 8) {
4963: bg = standard_color[fcx - 1];
4964: } else if (fcx > 8) {
4965: bg = custom_color[fcx - 9];
4966: } else {
4967: bg = actual_foreground;
4968: }
4969:
4970: } else {
4971: int bcx = Attr.backgroundColor(attr);
4972: if (bcx != 0 && bcx <= 8) {
4973: bg = standard_color[bcx - 1];
4974: } else if (bcx > 8) {
4975: bg = custom_color[bcx - 9];
4976: }
4977: }
4978: return bg;
4979: }
4980:
4981: Color foregroundColor(boolean reverse, int attr) {
4982: Color fg = null;
4983: if (reverse) {
4984: int bcx = Attr.backgroundColor(attr);
4985: if (bcx != 0 && bcx <= 8) {
4986: fg = standard_color[bcx - 1];
4987: } else if (bcx > 8) {
4988: fg = custom_color[bcx - 9];
4989: } else {
4990: fg = actual_background;
4991: }
4992:
4993: } else {
4994: int fcx = Attr.foregroundColor(attr);
4995: if (fcx != 0 && fcx <= 8) {
4996: fg = standard_color[fcx - 1];
4997: } else if (fcx > 8) {
4998: fg = custom_color[fcx - 9];
4999: } else {
5000: fg = actual_foreground;
5001: }
5002: }
5003: return fg;
5004: }
5005:
5006: private static void ckEventDispatchThread() {
5007: /*
5008: if (!SwingUtilities.isEventDispatchThread()) {
5009: System.out.println("term: NOT IN EventDispatchThread");
5010: Thread.dumpStack();
5011: }
5012: */
5013: }
5014:
5015: /* attaches MouseWheelHandler to scroll the component
5016: */
5017: private static void addMouseWheelHandler(JComponent comp,
5018: JScrollBar bar) {
5019: comp.addMouseWheelListener(new MouseWheelHandler(bar)); // XXX who removes this lsnr?
5020: }
5021:
5022: private static class MouseWheelHandler implements
5023: MouseWheelListener {
5024:
5025: private JScrollBar scrollbar;
5026:
5027: public MouseWheelHandler(JScrollBar scrollbar) {
5028: this .scrollbar = scrollbar;
5029: }
5030:
5031: public void mouseWheelMoved(MouseWheelEvent e) {
5032: int totalScrollAmount = e.getUnitsToScroll()
5033: * scrollbar.getUnitIncrement();
5034: scrollbar
5035: .setValue(scrollbar.getValue() + totalScrollAmount);
5036: }
5037:
5038: }
5039: }
|