/*
* ColumnBlock.java
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2010 Anshal Shukla
*
* 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;
import java.util.Vector;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.jedit.textarea.Selection.Rect;
public class ColumnBlock extends Rect implements Node
{
private Node parent;
private Vector<Node> children = new Vector<Node>();
private Vector<ColumnBlockLine> lines = new Vector<ColumnBlockLine>();
float columnBlockWidth;
private boolean tabSizesDirty = true;
private JEditBuffer buffer;
private boolean isDirty;
@Override
//{{{ addChild() method
public void addChild(Node node)
{
// must add the children in sorted order
ColumnBlock block = (ColumnBlock) node;
ColumnBlock blockBelow = searchChildren(block.startLine);
if (blockBelow != null)
{
if (blockBelow.isLineWithinThisBlock(block.endLine) >= 0)
{
throw new IllegalArgumentException("Overlapping column blocks: "
+ block + " \n&\n" + blockBelow);
}
int index = children.indexOf(blockBelow);
children.add(index, node);
}
else
{
children.add(node);
}
}//}}}
@Override
//{{{ getChildren() method
public Vector<Node> getChildren()
{
return children;
}//}}}
@Override
//{{{ getParent() method
public Node getParent()
{
return parent;
}//}}}
//{{{ setWidth() method
public void setWidth(int width)
{
columnBlockWidth = width;
}//}}}
//{{{ setParent() method
public void setParent(Node parent)
{
this.parent = parent;
}//}}}
//{{{ setLines() method
public void setLines(Vector<ColumnBlockLine> lines)
{
this.lines = lines;
}//}}}
//{{{ getLines() method
public Vector<ColumnBlockLine> getLines()
{
return lines;
}//}}}
//{{{ ColumnBlock() method
public ColumnBlock()
{
}//}}}
//{{{ ColumnBlock() method
public ColumnBlock(JEditBuffer buffer, int startLine, int startColumn, int endLine,
int endColumn)
{
super(buffer, startLine, startColumn, endLine, endColumn);
this.buffer = buffer;
}//}}}
//{{{ ColumnBlock() method
public ColumnBlock(JEditBuffer buffer, int startLine, int endLine)
{
this.startLine = startLine;
this.endLine = endLine;
this.buffer = buffer;
}//}}}
//{{{ getStartLine() method
@Override
public int getStartLine()
{
return startLine;
}//}}}
//{{{ getEndLine() method
@Override
public int getEndLine()
{
return endLine;
}//}}}
//{{{ getColumnWidth() method
public int getColumnWidth()
{
return (int) columnBlockWidth;
}//}}}
//{{{ isLineWithinThisBlock() method
public int isLineWithinThisBlock(int line)
{
if (line < startLine)
{
return line - startLine;
}
else if (line > endLine)
{
return line - endLine;
}
else
{
return 0;
}
}//}}}
//{{{ getContainingBlock() method
public ColumnBlock getContainingBlock(int line, int offset)
{
ColumnBlock retBlock = null;
if (line >= startLine && line <= endLine)
{
int relativeOffset = offset - buffer.getLineStartOffset(line);
if (lines != null && !lines.isEmpty())
{
ColumnBlockLine blockLine = lines.get(line - startLine);
if (blockLine.getColumnEndIndex() >= relativeOffset
&& blockLine.getColumnStartIndex() <= relativeOffset)
{
retBlock = this;
}
}
if (retBlock == null && children != null && !children.isEmpty())
{
ColumnBlock block = searchChildren(line);
if (block != null && block.isLineWithinThisBlock(line) == 0)
{
retBlock = block.getContainingBlock(line, offset);
}
}
}
return retBlock;
}//}}}
//{{{ getContainingBlock() method
public ColumnBlock getColumnBlock(int line, int offset)
{
if (isDirty)
{
return null;
}
// int tabSize=-5;
synchronized (buffer.columnBlockLock)
{
ColumnBlock colBlock = null;
if (line >= startLine && line <= endLine)
{
if (lines != null && !lines.isEmpty())
{
ColumnBlockLine blockLine = lines.get(line - startLine);
if (blockLine.getColumnEndIndex() + buffer
.getLineStartOffset(line) == offset)
{
// tabSize =
// blockLine.getTabSize();
colBlock = this;
}
}
if (colBlock == null && children != null
&& !children.isEmpty())
{
ColumnBlock block = searchChildren(line, 0,
children.size() - 1);
if (block == null
|| block.isLineWithinThisBlock(line) != 0)
{
throwException(offset, line);
}
// tabSize =
// block.getColumnBlock(line,offset);
colBlock = block.getColumnBlock(line, offset);
}
}
// if(tabSize<0)
if (colBlock == null)
throwException(offset, line);
// return tabSize;
return colBlock;
}
}//}}}
//{{{ searchChildren() method
public ColumnBlock searchChildren(int line)
{
if (children != null && !children.isEmpty())
{
return searchChildren(line, 0, children.size() - 1);
}
else
{
return null;
}
}//}}}
//{{{ searchChildren() method
/*
* binary search on a sorted list searches the children for one
* containing the line no. line returns an exact match or the closest
* column block just below this line use isLineWithinThisBlock on the
* column block returned by this method to determine whether there was
* an exact match
*/
private ColumnBlock searchChildren(int line, int startIndex, int stopIndex)
{
if (children != null)
{
if (startIndex > stopIndex)
{
// no exact match found return the nearest
// column block just below this line
return (ColumnBlock) children.get(startIndex);
}
int currentSearchIndex = (startIndex + stopIndex) / 2;
int found = ((ColumnBlock) children.get(currentSearchIndex))
.isLineWithinThisBlock(line);
if (found == 0)
{
return (ColumnBlock) children.get(currentSearchIndex);
}
else if (found > 0)
{
if (children.size() - 1 > currentSearchIndex)
{
return searchChildren(line, currentSearchIndex + 1,
stopIndex);
}
else
{
return null;
}
}
else if (found < 0)
{
if (currentSearchIndex > 0)
{
return searchChildren(line, startIndex,
currentSearchIndex - 1);
}
else
{
// no exact match found return the
// nearest column block just below this
// line
return (ColumnBlock) children.get(0);
}
}
}
return null;
}//}}}
//{{{ toString() method
public String toString()
{
StringBuilder buf = new StringBuilder();
buf.append("ColumnBlock[startLine : ").append(startLine).append(" ,endLine : ").append(endLine)
.append(" ,columnBlockWidth : ").append(columnBlockWidth).append("] LINES:");
for (int i = 0; i < lines.size(); i++)
{
buf.append('\n');
buf.append("LINE ").append(i).append(':').append(lines.elementAt(i));
}
for (int i = 0; i < children.size(); i++)
{
buf.append('\n');
buf.append("CHILD ").append(i).append(':').append(children.elementAt(i));
}
return buf.toString();
}//}}}
//{{{ throwException() method
private void throwException(int offset, int line)
{
throw new IllegalArgumentException("{ELASTIC TABSTOP}CORRUPT DATA@{"
+ System.currentTimeMillis() + "} & Thread : "
+ Thread.currentThread().getName()
+ " :Cannot find the size for tab at offset "
+ (offset - buffer.getLineStartOffset(line)) + "in line " + line
+ "while searching in \n " + this);
}//}}}
//{{{ setDirtyStatus() method
public void setDirtyStatus(boolean status)
{
synchronized (buffer.columnBlockLock)
{
isDirty = status;
}
}//}}}
//{{{ updateLineNo() method
public void updateLineNo(int line)
{
// Things to do in this method
// update line no. in this column block
// update column block lines in this column block
// call this method on all children
startLine += line;
endLine += line;
for (int i = 0; i < lines.size(); i++)
{
lines.elementAt(i).updateLineNo(line);
}
for (int i = 0; i < children.size(); i++)
{
((ColumnBlock) children.elementAt(i)).updateLineNo(line);
}
}//}}}
//{{{ updateColumnBlockLineOffset() method
public void updateColumnBlockLineOffset(int line, int offsetAdd, boolean increaseStartOffset)
{
if (line >= startLine && line <= endLine)
{
if (lines != null && !lines.isEmpty())
{
ColumnBlockLine blockLine = lines.get(line - startLine);
if (increaseStartOffset)
{
blockLine.colStartIndex += offsetAdd;
}
blockLine.colEndIndex += offsetAdd;
}
if (children != null && !children.isEmpty())
{
ColumnBlock block = searchChildren(line);
if (block != null && block.isLineWithinThisBlock(line) == 0)
{
block.updateColumnBlockLineOffset(line, offsetAdd, true);
}
}
}
}//}}}
//{{{ setTabSizeDirtyStatus() method
/*
* tab sizes become dirty on font changes or when char is added or
* deleted inside ColumnBlock they become clean once they get calculated
* again inside the tab expander
*/
public void setTabSizeDirtyStatus(boolean dirty, boolean recursive)
{
tabSizesDirty = dirty;
if (recursive && children != null && !children.isEmpty())
{
for (int i = 0; i < children.size(); i++)
{
((ColumnBlock) children.elementAt(i)).setTabSizeDirtyStatus(true,
true);
}
}
}//}}}
//{{{ areTabSizesDirty() method
public boolean areTabSizesDirty()
{
return tabSizesDirty;
}//}}}
}