On Wed, 2006-02-08 at 12:16 +0200, Martin Vermeer wrote:
> On Wed, 2006-02-08 at 11:39 +0200, Martin Vermeer wrote:
> > Anybody else notice that its sshd seems dead? As a result I cannot use
> > CVS.
> 
> The reason I'm asking is, I've got multi-paragraph change tracking
> working (for 1.4.1 as promised), but cannot make a patch ;-)
> 
> - Martin

Here's the patch.

Basically it introduces one more position in a paragraph, after the last
character (i.e., pos = par.size() ) to represent the change attached to
the paragraph break ("carriage return", which we don't really have in
our model).

Then the methods acceptChange and rejectChange in LyXText are modified
to handle multi-paragraph changes, deleting whole paragraphs and merging
two paragraphs when called for (i.e. "accept red" and "reject blue"
across paragraph breaks).

The patch necessitated some changes in surprising places:

1) I needed a "backspace at pos = 0" routine that ignores change
tracking, in order to use from within the above methods. So I split off
the new method backspacePos0 from backspace, doing just that. This
change looks much bigger than it is, but it is just moving existing code
around.

2) I added a "[C]" display in the status bar when change tracking is on.
Actually I believe we can now allow CT to be switched on and off by the
user without demanding a merge, which caused trouble under the old
architecture. That's more like the way OOo (and MS Word?) works, and
saner IMHO.

Give this one a try. I was thinking 1.4.1 time frame.

- Martin


BTW First SVN Patch!

Index: paragraph_pimpl.C
===================================================================
--- paragraph_pimpl.C	(revision 13205)
+++ paragraph_pimpl.C	(working copy)
@@ -99,7 +99,7 @@
 	lyxerr[Debug::CHANGES] << "track changes for par "
 		<< id_ << " type " << type << endl;
 	changes_.reset(new Changes(type));
-	changes_->set(type, 0, size());
+	changes_->set(type, 0, size() + 1);
 }
 
 
Index: lyxtext.h
===================================================================
--- lyxtext.h	(revision 13205)
+++ lyxtext.h	(working copy)
@@ -223,6 +223,8 @@
 	///
 	bool Delete(LCursor & cur);
 	///
+	bool backspacePos0(LCursor & cur);
+	///
 	bool backspace(LCursor & cur);
 	///
 	bool selectWordWhenUnderCursor(LCursor & cur, lyx::word_location);
Index: paragraph_funcs.C
===================================================================
--- paragraph_funcs.C	(revision 13205)
+++ paragraph_funcs.C	(working copy)
@@ -233,12 +233,15 @@
 	pos_type pos_end = next.size() - 1;
 	pos_type pos_insert = par.size();
 
+	Change::Type cr = next.lookupChange(next.size());
 	// ok, now copy the paragraph
 	for (pos_type i = 0, j = 0; i <= pos_end; ++i) {
 		Change::Type change = next.lookupChange(i);
 		if (moveItem(next, par, bparams, i, pos_insert + j, change))
 			++j;
 	}
+	// Move the change status of "carriage return" over
+	par.setChange(par.size(), cr);

	pars.erase(pars.begin() + par_offset + 1);
 }
 
Index: lyxfind.C
===================================================================
--- lyxfind.C	(revision 13205)
+++ lyxfind.C	(working copy)
@@ -127,7 +127,7 @@
 
 bool findChange(DocIterator & cur)
 {
-	for (; cur; cur.forwardChar())
+	for (; cur; cur.forwardPos())
 		if (cur.inTexted() && cur.pos() != cur.paragraph().size() &&
 		    cur.paragraph().lookupChange(cur.pos())
 		    != Change::UNCHANGED)
@@ -344,25 +344,21 @@
 	if (!findChange(cur))
 		return false;
 
-	Paragraph const & par = cur.paragraph();
-	const pos_type pos = cur.pos();
+	bv->cursor().setCursor(cur);
+	bv->cursor().resetAnchor();
+	
+	Change orig_change = cur.paragraph().lookupChangeFull(cur.pos());
 
-	Change orig_change = par.lookupChangeFull(pos);
-	const pos_type parsize = par.size();
-	pos_type end = pos;
-
-	for (; end != parsize; ++end) {
-		Change change = par.lookupChangeFull(end);
+	DocIterator et = doc_iterator_end(cur.inset());
+	for (; cur != et; cur.forwardPosNoDescend()) {
+		Change change = cur.paragraph().lookupChangeFull(cur.pos());
 		if (change != orig_change) {
-			// slight UI optimisation: for replacements, we get
-			// text like : _old_new. Consider that as one change.
-			if (!(orig_change.type == Change::DELETED &&
-				change.type == Change::INSERTED))
-				break;
+			break;
 		}
 	}
-	pos_type length = end - pos;
-	bv->putSelectionAt(cur, length, false);
+	// Now put cursor to end of selection:
+	bv->cursor().setCursor(cur);
+	bv->cursor().setSelection();
 	// if we used a lfun like in find/replace, dispatch would do
 	// that for us
 	bv->update();
Index: paragraph.C
===================================================================
--- paragraph.C	(revision 13205)
+++ paragraph.C	(working copy)
@@ -1639,14 +1639,14 @@
 
 Change::Type Paragraph::lookupChange(lyx::pos_type pos) const
 {
-	BOOST_ASSERT(empty() || pos < size());
+	BOOST_ASSERT(pos <= size());
 	return pimpl_->lookupChange(pos);
 }
 
 
 Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const
 {
-	BOOST_ASSERT(empty() || pos < size());
+	BOOST_ASSERT(pos <= size());
 	return pimpl_->lookupChangeFull(pos);
 }
 
Index: text.C
===================================================================
--- text.C	(revision 13205)
+++ text.C	(working copy)
@@ -1026,14 +1026,10 @@
 void LyXText::breakParagraph(LCursor & cur, bool keep_layout)
 {
 	BOOST_ASSERT(this == cur.text());
-	// allow only if at start or end, or all previous is new text
+
 	Paragraph & cpar = cur.paragraph();
 	pit_type cpit = cur.pit();
 
-	if (cur.pos() != 0 && cur.pos() != cur.lastpos()
-	    && cpar.isChangeEdited(0, cur.pos()))
-		return;
-
 	LyXTextClass const & tclass = cur.buffer().params().getLyXTextClass();
 	LyXLayout_ptr const & layout = cpar.layout();
 
@@ -1088,6 +1084,12 @@
 
 	updateCounters(cur.buffer());
 
+	// Mark "carriage return" as inserted if change tracking:
+	if (cur.buffer().params().tracking_changes) {
+		cur.paragraph().setChange(cur.paragraph().size(), 
+			Change::INSERTED);
+	}
+
 	// This check is necessary. Otherwise the new empty paragraph will
 	// be deleted automatically. And it is more friendly for the user!
 	if (cur.pos() != 0 || isempty)
@@ -1394,18 +1396,34 @@
 	if (!cur.selection() && cur.lastpos() != 0)
 		return;
 
-	CursorSlice const & startc = cur.selBegin();
-	CursorSlice const & endc = cur.selEnd();
-	if (startc.pit() == endc.pit()) {
-		recordUndoSelection(cur, Undo::INSERT);
-		pars_[startc.pit()].acceptChange(startc.pos(), endc.pos());
-		finishUndo();
-		cur.clearSelection();
-		setCursorIntern(cur, startc.pit(), 0);
+	recordUndoSelection(cur, Undo::INSERT);
+	
+	DocIterator it = cur.selectionBegin();
+	DocIterator et = cur.selectionEnd();
+	pit_type pit = it.pit();
+	Change::Type const type = pars_[pit].lookupChange(it.pos());
+	for (; pit <= et.pit(); ++pit) {
+		pos_type left  = ( pit == it.pit() ? it.pos() : 0 );
+		pos_type right = 
+		    ( pit == et.pit() ? et.pos() : pars_[pit].size() );
+		pars_[pit].acceptChange(left, right);
 	}
-#ifdef WITH_WARNINGS
-#warning handle multi par selection
-#endif
+	if (type == Change::DELETED) {
+		ParagraphList & plist = paragraphs();
+		if (it.pit() + 1 < et.pit())
+			pars_.erase(plist.begin() + it.pit() + 1,
+				    plist.begin() + et.pit());
+		
+		// Paragraph merge if appropriate:
+		if (pars_[it.pit()].lookupChange(pars_[it.pit()].size()) 
+			== Change::DELETED) {
+			setCursorIntern(cur, it.pit() + 1, 0);
+			backspacePos0(cur);
+		}
+	}
+	finishUndo();
+	cur.clearSelection();
+	setCursorIntern(cur, it.pit(), 0);
 }
 
 
@@ -1415,18 +1433,33 @@
 	if (!cur.selection() && cur.lastpos() != 0)
 		return;
 
-	CursorSlice const & startc = cur.selBegin();
-	CursorSlice const & endc = cur.selEnd();
-	if (startc.pit() == endc.pit()) {
-		recordUndoSelection(cur, Undo::INSERT);
-		pars_[startc.pit()].rejectChange(startc.pos(), endc.pos());
-		finishUndo();
-		cur.clearSelection();
-		setCursorIntern(cur, startc.pit(), 0);
+	recordUndoSelection(cur, Undo::INSERT);
+
+	DocIterator it = cur.selectionBegin();
+	DocIterator et = cur.selectionEnd();
+	pit_type pit = it.pit();
+	Change::Type const type = pars_[pit].lookupChange(it.pos());
+	for (; pit <= et.pit(); ++pit) {
+		pos_type left  = ( pit == it.pit() ? it.pos() : 0 );
+		pos_type right = 
+		    ( pit == et.pit() ? et.pos() : pars_[pit].size() );
+		pars_[pit].rejectChange(left, right);
 	}
-#ifdef WITH_WARNINGS
-#warning handle multi par selection
-#endif
+	if (type == Change::INSERTED) {
+		ParagraphList & plist = paragraphs();
+		if (it.pit() + 1 < et.pit())
+			pars_.erase(plist.begin() + it.pit() + 1,
+				    plist.begin() + et.pit());
+		// Paragraph merge if appropriate:
+		if (pars_[it.pit()].lookupChange(pars_[it.pit()].size()) 
+			== Change::INSERTED) {
+			setCursorIntern(cur, it.pit() + 1, 0);
+			backspacePos0(cur);
+		}
+	}
+	finishUndo();
+	cur.clearSelection();
+	setCursorIntern(cur, it.pit(), 0);
 }
 
 
@@ -1558,86 +1591,100 @@
 }
 
 
-bool LyXText::backspace(LCursor & cur)
+bool LyXText::backspacePos0(LCursor & cur)
 {
 	BOOST_ASSERT(this == cur.text());
 	bool needsUpdate = false;
-	if (cur.pos() == 0) {
-		// The cursor is at the beginning of a paragraph, so
-		// the the backspace will collapse two paragraphs into
-		// one.
 
-		// but it's not allowed unless it's new
-		Paragraph & par = cur.paragraph();
-		if (par.isChangeEdited(0, par.size()))
-			return false;
+	Paragraph & par = cur.paragraph();
+	// is it an empty paragraph?
+	pos_type lastpos = cur.lastpos();
+	if (lastpos == 0 || (lastpos == 1 && par.isSeparator(0))) {
+		// This is an empty paragraph and we delete it just
+		// by moving the cursor one step
+		// left and let the DeleteEmptyParagraphMechanism
+		// handle the actual deletion of the paragraph.
 
-		// we may paste some paragraphs
-
-		// is it an empty paragraph?
-		pos_type lastpos = cur.lastpos();
-		if (lastpos == 0 || (lastpos == 1 && par.isSeparator(0))) {
-			// This is an empty paragraph and we delete it just
-			// by moving the cursor one step
-			// left and let the DeleteEmptyParagraphMechanism
-			// handle the actual deletion of the paragraph.
-
-			if (cur.pit() != 0) {
-                                // For KeepEmpty layouts we need to get
-                                // rid of the keepEmpty setting first.
-                                // And the only way to do this is to
-                                // reset the layout to something
-                                // else: f.ex. the default layout.
-                                if (par.allowEmpty()) {
-                                        Buffer & buf = cur.buffer();
-                                        BufferParams const & bparams = buf.params();
-                                        par.layout(bparams.getLyXTextClass().defaultLayout());
-                                }
+		if (cur.pit() != 0) {
+			// For KeepEmpty layouts we need to get
+			// rid of the keepEmpty setting first.
+			// And the only way to do this is to
+			// reset the layout to something
+			// else: f.ex. the default layout.
+			if (par.allowEmpty()) {
+				Buffer & buf = cur.buffer();
+				BufferParams const & bparams = buf.params();
+				par.layout(bparams.getLyXTextClass().defaultLayout());
+			}
                                 
-				cursorLeft(cur);
-				return true;
-			}
+			cursorLeft(cur);
+			return true;
 		}
+	}
 
-		if (cur.pit() != 0)
-			recordUndo(cur, Undo::DELETE, cur.pit() - 1);
+	if (cur.pit() != 0)
+		recordUndo(cur, Undo::DELETE, cur.pit() - 1);
 
-		pit_type tmppit = cur.pit();
-		// We used to do cursorLeftIntern() here, but it is
-		// not a good idea since it triggers the auto-delete
-		// mechanism. So we do a cursorLeftIntern()-lite,
-		// without the dreaded mechanism. (JMarc)
-		if (cur.pit() != 0) {
-			// steps into the above paragraph.
-			setCursorIntern(cur, cur.pit() - 1,
-					pars_[cur.pit() - 1].size(),
-					false);
-		}
+	pit_type tmppit = cur.pit();
+	// We used to do cursorLeftIntern() here, but it is
+	// not a good idea since it triggers the auto-delete
+	// mechanism. So we do a cursorLeftIntern()-lite,
+	// without the dreaded mechanism. (JMarc)
+	if (cur.pit() != 0) {
+		// steps into the above paragraph.
+		setCursorIntern(cur, cur.pit() - 1,
+				pars_[cur.pit() - 1].size(),
+				false);
+	}
 
-		// Pasting is not allowed, if the paragraphs have different
-		// layout. I think it is a real bug of all other
-		// word processors to allow it. It confuses the user.
-		// Correction: Pasting is always allowed with standard-layout
-		// Correction (Jug 20050717): Remove check about alignment!
-		Buffer & buf = cur.buffer();
-		BufferParams const & bufparams = buf.params();
-		LyXTextClass const & tclass = bufparams.getLyXTextClass();
-		pit_type const cpit = cur.pit();
+	// Pasting is not allowed, if the paragraphs have different
+	// layout. I think it is a real bug of all other
+	// word processors to allow it. It confuses the user.
+	// Correction: Pasting is always allowed with standard-layout
+	// Correction (Jug 20050717): Remove check about alignment!
+	Buffer & buf = cur.buffer();
+	BufferParams const & bufparams = buf.params();
+	LyXTextClass const & tclass = bufparams.getLyXTextClass();
+	pit_type const cpit = cur.pit();
 
-		if (cpit != tmppit
-		    && (pars_[cpit].layout() == pars_[tmppit].layout()
-		        || pars_[tmppit].layout() == tclass.defaultLayout()))
-		{
-			mergeParagraph(bufparams, pars_, cpit);
-			needsUpdate = true;
+	if (cpit != tmppit
+	    && (pars_[cpit].layout() == pars_[tmppit].layout()
+	        || pars_[tmppit].layout() == tclass.defaultLayout()))
+	{
+		mergeParagraph(bufparams, pars_, cpit);
+		needsUpdate = true;
 
-			if (cur.pos() != 0 && pars_[cpit].isSeparator(cur.pos() - 1))
+		if (cur.pos() != 0 && pars_[cpit].isSeparator(cur.pos() - 1))
 				--cur.pos();
 
-			// the counters may have changed
-			updateCounters(cur.buffer());
-			setCursor(cur, cur.pit(), cur.pos(), false);
+		// the counters may have changed
+		updateCounters(cur.buffer());
+		setCursor(cur, cur.pit(), cur.pos(), false);
+	}
+	return needsUpdate;
+}
+
+
+bool LyXText::backspace(LCursor & cur)
+{
+	BOOST_ASSERT(this == cur.text());
+	bool needsUpdate = false;
+	if (cur.pos() == 0) {
+		// The cursor is at the beginning of a paragraph, so
+		// the the backspace will collapse two paragraphs into
+		// one.
+
+		if (cur.buffer().params().tracking_changes) {
+			// Previous paragraph, mark "carriage return" as
+			// deleted:
+			Paragraph & par = pars_[cur.pit() - 1];
+			par.setChange(par.size(), Change::DELETED);
+			setCursorIntern(cur, cur.pit() - 1, par.size());
+			return false;
 		}
+
+		needsUpdate = backspacePos0(cur);
+
 	} else {
 		// this is the code for a normal backspace, not pasting
 		// any paragraphs
@@ -2184,9 +2231,11 @@
 	std::ostringstream os;
 
 	bool const show_change = buf.params().tracking_changes
-		&& cur.pos() != cur.lastpos()
 		&& par.lookupChange(cur.pos()) != Change::UNCHANGED;
 
+	if (buf.params().tracking_changes)
+		os << "[C] ";
+
 	if (show_change) {
 		Change change = par.lookupChangeFull(cur.pos());
 		Author const & a = buf.params().authors().get(change.author);

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to