[LyX/master] Implement change tracking of column/row addition/deletion

Juergen Spitzmueller spitz at lyx.org
Sat Jan 11 15:00:35 UTC 2020


commit bbc6ea4a5fb42516f91108d18a40520a2aba232a
Author: Juergen Spitzmueller <spitz at lyx.org>
Date:   Sat Jan 11 16:17:04 2020 +0100

    Implement change tracking of column/row addition/deletion
    
    Fixes #8469
    
    File format change
---
 development/FORMAT          |    4 +
 lib/lyx2lyx/lyx_2_4.py      |   24 +++++-
 src/BufferView.cpp          |    6 ++
 src/insets/InsetTabular.cpp |  205 ++++++++++++++++++++++++++++++++++++++++---
 src/insets/InsetTabular.h   |    8 ++-
 src/version.h               |    4 +-
 6 files changed, 231 insertions(+), 20 deletions(-)

diff --git a/development/FORMAT b/development/FORMAT
index 2a171c0..ee34ef4 100644
--- a/development/FORMAT
+++ b/development/FORMAT
@@ -7,6 +7,10 @@ changes happened in particular if possible. A good example would be
 
 -----------------------
 
+2020-01-11 Jürgen Spitzmüller <spitz at lyx.org>
+ 	* Format incremented to 592: Add tabular column/row tag changed=[added|deleted]
+          which tracks whether a whole row/column has been inserted/deleted in ct.
+
 2020-01-10 Jürgen Spitzmüller <spitz at lyx.org>
  	* Format incremented to 591: Add buffer param \postpone_fragile_content
          (option to toggle the movement of labels and stuff of moving arguments).
diff --git a/lib/lyx2lyx/lyx_2_4.py b/lib/lyx2lyx/lyx_2_4.py
index daae36b..75e7588 100644
--- a/lib/lyx2lyx/lyx_2_4.py
+++ b/lib/lyx2lyx/lyx_2_4.py
@@ -3689,6 +3689,24 @@ def revert_postpone_fragile(document):
 
     del document.header[i]
 
+def revert_colrow_tracking(document):
+    " Remove change tag from tabular columns/rows "
+    i = 0
+    while True:
+        i = find_token(document.body, "\\begin_inset Tabular", i+1)
+        if i == -1:
+            return
+        j = find_end_of_inset(document.body, i+1)
+        if j == -1:
+            document.warning("Malformed LyX document: Could not find end of tabular.")
+            continue
+        for k in range(i, j):
+            m = re.search('^<column.*change="([^"]+)".*>$', document.body[k])
+            if m:
+                document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', '')
+            m = re.search('^<row.*change="([^"]+)".*>$', document.body[k])
+            if m:
+                document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', '')
 
 ##
 # Conversion hub
@@ -3742,10 +3760,12 @@ convert = [
            [588, []],
            [589, [convert_totalheight]],
            [590, [convert_changebars]],
-           [591, [convert_postpone_fragile]]
+           [591, [convert_postpone_fragile]],
+           [592, []]
           ]
 
-revert =  [[590, [revert_postpone_fragile]],
+revert =  [[591, [revert_colrow_tracking]],
+           [590, [revert_postpone_fragile]],
            [589, [revert_changebars]],
            [588, [revert_totalheight]],
            [587, [revert_memoir_endnotes,revert_enotez,revert_theendnotes]],
diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index eda08ed..af0ad36 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -1539,12 +1539,18 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
 	case LFUN_CHANGE_NEXT:
 		findNextChange(this);
+		if (cur.inset().isTable())
+			// In tables, there might be whole changed rows or columns
+			cur.dispatch(cmd);
 		// FIXME: Move this LFUN to Buffer so that we don't have to do this:
 		dr.screenUpdate(Update::Force | Update::FitCursor);
 		break;
 
 	case LFUN_CHANGE_PREVIOUS:
 		findPreviousChange(this);
+		if (cur.inset().isTable())
+			// In tables, there might be whole changed rows or columns
+			cur.dispatch(cmd);
 		// FIXME: Move this LFUN to Buffer so that we don't have to do this:
 		dr.screenUpdate(Update::Force | Update::FitCursor);
 		break;
diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp
index d225854..1bd757b 100644
--- a/src/insets/InsetTabular.cpp
+++ b/src/insets/InsetTabular.cpp
@@ -451,6 +451,25 @@ bool getTokenValue(string const & str, char const * token, Length & len)
 }
 
 
+bool getTokenValue(string const & str, char const * token, Change::Type & change)
+{
+	// set the length to be zero() as default as this it should be if not
+	// in the file format.
+	change = Change::UNCHANGED;
+	string tmp;
+	if (getTokenValue(str, token, tmp)) {
+		if (tmp == "inserted") {
+			change = Change::INSERTED;
+			return true;
+		} else if (tmp == "deleted") {
+			change = Change::DELETED;
+			return true;
+		}
+	}
+	return false;
+}
+
+
 bool getTokenValue(string const & str, char const * token, Length & len, bool & flag)
 {
 	len = Length();
@@ -531,6 +550,16 @@ string const write_attribute(string const & name, Length const & value)
 	return value.zero() ? string() : write_attribute(name, value.asString());
 }
 
+template <>
+string const write_attribute(string const & name, Change::Type const & type)
+{
+	if (type == Change::INSERTED)
+		return write_attribute(name, from_ascii("inserted"));
+	else if (type == Change::DELETED)
+		return write_attribute(name, from_ascii("deleted"));
+	return string();
+}
+
 } // namespace
 
 
@@ -673,7 +702,8 @@ Tabular::RowData::RowData()
 	  endfoot(false),
 	  endlastfoot(false),
 	  newpage(false),
-	  caption(false)
+	  caption(false),
+	  change(Change::UNCHANGED)
 {}
 
 
@@ -681,7 +711,8 @@ Tabular::ColumnData::ColumnData()
 	: alignment(LYX_ALIGN_CENTER),
 	  valignment(LYX_VALIGN_TOP),
 	  width(0),
-	  varwidth(false)
+	  varwidth(false),
+	  change(Change::UNCHANGED)
 {
 }
 
@@ -738,15 +769,17 @@ void Tabular::init(Buffer * buf, row_type rows_arg,
 }
 
 
-void Tabular::deleteRow(row_type const row)
+void Tabular::deleteRow(row_type const row, bool const force)
 {
 	// Not allowed to delete last row
 	if (nrows() == 1)
 		return;
 
+	bool const ct = force ? false : buffer().params().track_changes;
+
 	for (col_type c = 0; c < ncols(); ++c) {
 		// mark track changes
-		if (buffer().params().track_changes)
+		if (ct)
 			cell_info[row][c].inset->setChange(Change(Change::DELETED));
 		// Care about multirow cells
 		if (row + 1 < nrows() &&
@@ -755,7 +788,9 @@ void Tabular::deleteRow(row_type const row)
 				cell_info[row + 1][c].multirow = CELL_BEGIN_OF_MULTIROW;
 		}
 	}
-	if (!buffer().params().track_changes) {
+	if (ct)
+		row_info[row].change = Change::DELETED;
+	else {
 		row_info.erase(row_info.begin() + row);
 		cell_info.erase(cell_info.begin() + row);
 	}
@@ -808,6 +843,8 @@ void Tabular::insertRow(row_type const row, bool copy)
 		if (buffer().params().track_changes)
 			cellInfo(i).inset->setChange(Change(Change::INSERTED));
 	}
+	if (buffer().params().track_changes)
+		row_info[row + 1].change = Change::INSERTED;
 }
 
 
@@ -857,15 +894,17 @@ void Tabular::moveRow(row_type row, RowDirection direction)
 }
 
 
-void Tabular::deleteColumn(col_type const col)
+void Tabular::deleteColumn(col_type const col, bool const force)
 {
 	// Not allowed to delete last column
 	if (ncols() == 1)
 		return;
 
+	bool const ct = force ? false : buffer().params().track_changes;
+
 	for (row_type r = 0; r < nrows(); ++r) {
 		// mark track changes
-		if (buffer().params().track_changes)
+		if (ct)
 			cell_info[r][col].inset->setChange(Change(Change::DELETED));
 		// Care about multicolumn cells
 		if (col + 1 < ncols() &&
@@ -873,10 +912,12 @@ void Tabular::deleteColumn(col_type const col)
 		    cell_info[r][col + 1].multicolumn == CELL_PART_OF_MULTICOLUMN) {
 				cell_info[r][col + 1].multicolumn = CELL_BEGIN_OF_MULTICOLUMN;
 		}
-		if (!buffer().params().track_changes)
+		if (!ct)
 			cell_info[r].erase(cell_info[r].begin() + col);
 	}
-	if (!buffer().params().track_changes)
+	if (ct)
+		column_info[col].change = Change::DELETED;
+	else
 		column_info.erase(column_info.begin() + col);
 	updateIndexes();
 }
@@ -922,6 +963,8 @@ void Tabular::insertColumn(col_type const col, bool copy)
 		if (buffer().params().track_changes)
 			cellInfo(i).inset->setChange(Change(Change::INSERTED));
 	}
+	if (buffer().params().track_changes)
+		column_info[col + 1].change = Change::INSERTED;
 }
 
 
@@ -1662,8 +1705,9 @@ void Tabular::write(ostream & os) const
 		os << "<column"
 		   << write_attribute("alignment", column_info[c].alignment);
 		if (column_info[c].alignment == LYX_ALIGN_DECIMAL)
-		   os << write_attribute("decimal_point", column_info[c].decimal_point);
-		os << write_attribute("valignment", column_info[c].valignment)
+			os << write_attribute("decimal_point", column_info[c].decimal_point);
+		os << write_attribute("change", column_info[c].change)
+		   << write_attribute("valignment", column_info[c].valignment)
 		   << write_attribute("width", column_info[c].p_width.asString())
 		   << write_attribute("varwidth", column_info[c].varwidth)
 		   << write_attribute("special", column_info[c].align_special)
@@ -1684,7 +1728,8 @@ void Tabular::write(ostream & os) const
 			os << write_attribute("interlinespace", def);
 		else
 			os << write_attribute("interlinespace", row_info[r].interline_space);
-		os << write_attribute("endhead", row_info[r].endhead)
+		os << write_attribute("change", row_info[r].change)
+		   << write_attribute("endhead", row_info[r].endhead)
 		   << write_attribute("endfirsthead", row_info[r].endfirsthead)
 		   << write_attribute("endfoot", row_info[r].endfoot)
 		   << write_attribute("endlastfoot", row_info[r].endlastfoot)
@@ -1783,6 +1828,7 @@ void Tabular::read(Lexer & lex)
 		getTokenValue(line, "width", column_info[c].p_width);
 		getTokenValue(line, "special", column_info[c].align_special);
 		getTokenValue(line, "varwidth", column_info[c].varwidth);
+		getTokenValue(line, "change", column_info[c].change);
 	}
 
 	for (row_type i = 0; i < nrows(); ++i) {
@@ -1804,6 +1850,7 @@ void Tabular::read(Lexer & lex)
 		getTokenValue(line, "endlastfoot", row_info[i].endlastfoot);
 		getTokenValue(line, "newpage", row_info[i].newpage);
 		getTokenValue(line, "caption", row_info[i].caption);
+		getTokenValue(line, "change", row_info[i].change);
 		for (col_type j = 0; j < ncols(); ++j) {
 			l_getline(is, line);
 			if (!prefixIs(line, "<cell")) {
@@ -5072,14 +5119,55 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd)
 	case LFUN_WORD_CAPITALIZE:
 	case LFUN_WORD_UPCASE:
 	case LFUN_WORD_LOWCASE:
-	case LFUN_CHARS_TRANSPOSE:
+	case LFUN_CHARS_TRANSPOSE: {
+		bool const ct = (act == LFUN_CHANGE_ACCEPT || act == LFUN_CHANGE_REJECT);
 		if (cur.selIsMultiCell()) {
 			row_type rs, re;
 			col_type cs, ce;
 			getSelection(cur, rs, re, cs, ce);
 			Cursor tmpcur = cur;
 			for (row_type r = rs; r <= re; ++r) {
+				if (ct && cs == 0 && ce == tabular.ncols() - 1) {
+					// whole row selected
+					if (act == LFUN_CHANGE_ACCEPT) {
+						if (tabular.row_info[r].change == Change::INSERTED)
+							tabular.row_info[r].change = Change::UNCHANGED;
+						else if (tabular.row_info[r].change == Change::DELETED) {
+							tabular.deleteRow(r, true);
+							--re;
+							continue;
+						}
+					} else {
+						if (tabular.row_info[r].change == Change::DELETED)
+							tabular.row_info[r].change = Change::UNCHANGED;
+						else if (tabular.row_info[r].change == Change::INSERTED) {
+							tabular.deleteRow(r, true);
+							--re;
+							continue;
+						}
+					}
+				}
 				for (col_type c = cs; c <= ce; ++c) {
+					if (ct && rs == 0 && re == tabular.nrows() - 1) {
+						// whole col selected
+						if (act == LFUN_CHANGE_ACCEPT) {
+							if (tabular.column_info[c].change == Change::INSERTED)
+								tabular.column_info[c].change = Change::UNCHANGED;
+							else if (tabular.column_info[c].change == Change::DELETED) {
+								tabular.deleteColumn(c, true);
+								--ce;
+								continue;
+							}
+						} else {
+							if (tabular.column_info[c].change == Change::DELETED)
+								tabular.column_info[c].change = Change::UNCHANGED;
+							else if (tabular.column_info[c].change == Change::INSERTED) {
+								tabular.deleteColumn(c, true);
+								--ce;
+								continue;
+							}
+						}
+					}
 					// cursor follows cell:
 					tmpcur.idx() = tabular.cellIndex(r, c);
 					// select this cell only:
@@ -5093,7 +5181,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd)
 					cell(tmpcur.idx())->dispatch(tmpcur, cmd);
 				}
 			}
-			if (act == LFUN_CHANGE_ACCEPT || act == LFUN_CHANGE_REJECT) {
+			if (ct) {
 				// cursor might be invalid
 				cur.fixIfBroken();
 			}
@@ -5102,6 +5190,40 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd)
 			cell(cur.idx())->dispatch(cur, cmd);
 			break;
 		}
+	}
+
+	case LFUN_CHANGE_NEXT:
+	case LFUN_CHANGE_PREVIOUS: {
+		// BufferView::dispatch has already moved the cursor, we just
+		// need to select here if we have a changed row or column
+		if (tabular.row_info[tabular.cellRow(cur.idx())].change != Change::UNCHANGED) {
+			// select row
+			cur.idx() = tabular.getFirstCellInRow(tabular.cellRow(cur.idx()));
+			cur.pit() = 0;
+			cur.pos() = 0;
+			cur.resetAnchor();
+			cur.idx() = tabular.getLastCellInRow(tabular.cellRow(cur.idx()));
+			cur.pit() = cur.lastpit();
+			cur.pos() = cur.lastpos();
+			cur.selection(true);
+			bvcur = cur;
+			rowselect_ = true;
+		}
+		else if (tabular.column_info[tabular.cellColumn(cur.idx())].change != Change::UNCHANGED) {
+			// select column
+			cur.idx() = tabular.cellIndex(0, tabular.cellColumn(cur.idx()));
+			cur.pit() = 0;
+			cur.pos() = 0;
+			cur.resetAnchor();
+			cur.idx() = tabular.cellIndex(tabular.nrows() - 1, tabular.cellColumn(cur.idx()));
+			cur.pit() = cur.lastpit();
+			cur.pos() = cur.lastpos();
+			cur.selection(true);
+			bvcur = cur;
+			colselect_ = true;
+		}
+		break;
+	}
 
 	case LFUN_INSET_SETTINGS:
 		// relay this lfun to Inset, not to the cell.
@@ -5658,6 +5780,37 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd,
 			return cell(cur.idx())->getStatus(cur, cmd, status);
 	}
 
+	case LFUN_CHANGE_ACCEPT:
+	case LFUN_CHANGE_REJECT: {
+		if (cur.selIsMultiCell()) {
+			row_type rs, re;
+			col_type cs, ce;
+			getSelection(cur, rs, re, cs, ce);
+			for (row_type r = rs; r <= re; ++r) {
+				if (tabular.row_info[r].change != Change::UNCHANGED) {
+					status.setEnabled(true);
+					return true;
+				}
+				for (col_type c = cs; c <= ce; ++c) {
+					if (tabular.column_info[c].change != Change::UNCHANGED) {
+						status.setEnabled(true);
+						return true;
+					}
+				}
+			}
+		} else {
+			if (tabular.row_info[tabular.cellRow(cur.idx())].change != Change::UNCHANGED) {
+				status.setEnabled(true);
+				return true;
+			}
+			else if (tabular.column_info[tabular.cellColumn(cur.idx())].change != Change::UNCHANGED) {
+				status.setEnabled(true);
+				return true;
+			}
+		}
+		return cell(cur.idx())->getStatus(cur, cmd, status);
+	}
+
 	// disable in non-fixed-width cells
 	case LFUN_PARAGRAPH_BREAK:
 		// multirow does not allow paragraph breaks
@@ -6980,6 +7133,18 @@ void InsetTabular::acceptChanges()
 {
 	for (idx_type idx = 0; idx < nargs(); ++idx)
 		cell(idx)->acceptChanges();
+	for (row_type row = 0; row < tabular.nrows(); ++row) {
+		if (tabular.row_info[row].change == Change::INSERTED)
+			tabular.row_info[row].change = Change::UNCHANGED;
+		else if (tabular.row_info[row].change == Change::DELETED)
+			tabular.deleteRow(row, true);
+	}
+	for (col_type col = 0; col < tabular.ncols(); ++col) {
+		if (tabular.column_info[col].change == Change::INSERTED)
+			tabular.column_info[col].change = Change::UNCHANGED;
+		else if (tabular.column_info[col].change == Change::DELETED)
+			tabular.deleteColumn(col, true);
+	}
 }
 
 
@@ -6987,6 +7152,18 @@ void InsetTabular::rejectChanges()
 {
 	for (idx_type idx = 0; idx < nargs(); ++idx)
 		cell(idx)->rejectChanges();
+	for (row_type row = 0; row < tabular.nrows(); ++row) {
+		if (tabular.row_info[row].change == Change::DELETED)
+			tabular.row_info[row].change = Change::UNCHANGED;
+		else if (tabular.row_info[row].change == Change::INSERTED)
+			tabular.deleteRow(row, true);
+	}
+	for (col_type col = 0; col < tabular.ncols(); ++col) {
+		if (tabular.column_info[col].change == Change::DELETED)
+			tabular.column_info[col].change = Change::UNCHANGED;
+		else if (tabular.column_info[col].change == Change::INSERTED)
+			tabular.deleteColumn(col, true);
+	}
 }
 
 
diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h
index 65cc2ae..edd7242 100644
--- a/src/insets/InsetTabular.h
+++ b/src/insets/InsetTabular.h
@@ -537,7 +537,7 @@ public:
 	///
 	void appendRow(row_type row);
 	///
-	void deleteRow(row_type row);
+	void deleteRow(row_type row, bool const force = false);
 	///
 	void copyRow(row_type row);
 	///
@@ -549,7 +549,7 @@ public:
 	///
 	void appendColumn(col_type column);
 	///
-	void deleteColumn(col_type column);
+	void deleteColumn(col_type column, bool const force = false);
 	///
 	void copyColumn(col_type column);
 	///
@@ -785,6 +785,8 @@ public:
 		bool newpage;
 		/// caption
 		bool caption;
+		///
+		Change::Type change;
 	};
 	///
 	typedef std::vector<RowData> row_vector;
@@ -808,6 +810,8 @@ public:
 		docstring decimal_point;
 		///
 		bool varwidth;
+		///
+		Change::Type change;
 	};
 	///
 	typedef std::vector<ColumnData> column_vector;
diff --git a/src/version.h b/src/version.h
index 8d5c212..df249ab 100644
--- a/src/version.h
+++ b/src/version.h
@@ -32,8 +32,8 @@ extern char const * const lyx_version_info;
 
 // Do not remove the comment below, so we get merge conflict in
 // independent branches. Instead add your own.
-#define LYX_FORMAT_LYX 591 // spitz: postpone_fragile_content
-#define LYX_FORMAT_TEX2LYX 591
+#define LYX_FORMAT_LYX 592 // spitz: row/column change tracking
+#define LYX_FORMAT_TEX2LYX 592
 
 #if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
 #ifndef _MSC_VER


More information about the lyx-cvs mailing list