/*
* 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;
}