blob: cbe620fea24d429711f843372e763c07bef6d916 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* "Sel.java"
* Sel.java 1.22 01/07/26
*/
package org.netbeans.lib.terminalemulator;
import java.awt.*;
import java.awt.datatransfer.*;
/**
* Selection expert:
* <ul>
* <li> Tracks gestures
* <li> Retrieves actual selected text
* <li> Paints the selection into a given Graphic
* </ul>
*
* The following actions are understood and dealt with:
* <dl>
* <dt> track()
* <dd> in general extends a selection.
* It is usually connecetd to a mouse drag event.
* <dt> track()
* <dd> will initiate a character-sized selection if no selection exists.
* <dt> select_word()
* <dd> selects a "word" after which track() extends the selection by words.
* It is usually connected to a left double-click.
* <dt> slect_line()
* <dd> selects a line after which track() extend the selection by lines.
* It is usually connected to a left triple-click.
* <dt> done()
* <dd> stops tracking. The current selection is stuffed into the clipboard.
* It is usually connected to a left release.
* <dt> extend()
* <dd> only extends an existing selection. (Doesn't start one like track)
* It is usually connected to a mouse right-press.
* <dt> cancel() nulls the selection.
* </dl>
* <p>
* A Selection has two components that need tracking, the visual feedback
* and the selected text. As the history scrolls out the visual feedback
* will be eliminated, but we (SHOULD but don't yet) retain the text so
* that copy actions still retreieve it properly. DtTerm actually
* suffers from this.
* <p>
* The selection has an origin and an extent. In general the origin and
* extent are not ordered (that is extent might be _before_ origin). they
* get ordered as needed.
*/
class Sel implements ClipboardOwner {
// What granularity of selection are we working with
private static final int SEL_NONE = 0;
private static final int SEL_CHAR = 1;
private static final int SEL_WORD = 2;
private static final int SEL_LINE = 3;
// See intersection() for explanation.
public static final int INT_NONE = 0;
public static final int INT_ABOVE = 1;
public static final int INT_ON = 2;
public static final int INT_STRADDLES = 3;
public static final int INT_BELOW = 4;
private int sel_tracking;
private int old_sel_tracking;
// origin and extent are kept in absolute RowCol coordinates
private Coord sel_origin;
/* TMP private */ public Coord sel_extent;
// The word that was initially selected by select_word
// Used by 'track()'.
private Extent initial_word;
private Term term;
private State state;
// properties:
private Color color = new Color(204, 204, 255); // swing color
void setColor(Color color) { this.color = color; }
Color getColor() { return color; }
private Color xor_color = Color.white;
void setXORColor(Color color) { xor_color = color; }
Color getXORColor() { return xor_color; }
Sel(Term term, State state) {
this.term = term;
this.state = state;
}
/**
* Adjust the selection against 'afirstline'.
* <p>
* As the selection reaches the top of the history buffer it will get
* trimmed until eventually all of it will go away.
*
* This form doesn't work if the selection is "split" by insertion of
* lines. Maybe we SHOULD have two arguments, adjust origin and adjust
* extent?
*/
void adjust(int afirstline, int amount, int alastline) {
if (sel_origin == null)
return;
/* DEBUG
System.out.println("Sel.adjust origin " + sel_origin.row + " " + sel_origin.col); // NOI18N
System.out.println("Sel.adjust extent " + sel_extent.row + " " + sel_extent.col); // NOI18N
System.out.println("Sel.adjust afirstline " + afirstline + " amount " + amount + " alastline " + alastline); // NOI18N
*/
if (sel_origin.compareTo(sel_extent) >= 0) {
// extent before origin
sel_extent.row += amount;
if (sel_extent.row < afirstline)
sel_extent.row = afirstline;
sel_origin.row += amount;
if (sel_origin.row >= alastline) {
sel_origin.row = alastline-1;
sel_origin.col = term.buf().totalCols();
}
if (sel_origin.row < afirstline || sel_extent.row > alastline) {
// it has completely vanished
sel_extent = null;
sel_origin = null;
}
} else {
// origin before extent
sel_origin.row += amount;
if (sel_origin.row < afirstline)
sel_origin.row = afirstline;
sel_extent.row += amount;
if (sel_extent.row >= alastline) {
sel_extent.row = alastline-1;
sel_extent.col = term.buf().totalCols();
}
if (sel_extent.row < afirstline || sel_origin.row > alastline) {
// it has completely vanished
sel_origin = null;
sel_extent = null;
}
}
term.fireSelectionExtentChanged();
}
void relocate(int from, int to) {
if (sel_origin == null)
return;
int delta = to - from;
sel_origin.row += delta;
sel_extent.row += delta;
}
Extent getExtent() {
if (sel_origin == null)
return null;
Extent x = new Extent(sel_origin, sel_extent);
x.order();
return x;
}
void setExtent(Extent extent) {
cancel(false);
extent.order();
sel_origin = (Coord) extent.begin.clone();
sel_extent = (Coord) extent.end.clone();
done(/* OLD false */); // so it makes it into clipboard
}
public void select_word(Extent range) {
sel_origin = new Coord(range.begin);
sel_extent = new Coord(range.end);
sel_tracking = Sel.SEL_WORD;
old_sel_tracking = Sel.SEL_NONE;
initial_word = range;
}
public void select_line(Coord coord) {
// LATER coord.clip(term.buf.nlines, term.buf.totalCols(), term.firsta);
sel_origin = Coord.make(coord.row, 0);
sel_extent = Coord.make(coord.row, term.buf().totalCols());
sel_tracking = Sel.SEL_LINE;
old_sel_tracking = Sel.SEL_NONE;
}
public void select_line(Extent range) {
sel_origin = new Coord(range.begin);
sel_extent = new Coord(range.end);
sel_tracking = Sel.SEL_LINE;
old_sel_tracking = Sel.SEL_NONE;
}
private boolean extend_work(Coord p, int tracking) {
/*
* return true if a screen refresh is needed
*/
if (tracking == Sel.SEL_NONE) {
return false;
} else if (tracking == Sel.SEL_CHAR) {
sel_extent = p;
} else if (tracking == Sel.SEL_WORD) {
BExtent Bnew_range = term.buf().find_word(term.getWordDelineator(), p.toBCoord(term.firsta()));
Extent new_range = Bnew_range.toExtent(term.firsta());
if (p.compareTo(initial_word.begin) < 0) {
sel_origin = new Coord(new_range.begin);
sel_extent = initial_word.end;
} else if (p.compareTo(initial_word.end) > 0) {
sel_origin = initial_word.begin;
sel_extent = new Coord(new_range.end);
} else {
sel_origin = initial_word.begin;
sel_extent = initial_word.end;
}
} else if (tracking == Sel.SEL_LINE) {
if (p.compareTo(sel_origin) > 0) {
sel_origin = Coord.make(sel_origin.row, 0);
sel_extent = Coord.make(p.row, term.buf().totalCols());
} else {
sel_origin = Coord.make(sel_origin.row, term.buf().totalCols());
sel_extent = Coord.make(p.row, 0);
}
}
return true;
}
public void track(Coord p) {
if (sel_tracking == Sel.SEL_NONE) {
// initiate a selection
sel_origin = p;
sel_extent = p;
sel_tracking = Sel.SEL_CHAR;
old_sel_tracking = Sel.SEL_NONE;
}
extend_work(p, sel_tracking);
}
public boolean extend(Coord p) {
// return true if a screen refresh is needed
if (sel_origin == null)
return false;
else
return extend_work(p, old_sel_tracking);
}
/*
* Variation on cancel which doesn't update the Selection.
* Used by lostOwnership() in addition to plain cancel().
*/
private boolean cancelHelp(boolean and_fire) {
if (sel_origin == null)
return false;
old_sel_tracking = Sel.SEL_NONE;
sel_tracking = Sel.SEL_NONE;
sel_origin = null;
sel_extent = null;
initial_word = null;
if (and_fire)
term.fireSelectionExtentChanged();
return true;
}
public boolean cancel(boolean and_fire) {
if (!cancelHelp(and_fire))
return false;
term.copyToSelection();
return true;
}
public void done(/* OLD boolean force_copy */) {
// don't track anymore
// but extend will still work
old_sel_tracking = sel_tracking;
sel_tracking = Sel.SEL_NONE;
term.copyToSelection();
term.fireSelectionExtentChanged();
}
public void lostOwnership(Clipboard cb, Transferable c) {
/*
* Part of the ClipboardOwner interface.
* The string created in sel_done should be retained until
* this function is called.
*/
/* DEBUG
System.out.println("lostOwnership()"); // NOI18N
*/
if (cancelHelp(true))
term.repaint(false);
}
public String getSelection() {
Extent x = getExtent();
if (x == null)
return null;
if (x.begin != null && x.end != null) {
final StringBuffer text = new StringBuffer();
term.visitLines(x.begin, x.end, true, new LineVisitor() {
public boolean visit(Line l, int row, int bcol, int ecol) {
text.append(l.text(bcol, ecol));
return true;
}
} );
return text.toString();
}
return ""; //NOI18N
}
/*
* Helps decide what to do with the selection when a line is
* added, removed or cleared.
*/
int intersection(int line) {
/* DEBUG
if (sel_origin == null) {
System.out.println("Sel.intersection(" + line + ") no selection"); // NOI18N
} else {
System.out.println("Sel.intersection(" + line + ")" + // NOI18N
" sel_origin.row = " + sel_origin.row + // NOI18N
" sel_extent.row = " + sel_extent.row); // NOI18N
}
*/
Extent x = getExtent();
if (x == null)
return INT_NONE;
x.order();
if (x.end.row < line)
return INT_ABOVE;
else if (x.end.row == line)
return INT_ON;
else if (x.begin.row > line)
return INT_BELOW;
else
return INT_STRADDLES;
}
/**
* Select inside one line
* Rows and columns are in absolute coords.
*/
private void paint(Graphics g, int row, int bcol, int ecol) {
// Instead of doing this SHOULD clip the Extent to what's in view
// Row is outside the view
if (row < state.firstx)
return;
if (row > state.firstx + state.rows)
return;
// Construct the rectangle we're going to paint
BCoord begin = new BCoord(row, bcol);
BCoord end = new BCoord(row, ecol);
begin = term.toViewCoord(begin);
end = term.toViewCoord(end);
//Hotfix for issue 40189
if (begin == null || end == null) {
return;
}
int lw; // width of last character in selection
Line l = term.buf().lineAt(row);
lw = l.width(term.metrics(), ecol);
Point pbegin = term.toPixel(begin);
Point pend = term.toPixel(end);
pend.y += term.metrics().height;
pend.x += term.metrics().width * lw; // xterm actually doesn't do this
Dimension dim = new Dimension(pend.x - pbegin.x,
pend.y - pbegin.y);
Rectangle rect = new Rectangle(pbegin, dim);
if (term.isSelectionXOR())
g.setXORMode(xor_color);
else
g.setColor(color);
g.fillRect(rect.x, rect.y, rect.width, rect.height);
}
void paint(final Graphics g) {
/*
* Paint the selection.
*/
Extent x = getExtent();
if (x == null)
return;
x.order();
// DEBUG System.out.println("Sel.paint extent: " + x); // NOI18N
term.visitLines(x.begin, x.end, true, new LineVisitor() {
public boolean visit(Line l, int row, int bcol, int ecol) {
paint(g, row, bcol, ecol);
return true;
}
} );
}
}