package com.yoursway.modelediting.swt; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import com.yoursway.modelediting.Fragment; import com.yoursway.modelediting.IModelListener; import com.yoursway.modelediting.Model; import com.yoursway.modelediting.ReplaceImpossibleException; public class ModelReconciler implements IModelListener { private class StyledTextListener implements Listener { boolean disabled = false; public void handleEvent(Event event) { if (disabled) return; if (event.type == SWT.Verify) { event.doit = false; try { if (canModify(event.start, event.end)) modify(event.start, event.end, event.text); } catch (ReplaceImpossibleException e) { throw new RuntimeException(e); } } } } private final IStyledText styledText; private final Model model; private final StyledTextListener styledTextListener; private int fragmentStarts[]; private int fragmentLengths[]; private boolean fragmentHasStart[], fragmentHasEnd[]; public ModelReconciler(IStyledText styledText, Model model) { if (styledText == null) throw new NullPointerException("styledText is null"); if (model == null) throw new NullPointerException("model is null"); this.styledText = styledText; this.model = model; List<Fragment> fragments = model.fragments(); indexFragments(fragments); StringBuilder text = new StringBuilder(); for (Fragment f : fragments) { text.append(f.toString()); } styledText.setText(text.toString()); model.addListener(this); styledTextListener = new StyledTextListener(); styledText.addListener(SWT.Verify, styledTextListener); styledText.addListener(SWT.Modify, styledTextListener); } private void indexFragments(List<Fragment> fragments) { fragmentStarts = new int[fragments.size()]; fragmentLengths = new int[fragments.size()]; fragmentHasStart = new boolean[fragments.size()]; fragmentHasEnd = new boolean[fragments.size()]; int i = 0, start = 0; for (Fragment f : fragments) { fragmentStarts[i] = start; fragmentLengths[i] = f.toString().length(); fragmentHasStart[i] = f.includesStart(); fragmentHasEnd[i] = f.includesEnd(); start += fragmentLengths[i]; i++; } } public int findLeftFragment(int offset, boolean isDeletion) { int position = 0; int index = 0; for (int i = 0; i < fragmentStarts.length; i++) { int intervalStart = position + (fragmentHasStart[i] || isDeletion ? 0 : 1); int fLength = fragmentLengths[i]; int intervalEnd = position + (fragmentHasEnd[i] ? 0 : -1) + fLength; // System.out.println("("+intervalStart+","+intervalEnd+")"); if (offset < intervalStart - 1) return -1; if (offset >= intervalStart && offset <= intervalEnd) return index; position += fLength; index++; } return -1; } public int findRightFragment(int offset, boolean isDeletion) { int position = 0; int index = 0; int rightmost = -1; for (int i = 0; i < fragmentStarts.length; i++) { int intervalStart = position + (fragmentHasStart[i] ? 0 : 1); int fLength = fragmentLengths[i]; int intervalEnd = position + (fragmentHasEnd[i] || isDeletion ? 0 : -1) + fLength; if (offset < intervalStart - 1) return rightmost; if (offset >= intervalStart && offset <= intervalEnd) rightmost = index; position += fLength; index++; } return rightmost; } /** * FIXME: speedup by caching fragment offsets */ public int getFragmentOffset(int index) { // int pos = 0; // int num = 0; // for (int i = 0; i < fragmentLengths.length; i++) { // if (num == index) // return pos; // pos += fragment.toString().length(); // num++; // } // return pos; return fragmentStarts[index]; } public boolean canModify(int start, int end) { boolean isDeletion = start < end; int startFragment = findLeftFragment(start, isDeletion); int endFragment = findRightFragment(end, isDeletion); if (startFragment == -1 || endFragment == -1 || startFragment > endFragment) return false; int startOffset = start - getFragmentOffset(startFragment); boolean hasModifiableArea = false; for (int i = startFragment; i <= endFragment; i++) { int endOffset; Fragment fragment = model.fragments().get(i); if (i == endFragment) { endOffset = end - getFragmentOffset(endFragment); } else { endOffset = fragmentLengths[i]; } startOffset = 0; if (fragment.canReplace(startOffset, endOffset - startOffset)) { hasModifiableArea = true; } } return hasModifiableArea; } public void modify(int start, int end, String text) throws ReplaceImpossibleException { if (text == null) throw new NullPointerException("text is null"); boolean isDeletion = (start < end); int startFragment = findLeftFragment(start, isDeletion); int endFragment = findRightFragment(end, isDeletion); if (startFragment == -1 || endFragment == -1 || startFragment > endFragment) throw new ReplaceImpossibleException("Given text ranges are not available"); int startOffset = start - getFragmentOffset(startFragment); for (int i = startFragment; i <= endFragment; i++) { int endOffset; Fragment fragment = model.fragments().get(i); if (i == endFragment) endOffset = end - getFragmentOffset(endFragment); else endOffset = fragmentLengths[i]; if (fragment.canReplace(startOffset, endOffset - startOffset)) { model.replace(this, fragment, startOffset, endOffset - startOffset, text); text = ""; } startOffset = 0; } if (!text.equals("")) { throw new ReplaceImpossibleException("Text ranges are not editable"); } } public void modelChanged(Object sender, Model model, int firstFragment, int oldCount, int newCount) { int l = 0; final int startOffset = fragmentStarts[firstFragment]; for (int i = firstFragment; i < firstFragment + oldCount; i++) l += fragmentLengths[i]; final int cutLength = l; final StringBuilder newText = new StringBuilder(); for (int i = firstFragment; i < firstFragment + newCount; i++) { newText.append(model.fragments().get(i)); } indexFragments(model.fragments()); final String text = styledText.getText(); final String text2 = text.substring(0, startOffset) + newText + text.substring(startOffset + cutLength); Display.getDefault().syncExec(new Runnable() { public void run() { styledTextListener.disabled = true; if (!text.equals(text2)) { // styledText.replaceTextRange(startOffset, cutLength, // newText.toString()); styledText.setText(text2); } styledTextListener.disabled = false; } }); } void dispose() { model.removeListener(this); styledText.removeListener(SWT.Verify, styledTextListener); styledText.removeListener(SWT.Modify, styledTextListener); } }