001: /*
002: * SelectionManager.java
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2004 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.textarea;
024:
025: //{{{ Imports
026: import java.util.ArrayList;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Set;
030: import java.util.TreeSet;
031: import org.gjt.sp.jedit.buffer.*;
032:
033: //}}}
034:
035: class SelectionManager {
036: // this is package-private so that the painter can use it without
037: // having to call getSelection() (which involves an array copy)
038: List<Selection> selection;
039:
040: //{{{ SelectionManager constructor
041: SelectionManager(TextArea textArea) {
042: this .textArea = textArea;
043: selection = new ArrayList<Selection>();
044: } //}}}
045:
046: //{{{ getSelectionCount() method
047: /**
048: * Returns the number of selections. This can be used to test
049: * for the existence of selections.
050: */
051: int getSelectionCount() {
052: return selection.size();
053: } //}}}
054:
055: //{{{ getSelection() method
056: /**
057: * Returns the current selection.
058: * @since jEdit 3.2pre1
059: */
060: public Selection[] getSelection() {
061: return selection.toArray(new Selection[selection.size()]);
062: } //}}}
063:
064: //{{{ setSelection() method
065: /**
066: * Sets the selection. Nested and overlapping selections are merged
067: * where possible.
068: */
069: void setSelection(Selection[] selection) {
070: this .selection.clear();
071: addToSelection(selection);
072: } //}}}
073:
074: //{{{ addToSelection() method
075: /**
076: * Adds to the selection. Nested and overlapping selections are merged
077: * where possible. Null elements of the array are ignored.
078: * @param selection The new selection
079: * since jEdit 3.2pre1
080: */
081: void addToSelection(Selection[] selection) {
082: if (selection != null) {
083: for (int i = 0; i < selection.length; i++) {
084: Selection s = selection[i];
085: if (s != null)
086: addToSelection(s);
087: }
088: }
089: } //}}}
090:
091: //{{{ addToSelection() method
092: void addToSelection(Selection addMe) {
093: if (addMe.start > addMe.end) {
094: throw new IllegalArgumentException(addMe.start + " > "
095: + addMe.end);
096: } else if (addMe.start == addMe.end) {
097: if (addMe instanceof Selection.Range)
098: return;
099: else if (addMe instanceof Selection.Rect) {
100: if (((Selection.Rect) addMe).extraEndVirt == 0)
101: return;
102: }
103: }
104:
105: Iterator<Selection> iter = selection.iterator();
106: while (iter.hasNext()) {
107: // try and merge existing selections one by
108: // one with the new selection
109: Selection s = iter.next();
110: if (s.overlaps(addMe)) {
111: addMe.start = Math.min(s.start, addMe.start);
112: addMe.end = Math.max(s.end, addMe.end);
113: iter.remove();
114: }
115: }
116:
117: addMe.startLine = textArea.getLineOfOffset(addMe.start);
118: addMe.endLine = textArea.getLineOfOffset(addMe.end);
119:
120: boolean added = false;
121:
122: for (int i = 0; i < selection.size(); i++) {
123: Selection s = selection.get(i);
124: if (addMe.start < s.start) {
125: selection.add(i, addMe);
126: added = true;
127: break;
128: }
129: }
130:
131: if (!added)
132: selection.add(addMe);
133:
134: textArea.invalidateLineRange(addMe.startLine, addMe.endLine);
135: } //}}}
136:
137: //{{{ setSelection() method
138: /**
139: * Sets the selection. Nested and overlapping selections are merged
140: * where possible.
141: */
142: void setSelection(Selection selection) {
143: this .selection.clear();
144:
145: if (selection != null)
146: addToSelection(selection);
147: } //}}}
148:
149: //{{{ getSelectionAtOffset() method
150: /**
151: * Returns the selection containing the specific offset, or <code>null</code>
152: * if there is no selection at that offset.
153: * @param offset The offset
154: * @since jEdit 3.2pre1
155: */
156: Selection getSelectionAtOffset(int offset) {
157: if (selection != null) {
158: for (Selection s : selection) {
159: if (offset >= s.start && offset <= s.end)
160: return s;
161: }
162: }
163:
164: return null;
165: } //}}}
166:
167: //{{{ removeFromSelection() method
168: /**
169: * Deactivates the specified selection.
170: * @param sel The selection
171: */
172: void removeFromSelection(Selection sel) {
173: selection.remove(sel);
174: } //}}}
175:
176: //{{{ resizeSelection() method
177: /**
178: * Resizes the selection at the specified offset, or creates a new
179: * one if there is no selection at the specified offset. This is a
180: * utility method that is mainly useful in the mouse event handler
181: * because it handles the case of end being before offset gracefully
182: * (unlike the rest of the selection API).
183: * @param offset The offset
184: * @param end The new selection end
185: * @param extraEndVirt Only for rectangular selections - specifies how
186: * far it extends into virtual space.
187: * @param rect Make the selection rectangular?
188: */
189: void resizeSelection(int offset, int end, int extraEndVirt,
190: boolean rect) {
191: boolean reversed = false;
192: if (end < offset) {
193: int tmp = offset;
194: offset = end;
195: end = tmp;
196: reversed = true;
197: }
198:
199: Selection newSel;
200: if (rect) {
201: Selection.Rect rectSel = new Selection.Rect(offset, end);
202: if (reversed)
203: rectSel.extraStartVirt = extraEndVirt;
204: else
205: rectSel.extraEndVirt = extraEndVirt;
206: newSel = rectSel;
207: } else
208: newSel = new Selection.Range(offset, end);
209:
210: addToSelection(newSel);
211: } //}}}
212:
213: //{{{ getSelectedLines() method
214: /**
215: * Returns a sorted array of line numbers on which a selection or
216: * selections are present.<p>
217: *
218: * This method is the most convenient way to iterate through selected
219: * lines in a buffer. The line numbers in the array returned by this
220: * method can be passed as a parameter to such methods as
221: * {@link org.gjt.sp.jedit.Buffer#getLineText(int)}.
222: */
223: int[] getSelectedLines() {
224:
225: Set<Integer> set = new TreeSet<Integer>();
226: for (Selection s : selection) {
227: int endLine = s.end == textArea
228: .getLineStartOffset(s.endLine) ? s.endLine - 1
229: : s.endLine;
230:
231: for (int j = s.startLine; j <= endLine; j++) {
232: set.add(j);
233: }
234: }
235:
236: int[] returnValue = new int[set.size()];
237: int i = 0;
238: for (Integer line : set)
239: returnValue[i++] = line;
240:
241: return returnValue;
242: } //}}}
243:
244: //{{{ invertSelection() method
245: void invertSelection() {
246: Selection[] newSelection = new Selection[selection.size() + 1];
247: int lastOffset = 0;
248: for (int i = 0; i < selection.size(); i++) {
249: Selection s = selection.get(i);
250: newSelection[i] = new Selection.Range(lastOffset, s
251: .getStart());
252: lastOffset = s.getEnd();
253: }
254: newSelection[selection.size()] = new Selection.Range(
255: lastOffset, textArea.getBufferLength());
256: setSelection(newSelection);
257: } //}}}
258:
259: //{{{ getSelectionStartEndOnLine() method
260: /**
261: * Returns the x co-ordinates of the selection start and end on the
262: * given line. May return null.
263: */
264: int[] getSelectionStartAndEnd(int screenLine, int physicalLine,
265: Selection s) {
266: int start = textArea.getScreenLineStartOffset(screenLine);
267: int end = textArea.getScreenLineEndOffset(screenLine);
268:
269: if (end <= s.start || start > s.end)
270: return null;
271:
272: int selStartScreenLine;
273: if (textArea.displayManager.isLineVisible(s.startLine))
274: selStartScreenLine = textArea
275: .getScreenLineOfOffset(s.start);
276: else
277: selStartScreenLine = -1;
278:
279: int selEndScreenLine;
280: if (textArea.displayManager.isLineVisible(s.endLine))
281: selEndScreenLine = textArea.getScreenLineOfOffset(s.end);
282: else
283: selEndScreenLine = -1;
284:
285: JEditBuffer buffer = textArea.getBuffer();
286:
287: int lineStart = buffer.getLineStartOffset(physicalLine);
288: int x1, x2;
289:
290: if (s instanceof Selection.Rect) {
291: start -= lineStart;
292: end -= lineStart;
293:
294: Selection.Rect rect = (Selection.Rect) s;
295: int _start = rect.getStartColumn(buffer);
296: int _end = rect.getEndColumn(buffer);
297:
298: int lineLen = buffer.getLineLength(physicalLine);
299:
300: int[] total = new int[1];
301:
302: int rectStart = buffer.getOffsetOfVirtualColumn(
303: physicalLine, _start, total);
304: if (rectStart == -1) {
305: x1 = (_start - total[0]) * textArea.charWidth;
306: rectStart = lineLen;
307: } else
308: x1 = 0;
309:
310: int rectEnd = buffer.getOffsetOfVirtualColumn(physicalLine,
311: _end, total);
312: if (rectEnd == -1) {
313: x2 = (_end - total[0]) * textArea.charWidth;
314: rectEnd = lineLen;
315: } else
316: x2 = 0;
317:
318: if (end <= rectStart || start > rectEnd)
319: return null;
320:
321: x1 = rectStart < start ? 0 : x1
322: + textArea.offsetToXY(physicalLine, rectStart).x;
323: x2 = rectEnd > end ? textArea.getWidth() : x2
324: + textArea.offsetToXY(physicalLine, rectEnd).x;
325: } else if (selStartScreenLine == selEndScreenLine
326: && selStartScreenLine != -1) {
327: x1 = textArea.offsetToXY(physicalLine, s.start - lineStart).x;
328: x2 = textArea.offsetToXY(physicalLine, s.end - lineStart).x;
329: } else if (screenLine == selStartScreenLine) {
330: x1 = textArea.offsetToXY(physicalLine, s.start - lineStart).x;
331: x2 = textArea.getWidth();
332: } else if (screenLine == selEndScreenLine) {
333: x1 = 0;
334: x2 = textArea.offsetToXY(physicalLine, s.end - lineStart).x;
335: } else {
336: x1 = 0;
337: x2 = textArea.getWidth();
338: }
339:
340: if (x1 < 0)
341: x1 = 0;
342: if (x2 < 0)
343: x2 = 0;
344:
345: if (x1 == x2)
346: x2++;
347:
348: return new int[] { x1, x2 };
349: } //}}}
350:
351: //{{{ insideSelection() method
352: /**
353: * Returns if the given point is inside a selection.
354: * Used by drag and drop code in MouseHandler below.
355: */
356: boolean insideSelection(int x, int y) {
357: int offset = textArea.xyToOffset(x, y);
358:
359: Selection s = textArea.getSelectionAtOffset(offset);
360: if (s == null)
361: return false;
362:
363: int screenLine = textArea.getScreenLineOfOffset(offset);
364: if (screenLine == -1)
365: return false;
366:
367: int[] selectionStartAndEnd = getSelectionStartAndEnd(
368: screenLine, textArea.getLineOfOffset(offset), s);
369: if (selectionStartAndEnd == null)
370: return false;
371:
372: return x >= selectionStartAndEnd[0]
373: && x <= selectionStartAndEnd[1];
374: } //}}}
375:
376: private TextArea textArea;
377: }
|