public class CscopeEntry {
	private static int CSCOPE_FILE = 1;
	private static int CSCOPE_ACTUALNAME = 2;
	private static int CSCOPE_NAME = 4;
	private static int CSCOPE_LINENUMBER = 3;
	
	public String actualName = "";
	public String name = "";
	public String file = "";
	public int lineNumber = -1;
	private int endLine = -2;
	
	private static Pattern CscopeEntryPattern = Pattern.compile("(\\S*)\\s(\\S*)\\s(\\d*)\\s(.*)");
	public CscopeEntry(String line) {
		Matcher matcher = CscopeEntryPattern.matcher(line);
		
		if(matcher.find()) {
			this.file = matcher.group(CSCOPE_FILE);
			this.name = matcher.group(CSCOPE_NAME);
			this.actualName = matcher.group(CSCOPE_ACTUALNAME);
			this.lineNumber = Integer.parseInt(matcher.group(CSCOPE_LINENUMBER));
		} else
			throw new IllegalArgumentException("Couldn't parse " + line);
	}
	
	public int getEndLine(CscopeWrapper cscopeWrapper) {
		if (endLine == -2)
			endLine = cscopeWrapper.getFunctionEndLine(this);
		return endLine;
	}
	
	public String getUrl(String projectPath) {
		if (file.length() > projectPath.length()) {
			String relativeFilename = file.substring(projectPath.length() + 1);
			return relativeFilename + ":" + name;
		} else
			return file + ":" + name;
	}
	
	public String getActualUrl(String projectPath) {
		if (file.length() > projectPath.length()) {
			String relativeFilename = file.substring(projectPath.length() + 1);
			return relativeFilename + ":" + actualName;
		} else
			return file + ":" + actualName;
	}
	
	public String toString() {
		return file + ":" + name + "@" + lineNumber;
	}
} public class ProjectController {
	
	protected Context context;
	
	public Project project;
	private Cscope cscope;
	private CscopeWrapper cscopeWrapper;

	
	public ProjectController(String name, Context context) {
		this.context = context;
		this.project = loadProject(name);
		this.cscope = new Cscope(context);
		this.cscopeWrapper = new CscopeWrapper(cscope, project, context);
	}

	// TODO Integrate checks for project name
	public boolean isNameValid(String projectName) {
		if (TextUtils.isEmpty(projectName))
			return false;
		else
			return true;
	}
	
	public Project loadProject(String name) {
		if(TextUtils.isEmpty(name))
			throw new IllegalArgumentException("Invalid project name");
		
		try {
			return Project.readProject(name, context);
		} catch (IOException e) {
			Log.e("CodeMap", e.getLocalizedMessage());
			return new Project(name);
		}		
	}
	
	public static ArrayList<String> getProjectsList(Context context) {
		ArrayList<String> result = new ArrayList<String>();
				
		String[] fileList = context.fileList();
		
		for(String filename: fileList) {
			if(filename.endsWith(".project")) {
				String projectName = filename.substring(0, filename.length()
						- ".project".length());
				result.add(projectName);
			}
		}

		return result;
	}	
	
	public String[] getProjectFiles() {
		if(project.files != null)
			return project.files;
		
		try {
			this.project.files = cscope.getFiles(project);
			return project.files;
		} catch (FileNotFoundException e) {
			Log.e("CodeMap", e.getStackTrace().toString());
			return new String[0];
		}
	}
	
	public void updateProject() {
		if(project.isUrlValid()) {
			try {
				JGitWrapper jgit = new JGitWrapper(project, context);
				jgit.update(this);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (IllegalStateException e) {
				Toast.makeText(context, "Error: " + e.getLocalizedMessage(),
						Toast.LENGTH_SHORT).show();
			}
		}
		else {
			buildIndex();
			Toast.makeText(context, "Updated " + project.getName(),
					Toast.LENGTH_SHORT).show();
		}
	}
	
	public void buildIndex() {
		cscope.generateNamefile(project);
		cscope.generateReffile(project);
	}

	public ArrayList<String> getDeclarations(String filename) {
		ArrayList<String> symbolList = project.symbols.get(filename);
		if(symbolList != null)
			return symbolList;
		else {
			ArrayList<String> declarations = cscopeWrapper.getDeclarations(filename);
			project.symbols.put(filename, declarations);
			return declarations;
		}
	}
	
	public static String getFileFromUrl(String url) {
		int colonIndex = url.indexOf(":");
		
		if (colonIndex < 0)
			return "";
		else
			return url.substring(0, colonIndex);
	}
	
	public static String getFunctionFromUrl(String url) {
		int colonIndex = url.indexOf(":");
		
		if (colonIndex < 0)
			return url;
		
		return url.substring(colonIndex + 1);
	}
	
	
	
	public ArrayList<CscopeEntry> getUrlEntries(String url)
			throws IllegalArgumentException {
		final String fileName = getFileFromUrl(url);
		final String functionName = getFunctionFromUrl(url);

		ArrayList<CscopeEntry> allEntries = cscopeWrapper.getAllEntries(
				functionName, fileName);
		
		return allEntries;
	}
	
	
	/**
	 * Call in case of full url.
	 */
	// TODO Refactor, only createCodeMapItem() calls this
	public SpannableString getFunctionSource(String url) {
		final String fileName = getFileFromUrl(url);
		final String functionName = getFunctionFromUrl(url);

		try {
			ArrayList<CscopeEntry> allEntries = cscopeWrapper.getAllEntries(
					functionName, fileName);
			return getFunctionSource(allEntries.get(0));
		} catch (IllegalArgumentException e) {
			Log.e("CodeMap", e.getLocalizedMessage());
			return new SpannableString("");
		}
	}
	
	public SpannableString getFunctionSource(CscopeEntry entry) {
		String content = cscopeWrapper.getFunction(entry).trim();
		SyntaxHighlighter highlighter = new SyntaxHighlighter(content);
		highlighter.markupReferences(cscopeWrapper.getReferences(entry));

		return highlighter.formatToHtml();
	}
	
	
	public SpannableString getFileSource(String fileName) {
		try {
			String content = cscopeWrapper.getFile(fileName).trim();
			SyntaxHighlighter highlighter = new SyntaxHighlighter(content);
			highlighter.markupReferences(cscopeWrapper.getFileReferences( fileName));

			return highlighter.formatToHtml();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			return new SpannableString("");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return new SpannableString("");
		}
	}
} public class CodeMapCursorPoint extends PointF {
	
	public CodeMapCursorPoint() {
		super();
	}
	
	public CodeMapCursorPoint(float x, float y) {
		super(x, y);
	}
	
	public CodeMapPoint getCodeMapPoint(WorkspaceView codeMapView) {
		CodeMapPoint transformedPoint = new CodeMapPoint();
		transformedPoint.x = (x + codeMapView.getScrollX()) / codeMapView.getScaleFactor();
		transformedPoint.y = (y + codeMapView.getScrollY()) / codeMapView.getScaleFactor();
		return transformedPoint;
	}
} public class CodeMapPoint extends PointF implements Serializable {
	private static final long serialVersionUID = 1L;
	
	public CodeMapPoint() {
		super();
	}
	
	public CodeMapPoint(float x, float y) {
		super(x, y);
	}

	public CodeMapPoint(PointF point) {
		super(point.x, point.y);
	}
	
    private void writeObject(final java.io.ObjectOutputStream out)
            throws IOException {
        out.writeFloat(x);
        out.writeFloat(y);
    }

    private void readObject(final java.io.ObjectInputStream in)
            throws IOException {
        x = in.readFloat();
        y = in.readFloat();
    }
} public interface FindDeclarationCallback {
	public void onSuccess(ArrayList<CscopeEntry> entries);
	public void onFailure();
} public class CodeMapAnnotation extends CodeMapItem {

	private EditText editText;
	
	public CodeMapAnnotation(Context context, AttributeSet attrs) {
		this(context, null, "");
	}

	public CodeMapAnnotation(Context context, CodeMapPoint point, String contents) {
		super(context, null, "Annotation");
		
		this.editText = new EditText(getContext());
		this.editText.setMaxWidth(300);
		setContentView(editText);
		
		init(contents);
		setPosition(point);
		setupForAnnotation();
	}
	
	public void init(String contents) {
		if (contents.isEmpty())
			editText.setHint("Click to add note");
		else
			editText.setText(contents);
	}
	
	public String getContents() {
		return editText.getText().toString();
	}
} public class CodeMapFunction extends CodeMapItem {

	private TextView sourceView;
	
	private float yOffset = 0;

	private View scrollView;
	
	public CodeMapFunction(Context context, AttributeSet attrs) {
		this(context, new CodeMapPoint(0, 0), "", new SpannableString(""));
	}
	
	public CodeMapFunction(Context context, CodeMapPoint point, String name,
			SpannableString content) {
		super(context, null, name);
				
		scrollView = inflate(context, R.layout.codemap_function, null);
		sourceView = (TextView) scrollView.findViewById(R.id.codemap_function);
		sourceView.setTextSize(Utils.getSourceFontsize(getContext()));
		setContentView(scrollView);
		
		init(name, content);
		setPosition(point);
	}
	
	public void init(String name, SpannableString content) {
		sourceView.setText(content);
		
		Spannable span = SpanUtils.replaceAll(content,
				URLSpan.class, new FunctionLinkSpanConverter(this), sourceView);
		
		sourceView.setText(span);
		sourceView.setLinksClickable(true);
		sourceView.setMovementMethod(LinkMovementMethod.getInstance());
		
		sourceView.setOnTouchListener(new OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				yOffset = event.getY();
				return false;
			}
		});
		
		this.titleView.setText(name);
		makeItemMoveable();
	}

	public void addChildFragment(String url) {
		if (this.codeMapView != null) {
			this.codeMapView.getController().addChildFragmentFromUrl(url,
					this, yOffset);
			this.yOffset = 0;
		}
	}
	
	@Override
	public void setFontSize(int fontSize) {
		this.sourceView.setTextSize(fontSize);
	}
} public abstract class CodeMapItem extends LinearLayout implements ICodeMapItem {
	public UUID id = UUID.randomUUID();

	public TextView titleView;
	private ImageButton removeButton;
	private LinearLayout containerView;
	
	private View contentView;
	protected WorkspaceView codeMapView;
	
	private boolean moveItem = false;
	
	public CodeMapItem(Context context, AttributeSet attrs, String name) {
		super(context, attrs);
		
		inflate(context, R.layout.codemap_item, this);
				
		containerView = (LinearLayout) findViewById(R.id.codemap_item_container);
		
		titleView = (TextView) findViewById(R.id.title);
		titleView.setText(name);
		
		removeButton = (ImageButton) findViewById(R.id.remove);
		removeButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {				
				remove();
			}
		});

		this.post(Utils.getTouchDelegateAction(this, removeButton, 50, 50, 50, 50));
	}

	
	public void setCodeMapView(WorkspaceView codeMapView) {
		this.codeMapView = codeMapView;
	}
	

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
				
		if (this.codeMapView != null && moveItem) {
			this.codeMapView.moveFragment(this);
			this.moveItem = false;
		}
	}

	protected void setContentView(View view) {
		this.contentView = view;
		containerView.addView(view);
	}
	

	public void setPosition(CodeMapPoint point) {
		setX(point.x);
		setY(point.y);
	}
	
	public CodeMapPoint getPosition() {
		return new CodeMapPoint(getX(), getY());
	}

	public void setPositionCenter(CodeMapPoint point) {
		float startX = point.x - (getWidth() / 2);
		float startY = point.y - (getHeight() / 2);
		setX(startX);
		setY(startY);
	}
	
	public Rect getBounds() {
		final int top = (int) getY();
		final int bottom = top + getHeight();

		final int left = (int) getX();
		final int right = left + getWidth();
				
		return new Rect(left, top, right, bottom);
	}
	
	public void push(Point offset) {
		setX(getX() + offset.x);
		setY(getY() + offset.y);
	}

	public boolean contains(CodeMapPoint point) {
//		Log.d("CodeMap", "point : [" + getX() + " < " + point.x + " < "
//				+ (getX() + getWidth()) + "] [" + getY() + " < " + point.y
//				+ " < " + (getY() + getHeight()) + "]");
		if (point.x >= getX() && point.x <= getX() + getWidth()
				&& point.y >= getY() && point.y <= getY() + getHeight()) {
			//Log.d("CodeMap", "match!");
			return true;
		}
		else
			return false;
	}

	public void makeItemMoveable() {
		this.moveItem = true;
	}
	
	public void remove() {
		if(codeMapView != null)
			codeMapView.remove(this);
	}
	
	public String getUrl() {
		return this.titleView.getText().toString();
	}
	
	public float getContentViewYOffset() {
		return titleView.getHeight() + titleView.getPaddingTop()
				+ titleView.getPaddingBottom() + contentView.getPaddingTop()
				+ contentView.getPaddingBottom() + 5;
	}
	
	public float getTitleViewYMid() {
		return this.getY() + (titleView.getHeight() / 2);
	}
	
	public void setFontSize(int fontSize) {
		// Do nothing
	}
	
	public void setupForAnnotation() {
		containerView.setBackgroundResource(R.drawable.fragment_bg_annotation);
	}
} public class CodeMapLink {

	public CodeMapItem parent;
	public CodeMapItem child;
	
	public float yOffset = 0;

	public CodeMapLink(CodeMapItem parent, CodeMapItem child, float yOffset) {
		this.parent = parent;
		this.child = child;
		this.yOffset = yOffset;
	}
	
	public boolean hasItem(CodeMapItem item) {
		return parent == item || child == item;
	}
	
	public void doDraw(Canvas canvas) {
		Paint paint = new Paint();
		
		float startX = parent.getX() + parent.getWidth();
		float startY = parent.getY() + yOffset;
		
		float endX = child.getX();
		float endY = child.getTitleViewYMid();

		float midX = (startX + endX) / 2;
		
		canvas.drawLine(startX, startY, midX, startY, paint);
		canvas.drawLine(midX, startY, midX, endY, paint);
		canvas.drawLine(midX, endY, endX, endY, paint);
	}
	
	public String toString() {
		return parent.getUrl() + "->" + child.getUrl();
	}
} public class OutlineItem {
	public enum TYPE {DIRECTORY, FILE, SYMBOL};
	
	public String name = "";
	public int level = 0;
	public TYPE type = TYPE.FILE;
	
	public int declarationCycle = 0;
	
	public OutlineItem(String name, int level, TYPE type) {
		this.name = name;
		this.level = level;
		this.type = type;
	}
	
	public ArrayList<OutlineItem> getChildren(ProjectController controller, Context context) {
		ArrayList<OutlineItem> items = new ArrayList<OutlineItem>();

		if (this.type == TYPE.SYMBOL)
			return items;
		
		File file = new File(controller.project.getSourcePath(context)
				+ File.separator + this.name);
		
		if(file.isDirectory()) {
			for (String filename: file.list())
				items.add(new OutlineItem(this.name + File.separator + filename, this.level + 1, TYPE.FILE));
		} else {
			ArrayList<String> declarations = controller.getDeclarations(this.name);
			
			for(String declaration: declarations)
				items.add(new OutlineItem(declaration, this.level + 1, TYPE.SYMBOL));
		}
		
		return items;
	}
}
import java.io.IOException;
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.text.SpannableString;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.PopupMenu;
import android.widget.Toast;
import com.hdweiss.codemap.data.CscopeEntry;
import com.hdweiss.codemap.data.ProjectController;
import com.hdweiss.codemap.util.CodeMapCursorPoint;
import com.hdweiss.codemap.util.CodeMapPoint;
import com.hdweiss.codemap.view.workspace.FindDeclarationTask.FindDeclarationCallback;
import com.hdweiss.codemap.view.workspace.fragments.CodeMapAnnotation;
import com.hdweiss.codemap.view.workspace.fragments.CodeMapFunction;
import com.hdweiss.codemap.view.workspace.fragments.CodeMapItem;
import com.hdweiss.codemap.view.workspace.fragments.CodeMapLink;
import com.hdweiss.codemap.view.workspace.outline.OutlineItem;
package com.hdweiss.codemap.view.workspace;




public class WorkspaceController extends ProjectController {

	private String workspaceName;
	public WorkspaceView codeMapView;

	public WorkspaceController(String projectName, String workspaceName, Context context) {
		super(projectName, context);
		this.workspaceName = workspaceName;
	}
	
	public void setView(WorkspaceView codeMapView) {
		this.codeMapView = codeMapView;
		this.codeMapView.setController(this);
		loadCodeMapState();
	}
	
	public String getWorkspaceName() {
		return this.workspaceName;
	}
	 
	
    public void loadCodeMapState() {
    	try {
			WorkspaceState state = WorkspaceState.readState(project, workspaceName, context);
			setState(state);
		} catch (IOException e) {
			e.printStackTrace();
			addAnnotationView("Welcome to CodeMap!\nYou can start browsing your project by using the explorer on the right.");
		}
    }
	
    @SuppressWarnings("unchecked")
	private void setState(WorkspaceState state) {
		if(state == null)
			return;
		
		WorkspaceStateLoader loadState = new WorkspaceStateLoader(state, codeMapView, this);
		loadState.execute(state.items);
		
		codeMapView.setScrollX(state.scrollX);
		codeMapView.setScrollY(state.scrollY);
		codeMapView.setScaleFactor(state.zoom, new CodeMapPoint());
	}
    
    
    public void saveCodeMapState() {
    	try {
			codeMapView.getState().writeState(project, context);
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    
	public void addAnnotationView(String content) {
		if(codeMapView != null) {
			CodeMapPoint position = new CodeMapCursorPoint(100, 100).getCodeMapPoint(codeMapView);