The big change tracking paragraph patch (bug 880)

from Martin Vermeer  <martin.vermeer@hut.fi>:

	* text.C (backspace): Fix changebar non-update
	* CutAndPaste.C (pasteSelectionHelper): comments
	* paragraph_funcs.C (mergeParagraph): fix Juergen's cut&paste bug
	* changes.h: comments
	* paragraph.C (stripLeadingSpaces): remove unnecessary setChange
	* text.C (backspace): allow deletion of inserted para break
	Change tracking -related bug fixes (reported by Juergen) and
	some documentation work
	* rowpainter.C (paintChangeBar): fix painting of change bar with
	only paragraph break changed
	* paragraph.[Ch] (write, lookupChange, lookupChangeFull;
	added setChangeFull):
	* paragraph_pimpl.[Ch] (trackChanges, cleanChanges, acceptChange,
	rejectChange, erase; added setChangeFull):
	* CutAndPaste.C (eraseSelectionHelper):
	* lyxtext.h:
	* text.C (readParToken, readParagraph, breakParagraph,
	acceptChange, rejectChange, backspace, currentState;
	added backspacePos0):
	* paragraph_funcs.C (breakParagraphConservative, mergeParagraph):
	* lyxfind.C (findChange, findNextChange): fix bug 880: Change
	tracked paragraphs should still allow a paragraph split (and related
	things, i.e., multi-paragraph change tracking)

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/branches/BRANCH_1_4_X@13486 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Jürgen Spitzmüller 2006-03-24 16:38:30 +00:00
parent 2e7158c61b
commit 3f2c546936
13 changed files with 320 additions and 208 deletions

View File

@ -1,3 +1,38 @@
2006-03-18 Martin Vermeer <martin.vermeer@hut.fi>
* text.C (backspace): Fix changebar non-update
2006-03-15 Martin Vermeer <martin.vermeer@hut.fi>
* CutAndPaste.C (pasteSelectionHelper): comments
* paragraph_funcs.C (mergeParagraph): fix Juergen's cut&paste bug
* changes.h: comments
* paragraph.C (stripLeadingSpaces): remove unnecessary setChange
* text.C (backspace): allow deletion of inserted para break
Change tracking -related bug fixes (reported by Juergen) and
some documentation work
2006-03-13 Martin Vermeer <martin.vermeer@hut.fi>
* rowpainter.C (paintChangeBar): fix painting of change bar with
only paragraph break changed
2006-03-11 Martin Vermeer <martin.vermeer@hut.fi>
* paragraph.[Ch] (write, lookupChange, lookupChangeFull;
added setChangeFull):
* paragraph_pimpl.[Ch] (trackChanges, cleanChanges, acceptChange,
rejectChange, erase; added setChangeFull):
* CutAndPaste.C (eraseSelectionHelper):
* lyxtext.h:
* text.C (readParToken, readParagraph, breakParagraph,
acceptChange, rejectChange, backspace, currentState;
added backspacePos0):
* paragraph_funcs.C (breakParagraphConservative, mergeParagraph):
* lyxfind.C (findChange, findNextChange): fix bug 880: Change
tracked paragraphs should still allow a paragraph split (and related
things, i.e., multi-paragraph change tracking)
2006-02-20 Jean-Marc Lasgouttes <lasgouttes@lyx.org> 2006-02-20 Jean-Marc Lasgouttes <lasgouttes@lyx.org>
* cursor.C (bruteFind): only iterate over the paragraphs that are * cursor.C (bruteFind): only iterate over the paragraphs that are

View File

@ -234,7 +234,8 @@ pasteSelectionHelper(Buffer const & buffer,
pit = last_paste; pit = last_paste;
pos = pars[last_paste].size(); pos = pars[last_paste].size();
// Maybe some pasting. // Join (conditionally) last pasted paragraph with next one, i.e.,
// the tail of the spliced document paragraph
if (!empty && last_paste + 1 != pit_type(pars.size())) { if (!empty && last_paste + 1 != pit_type(pars.size())) {
if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) { if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) {
mergeParagraph(buffer.params(), pars, last_paste); mergeParagraph(buffer.params(), pars, last_paste);
@ -271,64 +272,35 @@ PitPosPair eraseSelectionHelper(BufferParams const & params,
return PitPosPair(endpit, endpos); return PitPosPair(endpit, endpos);
} }
bool all_erased = true; // A paragraph break has to be physically removed by merging, but
// only if either (1) change tracking is off, or (2) the para break
// Clear fragments of the first par in selection // is "blue"
pars[startpit].erase(startpos, pars[startpit].size()); for (pit_type pit = startpit; pit != endpit + 1;) {
if (pars[startpit].size() != startpos) bool const merge = !params.tracking_changes ||
all_erased = false; pars[pit].lookupChange(pars[pit].size()) ==
Change::INSERTED;
// Clear fragments of the last par in selection pos_type const left = ( pit == startpit ? startpos : 0 );
endpos -= pars[endpit].erase(0, endpos); pos_type const right = ( pit == endpit ? endpos :
if (endpos != 0) pars[pit].size() + 1 );
all_erased = false; // Logical erase only:
pars[pit].erase(left, right);
// Erase all the "middle" paragraphs. // Separate handling of para break:
if (params.tracking_changes) { if (merge && pit != endpit &&
// Look through the deleted pars if any, erasing as needed pars[pit].hasSameLayout(pars[pit + 1])) {
for (pit_type pit = startpit + 1; pit != endpit;) { pos_type const thissize = pars[pit].size();
// "erase" the contents of the par if (doclear)
pars[pit].erase(0, pars[pit].size()); pars[pit + 1].stripLeadingSpaces();
if (pars[pit].empty()) { mergeParagraph(params, pars, pit);
// remove the par if it's now empty --endpit;
pars.erase(pars.begin() + pit); if (pit == endpit)
--endpit; endpos += thissize;
} else { } else
++pit; ++pit;
all_erased = false;
}
}
} else {
pars.erase(pars.begin() + startpit + 1, pars.begin() + endpit);
endpit = startpit + 1;
}
#if 0 // FIXME: why for cut but not copy ?
// the cut selection should begin with standard layout
if (realcut) {
buf->params().clear();
buf->bibkey = 0;
buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName());
}
#endif
if (startpit + 1 == pit_type(pars.size()))
return PitPosPair(endpit, endpos);
if (doclear) {
pars[startpit + 1].stripLeadingSpaces();
}
// Merge first and last paragraph, if possible
if (all_erased &&
(pars[startpit].hasSameLayout(pars[startpit + 1]) ||
pars[startpit + 1].empty())) {
mergeParagraph(params, pars, startpit);
// This because endpar gets deleted here!
endpit = startpit;
endpos = startpos;
} }
// Ensure legal cursor pos:
endpit = startpit;
endpos = startpos;
return PitPosPair(endpit, endpos); return PitPosPair(endpit, endpos);
} }

View File

@ -84,7 +84,8 @@ public:
/// return true if there is a deleted or unchanged range contained /// return true if there is a deleted or unchanged range contained
bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const;
/// remove the given entry /// remove the given entry. This implies that a character was
/// deleted at pos, and will adjust all range bounds past it
void erase(lyx::pos_type pos); void erase(lyx::pos_type pos);
/// output latex to mark a transition between two changetypes /// output latex to mark a transition between two changetypes
@ -134,22 +135,23 @@ private:
typedef std::vector<ChangeRange> ChangeTable; typedef std::vector<ChangeRange> ChangeTable;
/// our table of changes /// our table of changes, every row a range and change descriptor
ChangeTable table_; ChangeTable table_;
/// change type for an empty paragraph /// change type for an empty paragraph
Change::Type empty_type_; Change::Type empty_type_;
/// handle a delete /// handle a delete, either logical or physical (see erase)
void del(Change change, ChangeTable::size_type pos); void del(Change change, ChangeTable::size_type pos);
/// handle an add /// handle an add, adjusting range bounds past it
void add(Change change, ChangeTable::size_type pos); void add(Change change, ChangeTable::size_type pos);
/// merge neighbouring ranges /// merge neighbouring ranges, assuming that they are abutting
/// (as done by set())
void merge(); void merge();
/// consistency check /// consistency check, needed before merge()
void check() const; void check() const;
}; };

View File

@ -127,9 +127,8 @@ bool findBackwards(DocIterator & cur, MatchString const & match)
bool findChange(DocIterator & cur) bool findChange(DocIterator & cur)
{ {
for (; cur; cur.forwardChar()) for (; cur; cur.forwardPos())
if (cur.inTexted() && cur.pos() != cur.paragraph().size() && if (cur.inTexted() && cur.paragraph().lookupChange(cur.pos())
cur.paragraph().lookupChange(cur.pos())
!= Change::UNCHANGED) != Change::UNCHANGED)
return true; return true;
return false; return false;
@ -344,25 +343,21 @@ bool findNextChange(BufferView * bv)
if (!findChange(cur)) if (!findChange(cur))
return false; return false;
Paragraph const & par = cur.paragraph(); bv->cursor().setCursor(cur);
const pos_type pos = cur.pos(); bv->cursor().resetAnchor();
Change orig_change = cur.paragraph().lookupChangeFull(cur.pos());
Change orig_change = par.lookupChangeFull(pos); DocIterator et = doc_iterator_end(cur.inset());
const pos_type parsize = par.size(); for (; cur != et; cur.forwardPosNoDescend()) {
pos_type end = pos; Change change = cur.paragraph().lookupChangeFull(cur.pos());
for (; end != parsize; ++end) {
Change change = par.lookupChangeFull(end);
if (change != orig_change) { if (change != orig_change) {
// slight UI optimisation: for replacements, we get break;
// text like : _old_new. Consider that as one change.
if (!(orig_change.type == Change::DELETED &&
change.type == Change::INSERTED))
break;
} }
} }
pos_type length = end - pos; // Now put cursor to end of selection:
bv->putSelectionAt(cur, length, false); bv->cursor().setCursor(cur);
bv->cursor().setSelection();
// if we used a lfun like in find/replace, dispatch would do // if we used a lfun like in find/replace, dispatch would do
// that for us // that for us
bv->update(); bv->update();

View File

@ -220,9 +220,14 @@ public:
bool cursorTop(LCursor & cur); bool cursorTop(LCursor & cur);
/// ///
bool cursorBottom(LCursor & cur); bool cursorBottom(LCursor & cur);
/// /// Delete character at cursor. Honour change tracking
bool Delete(LCursor & cur); bool Delete(LCursor & cur);
/// /** At cursor position 0, merge paragraph with the one before it.
* Ignore CT (this is used in \c acceptChange, \c rejectChange for
* physical deletion of paragraph break)
*/
bool backspacePos0(LCursor & cur);
/// Delete character before cursor. Honour CT
bool backspace(LCursor & cur); bool backspace(LCursor & cur);
/// ///
bool selectWordWhenUnderCursor(LCursor & cur, lyx::word_location); bool selectWordWhenUnderCursor(LCursor & cur, lyx::word_location);

View File

@ -159,12 +159,15 @@ void Paragraph::write(Buffer const & buf, ostream & os,
lyx::time_type const curtime(lyx::current_time()); lyx::time_type const curtime(lyx::current_time());
int column = 0; int column = 0;
for (pos_type i = 0; i < size(); ++i) { for (pos_type i = 0; i <= size(); ++i) {
Change change = pimpl_->lookupChangeFull(i); Change change = pimpl_->lookupChangeFull(i);
Changes::lyxMarkChange(os, column, curtime, running_change, change); Changes::lyxMarkChange(os, column, curtime, running_change, change);
running_change = change; running_change = change;
if (i == size())
break;
// Write font changes // Write font changes
LyXFont font2 = getFontSettings(bparams, i); LyXFont font2 = getFontSettings(bparams, i);
if (font2 != font1) { if (font2 != font1) {
@ -223,15 +226,6 @@ void Paragraph::write(Buffer const & buf, ostream & os,
} }
} }
// to make reading work properly
if (!size()) {
running_change = pimpl_->lookupChange(0);
Changes::lyxMarkChange(os, column, curtime,
Change(Change::UNCHANGED), running_change);
}
Changes::lyxMarkChange(os, column, curtime,
running_change, Change(Change::UNCHANGED));
os << "\n\\end_layout\n"; os << "\n\\end_layout\n";
} }
@ -569,9 +563,8 @@ int Paragraph::stripLeadingSpaces()
return 0; return 0;
int i = 0; int i = 0;
while (!empty() && (isNewline(0) || isLineSeparator(0))) { while (!empty() && (isNewline(0) || isLineSeparator(0))
// Set Change::Type to Change::INSERTED to quietly remove it && (lookupChange(0) != Change::DELETED)) {
setChange(0, Change::INSERTED);
erase(0); erase(0);
++i; ++i;
} }
@ -1639,14 +1632,14 @@ void Paragraph::cleanChanges()
Change::Type Paragraph::lookupChange(lyx::pos_type pos) const Change::Type Paragraph::lookupChange(lyx::pos_type pos) const
{ {
BOOST_ASSERT(empty() || pos < size()); BOOST_ASSERT(pos <= size());
return pimpl_->lookupChange(pos); return pimpl_->lookupChange(pos);
} }
Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const
{ {
BOOST_ASSERT(empty() || pos < size()); BOOST_ASSERT(pos <= size());
return pimpl_->lookupChangeFull(pos); return pimpl_->lookupChangeFull(pos);
} }
@ -1669,6 +1662,12 @@ void Paragraph::setChange(lyx::pos_type pos, Change::Type type)
} }
void Paragraph::setChangeFull(lyx::pos_type pos, Change change)
{
pimpl_->setChangeFull(pos, change);
}
void Paragraph::markErased(bool erased) void Paragraph::markErased(bool erased)
{ {
pimpl_->markErased(erased); pimpl_->markErased(erased);

View File

@ -224,6 +224,9 @@ public:
/// set change at pos /// set change at pos
void setChange(lyx::pos_type pos, Change::Type type); void setChange(lyx::pos_type pos, Change::Type type);
/// set full change at pos
void setChangeFull(lyx::pos_type pos, Change change);
/// accept change /// accept change
void acceptChange(lyx::pos_type start, lyx::pos_type end); void acceptChange(lyx::pos_type start, lyx::pos_type end);

View File

@ -213,6 +213,9 @@ void breakParagraphConservative(BufferParams const & bparams,
if (moveItem(par, tmp, bparams, i, j - pos, change)) if (moveItem(par, tmp, bparams, i, j - pos, change))
++j; ++j;
} }
// Move over end-of-par change attr
tmp.setChange(tmp.size(), par.lookupChange(par.size()));
// If tracking changes, set all the text that is to be // If tracking changes, set all the text that is to be
// erased to Type::INSERTED. // erased to Type::INSERTED.
for (pos_type k = pos_end; k >= pos; --k) { for (pos_type k = pos_end; k >= pos; --k) {
@ -233,12 +236,28 @@ void mergeParagraph(BufferParams const & bparams,
pos_type pos_end = next.size() - 1; pos_type pos_end = next.size() - 1;
pos_type pos_insert = par.size(); pos_type pos_insert = par.size();
// What happens is the following. Later on, moveItem() will copy
// over characters from the next paragraph to be inserted into this
// position. Now, if the first char to be so copied is "red" (i.e.,
// marked deleted) and the paragraph break is marked "blue",
// insertChar will trigger (eventually, through record(), and see
// del() and erase() in changes.C) a "hard" character deletion.
// Which doesn't make sense of course at this pos, but the effect is
// to shorten the change range to which this para break belongs, by
// one. It will (should) remain "orphaned", having no CT info to it,
// and check() in changes.C will assert. Setting the para break
// forcibly to "black" prevents this scenario. -- MV 13.3.2006
par.setChange(par.size(), Change::UNCHANGED);
Change::Type cr = next.lookupChange(next.size());
// ok, now copy the paragraph // ok, now copy the paragraph
for (pos_type i = 0, j = 0; i <= pos_end; ++i) { for (pos_type i = 0, j = 0; i <= pos_end; ++i) {
Change::Type change = next.lookupChange(i); Change::Type change = next.lookupChange(i);
if (moveItem(next, par, bparams, i, pos_insert + j, change)) if (moveItem(next, par, bparams, i, pos_insert + j, change))
++j; ++j;
} }
// Move the change status of "carriage return" over
par.setChange(par.size(), cr);
pars.erase(pars.begin() + par_offset + 1); pars.erase(pars.begin() + par_offset + 1);
} }

View File

@ -99,7 +99,7 @@ void Paragraph::Pimpl::trackChanges(Change::Type type)
lyxerr[Debug::CHANGES] << "track changes for par " lyxerr[Debug::CHANGES] << "track changes for par "
<< id_ << " type " << type << endl; << id_ << " type " << type << endl;
changes_.reset(new Changes(type)); changes_.reset(new Changes(type));
changes_->set(type, 0, size()); changes_->set(type, 0, size() + 1);
} }
@ -116,7 +116,7 @@ void Paragraph::Pimpl::cleanChanges()
return; return;
changes_.reset(new Changes(Change::INSERTED)); changes_.reset(new Changes(Change::INSERTED));
changes_->set(Change::INSERTED, 0, size()); changes_->set(Change::INSERTED, 0, size() + 1);
} }
@ -147,6 +147,14 @@ void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type)
} }
void Paragraph::Pimpl::setChangeFull(pos_type pos, Change change)
{
if (!tracking())
return;
changes_->set(change, pos);
}
Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const
{ {
if (!tracking()) if (!tracking())
@ -204,10 +212,14 @@ void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end)
break; break;
case Change::DELETED: case Change::DELETED:
eraseIntern(i); // Suppress access to nonexistent
changes_->erase(i); // "end-of-paragraph char":
--end; if (i < size()) {
--i; eraseIntern(i);
changes_->erase(i);
--end;
--i;
}
break; break;
} }
} }
@ -235,15 +247,18 @@ void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end)
break; break;
case Change::INSERTED: case Change::INSERTED:
eraseIntern(i); if (i < size()) {
changes_->erase(i); eraseIntern(i);
--end; changes_->erase(i);
--i; --end;
--i;
}
break; break;
case Change::DELETED: case Change::DELETED:
changes_->set(Change::UNCHANGED, i); changes_->set(Change::UNCHANGED, i);
if (owner_->isInset(i)) // No real char at position size():
if (i < size() && owner_->isInset(i))
owner_->getInset(i)->markErased(false); owner_->getInset(i)->markErased(false);
break; break;
} }
@ -351,7 +366,7 @@ void Paragraph::Pimpl::eraseIntern(pos_type pos)
bool Paragraph::Pimpl::erase(pos_type pos) bool Paragraph::Pimpl::erase(pos_type pos)
{ {
BOOST_ASSERT(pos < size()); BOOST_ASSERT(pos <= size());
if (tracking()) { if (tracking()) {
Change::Type changetype(changes_->lookup(pos)); Change::Type changetype(changes_->lookup(pos));
@ -359,14 +374,19 @@ bool Paragraph::Pimpl::erase(pos_type pos)
// only allow the actual removal if it was /new/ text // only allow the actual removal if it was /new/ text
if (changetype != Change::INSERTED) { if (changetype != Change::INSERTED) {
if (owner_->isInset(pos)) if (pos < size() && owner_->isInset(pos))
owner_->getInset(pos)->markErased(true); owner_->getInset(pos)->markErased(true);
return false; return false;
} }
} }
eraseIntern(pos); // Don't physically access nonexistent end-of-paragraph char
return true; if (pos < size()) {
eraseIntern(pos);
return true;
}
return false;
} }

View File

@ -54,6 +54,8 @@ public:
bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const;
/// set change at pos /// set change at pos
void setChange(lyx::pos_type pos, Change::Type type); void setChange(lyx::pos_type pos, Change::Type type);
/// set full change at pos
void setChangeFull(lyx::pos_type pos, Change change);
/// mark as erased /// mark as erased
void markErased(bool); void markErased(bool);
/// accept change /// accept change

View File

@ -347,7 +347,7 @@ void RowPainter::paintChangeBar()
pos_type const start = row_.pos(); pos_type const start = row_.pos();
pos_type const end = row_.endpos(); pos_type const end = row_.endpos();
if (start == end || !par_.isChanged(start, end - 1)) if (start == end || !par_.isChanged(start, end))
return; return;
int const height = text_.isLastRow(pit_, row_) int const height = text_.isLastRow(pit_, row_)

View File

@ -315,8 +315,10 @@ void readParToken(Buffer const & buf, Paragraph & par, LyXLex & lex,
} else if (token == "\\change_unchanged") { } else if (token == "\\change_unchanged") {
// Hack ! Needed for empty paragraphs :/ // Hack ! Needed for empty paragraphs :/
// FIXME: is it still ?? // FIXME: is it still ??
/*
if (!par.size()) if (!par.size())
par.cleanChanges(); par.cleanChanges();
*/
change = Change(Change::UNCHANGED); change = Change(Change::UNCHANGED);
} else if (token == "\\change_inserted") { } else if (token == "\\change_inserted") {
lex.eatLine(); lex.eatLine();
@ -375,6 +377,9 @@ void readParagraph(Buffer const & buf, Paragraph & par, LyXLex & lex)
break; break;
} }
} }
// Final change goes to paragraph break:
par.setChangeFull(par.size(), change);
// Initialize begin_of_body_ on load; redoParagraph maintains // Initialize begin_of_body_ on load; redoParagraph maintains
par.setBeginOfBody(); par.setBeginOfBody();
} }
@ -1026,14 +1031,10 @@ namespace {
void LyXText::breakParagraph(LCursor & cur, bool keep_layout) void LyXText::breakParagraph(LCursor & cur, bool keep_layout)
{ {
BOOST_ASSERT(this == cur.text()); BOOST_ASSERT(this == cur.text());
// allow only if at start or end, or all previous is new text
Paragraph & cpar = cur.paragraph(); Paragraph & cpar = cur.paragraph();
pit_type cpit = cur.pit(); 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(); LyXTextClass const & tclass = cur.buffer().params().getLyXTextClass();
LyXLayout_ptr const & layout = cpar.layout(); LyXLayout_ptr const & layout = cpar.layout();
@ -1088,6 +1089,12 @@ void LyXText::breakParagraph(LCursor & cur, bool keep_layout)
updateCounters(cur.buffer()); 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 // This check is necessary. Otherwise the new empty paragraph will
// be deleted automatically. And it is more friendly for the user! // be deleted automatically. And it is more friendly for the user!
if (cur.pos() != 0 || isempty) if (cur.pos() != 0 || isempty)
@ -1392,18 +1399,34 @@ void LyXText::acceptChange(LCursor & cur)
if (!cur.selection() && cur.lastpos() != 0) if (!cur.selection() && cur.lastpos() != 0)
return; return;
CursorSlice const & startc = cur.selBegin(); recordUndoSelection(cur, Undo::INSERT);
CursorSlice const & endc = cur.selEnd();
if (startc.pit() == endc.pit()) { DocIterator it = cur.selectionBegin();
recordUndoSelection(cur, Undo::INSERT); DocIterator et = cur.selectionEnd();
pars_[startc.pit()].acceptChange(startc.pos(), endc.pos()); pit_type pit = it.pit();
finishUndo(); Change::Type const type = pars_[pit].lookupChange(it.pos());
cur.clearSelection(); for (; pit <= et.pit(); ++pit) {
setCursorIntern(cur, startc.pit(), 0); pos_type left = ( pit == it.pit() ? it.pos() : 0 );
pos_type right =
( pit == et.pit() ? et.pos() : pars_[pit].size() + 1 );
pars_[pit].acceptChange(left, right);
} }
#ifdef WITH_WARNINGS if (type == Change::DELETED) {
#warning handle multi par selection ParagraphList & plist = paragraphs();
#endif 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);
} }
@ -1413,18 +1436,33 @@ void LyXText::rejectChange(LCursor & cur)
if (!cur.selection() && cur.lastpos() != 0) if (!cur.selection() && cur.lastpos() != 0)
return; return;
CursorSlice const & startc = cur.selBegin(); recordUndoSelection(cur, Undo::INSERT);
CursorSlice const & endc = cur.selEnd();
if (startc.pit() == endc.pit()) { DocIterator it = cur.selectionBegin();
recordUndoSelection(cur, Undo::INSERT); DocIterator et = cur.selectionEnd();
pars_[startc.pit()].rejectChange(startc.pos(), endc.pos()); pit_type pit = it.pit();
finishUndo(); Change::Type const type = pars_[pit].lookupChange(it.pos());
cur.clearSelection(); for (; pit <= et.pit(); ++pit) {
setCursorIntern(cur, startc.pit(), 0); pos_type left = ( pit == it.pit() ? it.pos() : 0 );
pos_type right =
( pit == et.pit() ? et.pos() : pars_[pit].size() + 1 );
pars_[pit].rejectChange(left, right);
} }
#ifdef WITH_WARNINGS if (type == Change::INSERTED) {
#warning handle multi par selection ParagraphList & plist = paragraphs();
#endif 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);
} }
@ -1560,6 +1598,80 @@ bool LyXText::Delete(LCursor & cur)
} }
bool LyXText::backspacePos0(LCursor & cur)
{
BOOST_ASSERT(this == cur.text());
bool needsUpdate = 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.
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;
}
}
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);
}
// 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 (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);
}
return needsUpdate;
}
bool LyXText::backspace(LCursor & cur) bool LyXText::backspace(LCursor & cur)
{ {
BOOST_ASSERT(this == cur.text()); BOOST_ASSERT(this == cur.text());
@ -1569,77 +1681,20 @@ bool LyXText::backspace(LCursor & cur)
// the the backspace will collapse two paragraphs into // the the backspace will collapse two paragraphs into
// one. // one.
// but it's not allowed unless it's new if (cur.buffer().params().tracking_changes) {
Paragraph & par = cur.paragraph(); // Previous paragraph, mark "carriage return" as
if (par.isChangeEdited(0, par.size())) // deleted:
return false; Paragraph & par = pars_[cur.pit() - 1];
// Take care of a just inserted para break:
// we may paste some paragraphs if (par.lookupChange(par.size()) != Change::INSERTED) {
par.setChange(par.size(), Change::DELETED);
// is it an empty paragraph? setCursorIntern(cur, cur.pit() - 1, par.size());
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());
}
cursorLeft(cur);
return true; return true;
} }
} }
if (cur.pit() != 0) needsUpdate = backspacePos0(cur);
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);
}
// 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 (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);
}
} else { } else {
// this is the code for a normal backspace, not pasting // this is the code for a normal backspace, not pasting
// any paragraphs // any paragraphs
@ -2186,9 +2241,11 @@ string LyXText::currentState(LCursor & cur)
std::ostringstream os; std::ostringstream os;
bool const show_change = buf.params().tracking_changes bool const show_change = buf.params().tracking_changes
&& cur.pos() != cur.lastpos()
&& par.lookupChange(cur.pos()) != Change::UNCHANGED; && par.lookupChange(cur.pos()) != Change::UNCHANGED;
if (buf.params().tracking_changes)
os << "[C] ";
if (show_change) { if (show_change) {
Change change = par.lookupChangeFull(cur.pos()); Change change = par.lookupChangeFull(cur.pos());
Author const & a = buf.params().authors().get(change.author); Author const & a = buf.params().authors().get(change.author);

View File

@ -47,6 +47,9 @@ What's new
* User Interface: * User Interface:
- Enable breaking and merging of paragraphs in change tracking mode
(bug 880).
- Convert line endings for external copy/paste on OS X (bug 1955). - Convert line endings for external copy/paste on OS X (bug 1955).
- Disable saving when document is unchanged (bug 2313) - Disable saving when document is unchanged (bug 2313)