/* * SelectionManager.java * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2004 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.textarea; //{{{ Imports import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nonnull; import org.gjt.sp.jedit.buffer.*; //}}} class SelectionManager { // this is package-private so that the painter can use it without // having to call getSelection() (which involves an array copy) List<Selection> selection; //{{{ SelectionManager constructor SelectionManager(TextArea textArea) { this.textArea = textArea; selection = new ArrayList<Selection>(); } //}}} //{{{ getSelectionCount() method /** * Returns the number of selections. This can be used to test * for the existence of selections. */ int getSelectionCount() { return selection.size(); } //}}} //{{{ getSelection() method /** * Returns the current selection. * @since jEdit 3.2pre1 */ @Nonnull public Selection[] getSelection() { return selection.toArray( new Selection[selection.size()]); } //}}} //{{{ setSelection() method /** * Sets the selection. Nested and overlapping selections are merged * where possible. */ void setSelection(Selection[] selection) { this.selection.clear(); addToSelection(selection); } //}}} //{{{ addToSelection() method /** * Adds to the selection. Nested and overlapping selections are merged * where possible. Null elements of the array are ignored. * @param selection The new selection * since jEdit 3.2pre1 */ void addToSelection(Selection[] selection) { if(selection != null) { for (Selection s : selection) { if (s != null) addToSelection(s); } } } //}}} //{{{ addToSelection() method void addToSelection(Selection addMe) { if(addMe.start > addMe.end) { throw new IllegalArgumentException(addMe.start + " > " + addMe.end); } else if(addMe.start == addMe.end) { if(addMe instanceof Selection.Range) return; else if(addMe instanceof Selection.Rect) { if(((Selection.Rect)addMe).extraEndVirt == 0) return; } } Iterator<Selection> iter = selection.iterator(); while(iter.hasNext()) { // try and merge existing selections one by // one with the new selection Selection s = iter.next(); if(s.overlaps(addMe)) { addMe.start = Math.min(s.start,addMe.start); addMe.end = Math.max(s.end,addMe.end); iter.remove(); } } addMe.startLine = textArea.getLineOfOffset(addMe.start); addMe.endLine = textArea.getLineOfOffset(addMe.end); boolean added = false; for(int i = 0; i < selection.size(); i++) { Selection s = selection.get(i); if(addMe.start < s.start) { selection.add(i,addMe); added = true; break; } } if(!added) selection.add(addMe); textArea.invalidateLineRange(addMe.startLine,addMe.endLine); } //}}} //{{{ setSelection() method /** * Sets the selection. Nested and overlapping selections are merged * where possible. */ void setSelection(Selection selection) { this.selection.clear(); if(selection != null) addToSelection(selection); } //}}} //{{{ getSelectionAtOffset() method /** * Returns the selection containing the specific offset, or <code>null</code> * if there is no selection at that offset. * @param offset The offset * @since jEdit 3.2pre1 */ Selection getSelectionAtOffset(int offset) { if(selection != null) { for (Selection s : selection) { if(offset >= s.start && offset <= s.end) return s; } } return null; } //}}} //{{{ removeFromSelection() method /** * Deactivates the specified selection. * @param sel The selection */ void removeFromSelection(Selection sel) { selection.remove(sel); } //}}} //{{{ resizeSelection() method /** * Resizes the selection at the specified offset, or creates a new * one if there is no selection at the specified offset. This is a * utility method that is mainly useful in the mouse event handler * because it handles the case of end being before offset gracefully * (unlike the rest of the selection API). * @param offset The offset * @param end The new selection end * @param extraEndVirt Only for rectangular selections - specifies how * far it extends into virtual space. * @param rect Make the selection rectangular? */ void resizeSelection(int offset, int end, int extraEndVirt, boolean rect) { boolean reversed = false; if(end < offset) { int tmp = offset; offset = end; end = tmp; reversed = true; } Selection newSel; if(rect) { Selection.Rect rectSel = new Selection.Rect(offset,end); if(reversed) rectSel.extraStartVirt = extraEndVirt; else rectSel.extraEndVirt = extraEndVirt; newSel = rectSel; } else newSel = new Selection.Range(offset,end); addToSelection(newSel); } //}}} //{{{ getSelectedLines() method /** * Returns a sorted array of line numbers on which a selection or * selections are present.<p> * * This method is the most convenient way to iterate through selected * lines in a buffer. The line numbers in the array returned by this * method can be passed as a parameter to such methods as * {@link org.gjt.sp.jedit.Buffer#getLineText(int)}. */ int[] getSelectedLines() { Set<Integer> set = new TreeSet<Integer>(); for (Selection s : selection) { int endLine = s.end == textArea.getLineStartOffset(s.endLine) ? s.endLine - 1 : s.endLine; for(int j = s.startLine; j <= endLine; j++) { set.add(j); } } int[] returnValue = new int[set.size()]; int i = 0; for (Integer line : set) returnValue[i++] = line; return returnValue; } //}}} //{{{ invertSelection() method void invertSelection() { Selection[] newSelection = new Selection[selection.size() + 1]; int lastOffset = 0; for(int i = 0; i < selection.size(); i++) { Selection s = selection.get(i); newSelection[i] = new Selection.Range(lastOffset, s.getStart()); lastOffset = s.getEnd(); } newSelection[selection.size()] = new Selection.Range( lastOffset,textArea.getBufferLength()); setSelection(newSelection); } //}}} //{{{ getSelectionStartEndOnLine() method /** * Returns the x co-ordinates of the selection start and end on the * given line. May return null. */ int[] getSelectionStartAndEnd(int screenLine, int physicalLine, Selection s) { int start = textArea.getScreenLineStartOffset(screenLine); int end = textArea.getScreenLineEndOffset(screenLine); if(end <= s.start || start > s.end) return null; int selStartScreenLine; if(textArea.displayManager.isLineVisible(s.startLine)) selStartScreenLine = textArea.getScreenLineOfOffset(s.start); else selStartScreenLine = -1; int selEndScreenLine; if(textArea.displayManager.isLineVisible(s.endLine)) selEndScreenLine = textArea.getScreenLineOfOffset(s.end); else selEndScreenLine = -1; JEditBuffer buffer = textArea.getBuffer(); int lineStart = buffer.getLineStartOffset(physicalLine); int x1, x2; if(s instanceof Selection.Rect) { start -= lineStart; end -= lineStart; Selection.Rect rect = (Selection.Rect)s; int _start = rect.getStartColumn(buffer); int _end = rect.getEndColumn(buffer); int lineLen = buffer.getLineLength(physicalLine); int[] total = new int[1]; int rectStart = buffer.getOffsetOfVirtualColumn( physicalLine,_start,total); if(rectStart == -1) { x1 = (_start - total[0]) * textArea.charWidth; rectStart = lineLen; } else x1 = 0; int rectEnd = buffer.getOffsetOfVirtualColumn( physicalLine,_end,total); if(rectEnd == -1) { x2 = (_end - total[0]) * textArea.charWidth; rectEnd = lineLen; } else x2 = 0; if(end <= rectStart || start > rectEnd) return null; x1 = rectStart < start ? 0 : x1 + textArea.offsetToXY(physicalLine, rectStart).x; x2 = rectEnd > end ? textArea.getWidth() : x2 + textArea.offsetToXY(physicalLine, rectEnd).x; } else if(selStartScreenLine == selEndScreenLine && selStartScreenLine != -1) { x1 = textArea.offsetToXY(physicalLine, s.start - lineStart).x; x2 = textArea.offsetToXY(physicalLine, s.end - lineStart).x; } else if(screenLine == selStartScreenLine) { x1 = textArea.offsetToXY(physicalLine, s.start - lineStart).x; x2 = textArea.getWidth(); } else if(screenLine == selEndScreenLine) { x1 = 0; x2 = textArea.offsetToXY(physicalLine, s.end - lineStart).x; } else { x1 = 0; x2 = textArea.getWidth(); } if(x1 < 0) x1 = 0; if(x2 < 0) x2 = 0; if(x1 == x2) x2++; return new int[] { x1, x2 }; } //}}} //{{{ insideSelection() method /** * Returns if the given point is inside a selection. * Used by drag and drop code in MouseHandler below. */ boolean insideSelection(int x, int y) { int offset = textArea.xyToOffset(x,y); Selection s = textArea.getSelectionAtOffset(offset); if(s == null) return false; int screenLine = textArea.getScreenLineOfOffset(offset); if(screenLine == -1) return false; int[] selectionStartAndEnd = getSelectionStartAndEnd( screenLine,textArea.getLineOfOffset(offset),s); if(selectionStartAndEnd == null) return false; return x >= selectionStartAndEnd[0] && x <= selectionStartAndEnd[1]; } //}}} private final TextArea textArea; }