001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: *
041: * Contributor(s): Ivan Soleimanipour.
042: */
043:
044: /*
045: * "Sel.java"
046: * Sel.java 1.22 01/07/26
047: */
048:
049: package org.netbeans.lib.terminalemulator;
050:
051: import java.awt.*;
052: import java.awt.datatransfer.*;
053:
054: /**
055: * Selection expert:
056: * <ul>
057: * <li> Tracks gestures
058: * <li> Retrieves actual selected text
059: * <li> Paints the selection into a given Graphic
060: * </ul>
061: *
062: * The following actions are understood and dealt with:
063: * <dl>
064: * <dt> track()
065: * <dd> in general extends a selection.
066: * It is usually connecetd to a mouse drag event.
067: * <dt> track()
068: * <dd> will initiate a character-sized selection if no selection exists.
069: * <dt> select_word()
070: * <dd> selects a "word" after which track() extends the selection by words.
071: * It is usually connected to a left double-click.
072: * <dt> slect_line()
073: * <dd> selects a line after which track() extend the selection by lines.
074: * It is usually connected to a left triple-click.
075: * <dt> done()
076: * <dd> stops tracking. The current selection is stuffed into the clipboard.
077: * It is usually connected to a left release.
078: * <dt> extend()
079: * <dd> only extends an existing selection. (Doesn't start one like track)
080: * It is usually connected to a mouse right-press.
081: * <dt> cancel() nulls the selection.
082: * </dl>
083: * <p>
084: * A Selection has two components that need tracking, the visual feedback
085: * and the selected text. As the history scrolls out the visual feedback
086: * will be eliminated, but we (SHOULD but don't yet) retain the text so
087: * that copy actions still retreieve it properly. DtTerm actually
088: * suffers from this.
089: * <p>
090: * The selection has an origin and an extent. In general the origin and
091: * extent are not ordered (that is extent might be _before_ origin). they
092: * get ordered as needed.
093: */
094:
095: class Sel implements ClipboardOwner {
096: // What granularity of selection are we working with
097: private static final int SEL_NONE = 0;
098: private static final int SEL_CHAR = 1;
099: private static final int SEL_WORD = 2;
100: private static final int SEL_LINE = 3;
101:
102: // See intersection() for explanation.
103: public static final int INT_NONE = 0;
104: public static final int INT_ABOVE = 1;
105: public static final int INT_ON = 2;
106: public static final int INT_STRADDLES = 3;
107: public static final int INT_BELOW = 4;
108:
109: private int sel_tracking;
110: private int old_sel_tracking;
111:
112: // origin and extent are kept in absolute RowCol coordinates
113: private Coord sel_origin;
114: /* TMP private */public Coord sel_extent;
115:
116: // The word that was initially selected by select_word
117: // Used by 'track()'.
118: private Extent initial_word;
119:
120: private Term term;
121: private State state;
122:
123: // properties:
124: private Color color = new Color(204, 204, 255); // swing color
125:
126: void setColor(Color color) {
127: this .color = color;
128: }
129:
130: Color getColor() {
131: return color;
132: }
133:
134: private Color xor_color = Color.white;
135:
136: void setXORColor(Color color) {
137: xor_color = color;
138: }
139:
140: Color getXORColor() {
141: return xor_color;
142: }
143:
144: Sel(Term term, State state) {
145: this .term = term;
146: this .state = state;
147: }
148:
149: /**
150: * Adjust the selection against 'afirstline'.
151: * <p>
152: * As the selection reaches the top of the history buffer it will get
153: * trimmed until eventually all of it will go away.
154: *
155: * This form doesn't work if the selection is "split" by insertion of
156: * lines. Maybe we SHOULD have two arguments, adjust origin and adjust
157: * extent?
158: */
159: void adjust(int afirstline, int amount, int alastline) {
160:
161: if (sel_origin == null)
162: return;
163:
164: /* DEBUG
165: System.out.println("Sel.adjust origin " + sel_origin.row + " " + sel_origin.col); // NOI18N
166: System.out.println("Sel.adjust extent " + sel_extent.row + " " + sel_extent.col); // NOI18N
167: System.out.println("Sel.adjust afirstline " + afirstline + " amount " + amount + " alastline " + alastline); // NOI18N
168: */
169:
170: if (sel_origin.compareTo(sel_extent) >= 0) {
171: // extent before origin
172: sel_extent.row += amount;
173: if (sel_extent.row < afirstline)
174: sel_extent.row = afirstline;
175:
176: sel_origin.row += amount;
177: if (sel_origin.row >= alastline) {
178: sel_origin.row = alastline - 1;
179: sel_origin.col = term.buf.totalCols();
180: }
181: if (sel_origin.row < afirstline
182: || sel_extent.row > alastline) {
183: // it has completely vanished
184: sel_extent = null;
185: sel_origin = null;
186: }
187:
188: } else {
189: // origin before extent
190: sel_origin.row += amount;
191: if (sel_origin.row < afirstline)
192: sel_origin.row = afirstline;
193:
194: sel_extent.row += amount;
195: if (sel_extent.row >= alastline) {
196: sel_extent.row = alastline - 1;
197: sel_extent.col = term.buf.totalCols();
198: }
199: if (sel_extent.row < afirstline
200: || sel_origin.row > alastline) {
201: // it has completely vanished
202: sel_origin = null;
203: sel_extent = null;
204: }
205: }
206:
207: term.fireSelectionExtentChanged();
208: }
209:
210: void relocate(int from, int to) {
211: if (sel_origin == null)
212: return;
213: int delta = to - from;
214: sel_origin.row += delta;
215: sel_extent.row += delta;
216: }
217:
218: Extent getExtent() {
219: if (sel_origin == null)
220: return null;
221: Extent x = new Extent(sel_origin, sel_extent);
222: x.order();
223: return x;
224: }
225:
226: void setExtent(Extent extent) {
227: cancel(false);
228: extent.order();
229: sel_origin = (Coord) extent.begin.clone();
230: sel_extent = (Coord) extent.end.clone();
231: done(/* OLD false */); // so it makes it into clipboard
232: }
233:
234: public void select_word(Extent range) {
235: sel_origin = new Coord(range.begin);
236: sel_extent = new Coord(range.end);
237: sel_tracking = Sel.SEL_WORD;
238: old_sel_tracking = Sel.SEL_NONE;
239: initial_word = range;
240: }
241:
242: public void select_line(Coord coord) {
243:
244: // LATER coord.clip(term.buf.nlines, term.buf.totalCols(), term.firsta);
245:
246: sel_origin = Coord.make(coord.row, 0);
247: sel_extent = Coord.make(coord.row, term.buf.totalCols());
248: sel_tracking = Sel.SEL_LINE;
249: old_sel_tracking = Sel.SEL_NONE;
250: }
251:
252: private boolean extend_work(Coord p, int tracking) {
253: /*
254: * return true if a screen refresh is needed
255: */
256: if (tracking == Sel.SEL_NONE) {
257: return false;
258:
259: } else if (tracking == Sel.SEL_CHAR) {
260: sel_extent = p;
261:
262: } else if (tracking == Sel.SEL_WORD) {
263: BExtent Bnew_range = term.buf.find_word(
264: term.word_delineator, p.toBCoord(term.firsta));
265: Extent new_range = Bnew_range.toExtent(term.firsta);
266: if (p.compareTo(initial_word.begin) < 0) {
267: sel_origin = new Coord(new_range.begin);
268: sel_extent = initial_word.end;
269: } else if (p.compareTo(initial_word.end) > 0) {
270: sel_origin = initial_word.begin;
271: sel_extent = new Coord(new_range.end);
272: } else {
273: sel_origin = initial_word.begin;
274: sel_extent = initial_word.end;
275: }
276:
277: } else if (tracking == Sel.SEL_LINE) {
278: if (p.compareTo(sel_origin) > 0) {
279: sel_origin = Coord.make(sel_origin.row, 0);
280: sel_extent = Coord.make(p.row, term.buf.totalCols());
281: } else {
282: sel_origin = Coord.make(sel_origin.row, term.buf
283: .totalCols());
284: sel_extent = Coord.make(p.row, 0);
285: }
286: }
287: return true;
288: }
289:
290: public void track(Coord p) {
291: if (sel_tracking == Sel.SEL_NONE) {
292: // initiate a selection
293: sel_origin = p;
294: sel_extent = p;
295: sel_tracking = Sel.SEL_CHAR;
296: old_sel_tracking = Sel.SEL_NONE;
297: }
298: extend_work(p, sel_tracking);
299: }
300:
301: public boolean extend(Coord p) {
302: // return true if a screen refresh is needed
303: if (sel_origin == null)
304: return false;
305: else
306: return extend_work(p, old_sel_tracking);
307: }
308:
309: /*
310: * Variation on cancel which doesn't update the Selection.
311: * Used by lostOwnership() in addition to plain cancel().
312: */
313: private boolean cancelHelp(boolean and_fire) {
314: if (sel_origin == null)
315: return false;
316: old_sel_tracking = Sel.SEL_NONE;
317: sel_tracking = Sel.SEL_NONE;
318: sel_origin = null;
319: sel_extent = null;
320: initial_word = null;
321:
322: if (and_fire)
323: term.fireSelectionExtentChanged();
324:
325: return true;
326: }
327:
328: public boolean cancel(boolean and_fire) {
329: if (!cancelHelp(and_fire))
330: return false;
331: term.copyToSelection();
332: return true;
333: }
334:
335: public void done(/* OLD boolean force_copy */) {
336: // don't track anymore
337: // but extend will still work
338: old_sel_tracking = sel_tracking;
339: sel_tracking = Sel.SEL_NONE;
340:
341: term.copyToSelection();
342:
343: term.fireSelectionExtentChanged();
344: }
345:
346: public void lostOwnership(Clipboard cb, Transferable c) {
347: /*
348: * Part of the ClipboardOwner interface.
349: * The string created in sel_done should be retained until
350: * this function is called.
351: */
352: /* DEBUG
353: System.out.println("lostOwnership()"); // NOI18N
354: */
355: if (cancelHelp(true))
356: term.repaint(false);
357: }
358:
359: public String getSelection() {
360:
361: Extent x = getExtent();
362: if (x == null)
363: return null;
364:
365: if (x.begin != null && x.end != null) {
366: final StringBuffer text = new StringBuffer();
367: term.visitLines(x.begin, x.end, true, new LineVisitor() {
368: public boolean visit(Line l, int row, int bcol, int ecol) {
369: text.append(l.text(bcol, ecol));
370: return true;
371: }
372: });
373: return text.toString();
374: }
375:
376: return ""; //NOI18N
377: }
378:
379: /*
380: * Helps decide what to do with the selection when a line is
381: * added, removed or cleared.
382: */
383: int intersection(int line) {
384: /* DEBUG
385: if (sel_origin == null) {
386: System.out.println("Sel.intersection(" + line + ") no selection"); // NOI18N
387: } else {
388: System.out.println("Sel.intersection(" + line + ")" + // NOI18N
389: " sel_origin.row = " + sel_origin.row + // NOI18N
390: " sel_extent.row = " + sel_extent.row); // NOI18N
391: }
392: */
393:
394: Extent x = getExtent();
395: if (x == null)
396: return INT_NONE;
397: x.order();
398:
399: if (x.end.row < line)
400: return INT_ABOVE;
401: else if (x.end.row == line)
402: return INT_ON;
403: else if (x.begin.row > line)
404: return INT_BELOW;
405: else
406: return INT_STRADDLES;
407: }
408:
409: /**
410: * Select inside one line
411: * Rows and columns are in absolute coords.
412: */
413: private void paint(Graphics g, int row, int bcol, int ecol) {
414:
415: // Instead of doing this SHOULD clip the Extent to what's in view
416:
417: // Row is outside the view
418: if (row < state.firstx)
419: return;
420: if (row > state.firstx + state.rows)
421: return;
422:
423: // Construct the rectangle we're going to paint
424: BCoord begin = new BCoord(row, bcol);
425: BCoord end = new BCoord(row, ecol);
426:
427: begin = term.toViewCoord(begin);
428: end = term.toViewCoord(end);
429:
430: //Hotfix for issue 40189
431: if (begin == null || end == null) {
432: return;
433: }
434:
435: int lw; // width of last character in selection
436: Line l = term.buf.lineAt(row);
437: lw = l.width(term.metrics, ecol);
438:
439: Point pbegin = term.toPixel(begin);
440: Point pend = term.toPixel(end);
441: pend.y += term.metrics.height;
442: pend.x += term.metrics.width * lw; // xterm actually doesn't do this
443:
444: Dimension dim = new Dimension(pend.x - pbegin.x, pend.y
445: - pbegin.y);
446: Rectangle rect = new Rectangle(pbegin, dim);
447:
448: if (term.selection_xor)
449: g.setXORMode(xor_color);
450: else
451: g.setColor(color);
452:
453: g.fillRect(rect.x, rect.y, rect.width, rect.height);
454: }
455:
456: void paint(final Graphics g) {
457: /*
458: * Paint the selection.
459: */
460:
461: Extent x = getExtent();
462: if (x == null)
463: return;
464: x.order();
465:
466: // DEBUG System.out.println("Sel.paint extent: " + x); // NOI18N
467:
468: term.visitLines(x.begin, x.end, true, new LineVisitor() {
469: public boolean visit(Line l, int row, int bcol, int ecol) {
470: paint(g, row, bcol, ecol);
471: return true;
472: }
473: });
474: }
475: }
|