diff --git a/lib/ChangeLog b/lib/ChangeLog index 324a34e5d2..ff19f8c258 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,11 @@ +2003-02-08 John Levon + + * chkconfig.ltx: look for dvipost package + + * configure.m4: look for and prefer pplatex + + * ui/default.ui: Add change tracking menu items + 2003-02-07 Tomasz Luczak * kbd/polski.kmap: new keymap, which assumes that you have a diff --git a/lib/chkconfig.ltx b/lib/chkconfig.ltx index 6480354a69..cb720f1cf2 100644 --- a/lib/chkconfig.ltx +++ b/lib/chkconfig.ltx @@ -225,6 +225,7 @@ \TestPackage{varioref} \TestPackage{prettyref} \TestPackage{natbib} +\TestPackage{dvipost} % The test for the graphics package is slightly more involved... \newcommand\groption{dvips} diff --git a/lib/configure.m4 b/lib/configure.m4 index f95f6337f2..c4a27b7901 100644 --- a/lib/configure.m4 +++ b/lib/configure.m4 @@ -199,7 +199,7 @@ fi rm -f chklatex.ltx chklatex.log])dnl dnl # Search LaTeX2e -SEARCH_PROG([for a LaTeX2e program],LATEX,latex latex2e,CHECKLATEX2E,dnl +SEARCH_PROG([for a LaTeX2e program],LATEX,pplatex latex2e latex,CHECKLATEX2E,dnl [lyx_check_config=no]) latex_to_dvi=$LATEX test -z "$latex_to_dvi" && latex_to_dvi="none" diff --git a/lib/ui/default.ui b/lib/ui/default.ui index 4d65bee872..82bd514003 100644 --- a/lib/ui/default.ui +++ b/lib/ui/default.ui @@ -80,6 +80,7 @@ Menuset Item "Check TeX|h" "buffer-chktex" Item "Remove All Error Boxes|E" "error-remove-all" Item "Open/Close float|l" "inset-toggle" + Submenu "Change tracking|h" "edit_change" Separator Item "Preferences...|P" "dialog-preferences" Item "Reconfigure|R" "reconfigure" @@ -292,6 +293,12 @@ Menuset Item "ASCII as Paragraphs...|P" "file-insert-ascii-para" End + Menu "edit_change" + Item "Track changes|T" "track-changes" + Item "Merge changes ...|M" "merge-changes" + Item "Accept all changes|A" "accept-all-changes" + Item "Reject all changes|R" "reject-all-changes" + End # # LAYOUT MENU # diff --git a/po/POTFILES.in b/po/POTFILES.in index 5fc56ca9c0..b08fd3dfa0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,19 +1,17 @@ -src/BufferView.C -src/BufferView_pimpl.C -src/Chktex.C -src/CutAndPaste.C -src/LColor.C -src/LaTeX.C -src/LyXAction.C -src/MenuBackend.C src/buffer.C src/bufferlist.C +src/BufferView.C src/bufferview_funcs.C +src/BufferView_pimpl.C +src/Chktex.C src/converter.C +src/CutAndPaste.bak.C +src/CutAndPaste.C src/debug.C src/exporter.C -src/frontends/LyXView.C +src/frontends/controllers/biblio.C src/frontends/controllers/ButtonController.h +src/frontends/controllers/character.C src/frontends/controllers/ControlAboutlyx.C src/frontends/controllers/ControlBibtex.C src/frontends/controllers/ControlCharacter.C @@ -29,13 +27,13 @@ src/frontends/controllers/ControlSearch.C src/frontends/controllers/ControlSpellchecker.C src/frontends/controllers/ControlThesaurus.C src/frontends/controllers/ControlVCLog.C -src/frontends/controllers/biblio.C -src/frontends/controllers/character.C src/frontends/controllers/frnt_lang.C src/frontends/controllers/helper_funcs.C src/frontends/gnome/GLog.C +src/frontends/LyXView.C src/frontends/qt2/Alert_pimpl.C src/frontends/qt2/FileDialog.C +src/frontends/qt2/lengthcombo.C src/frontends/qt2/QAbout.C src/frontends/qt2/QBibitem.C src/frontends/qt2/QBibtex.C @@ -47,8 +45,8 @@ src/frontends/qt2/QCommandBuffer.C src/frontends/qt2/QDelimiterDialog.C src/frontends/qt2/QDocument.C src/frontends/qt2/QDocumentDialog.C -src/frontends/qt2/QERT.C src/frontends/qt2/QError.C +src/frontends/qt2/QERT.C src/frontends/qt2/QExternal.C src/frontends/qt2/QExternalDialog.C src/frontends/qt2/QFloat.C @@ -56,8 +54,8 @@ src/frontends/qt2/QGraphics.C src/frontends/qt2/QGraphicsDialog.C src/frontends/qt2/QInclude.C src/frontends/qt2/QIndex.C -src/frontends/qt2/QLPrintDialog.C src/frontends/qt2/QLog.C +src/frontends/qt2/QLPrintDialog.C src/frontends/qt2/QMathDialog.C src/frontends/qt2/QMathMatrixDialog.C src/frontends/qt2/QMinipage.C @@ -75,23 +73,24 @@ src/frontends/qt2/QTabularCreate.C src/frontends/qt2/QTexinfo.C src/frontends/qt2/QThesaurus.C src/frontends/qt2/QToc.C +src/frontends/qt2/QtView.C src/frontends/qt2/QURL.C src/frontends/qt2/QVCLog.C src/frontends/qt2/QWrap.C -src/frontends/qt2/QtView.C -src/frontends/qt2/lengthcombo.C src/frontends/xforms/Alert_pimpl.C src/frontends/xforms/ColorHandler.C +src/frontends/xforms/combox.C src/frontends/xforms/FileDialog.C src/frontends/xforms/FormAboutlyx.C src/frontends/xforms/FormBase.C src/frontends/xforms/FormBibitem.C src/frontends/xforms/FormBibtex.C +src/frontends/xforms/FormChanges.C src/frontends/xforms/FormCharacter.C src/frontends/xforms/FormCitation.C src/frontends/xforms/FormDocument.C -src/frontends/xforms/FormERT.C src/frontends/xforms/FormError.C +src/frontends/xforms/FormERT.C src/frontends/xforms/FormExternal.C src/frontends/xforms/FormFiledialog.C src/frontends/xforms/FormFloat.C @@ -124,15 +123,14 @@ src/frontends/xforms/FormToc.C src/frontends/xforms/FormUrl.C src/frontends/xforms/FormVCLog.C src/frontends/xforms/FormWrap.C -src/frontends/xforms/Menubar_pimpl.C -src/frontends/xforms/XMiniBuffer.C -src/frontends/xforms/combox.C src/frontends/xforms/input_validators.C +src/frontends/xforms/Menubar_pimpl.C src/frontends/xforms/xforms_helpers.C +src/frontends/xforms/XMiniBuffer.C src/gettext.h src/importer.C -src/insets/inset.C src/insets/insetbib.C +src/insets/inset.C src/insets/insetcaption.C src/insets/inseterror.C src/insets/insetert.C @@ -159,12 +157,15 @@ src/insets/inseturl.C src/insets/insetwrap.C src/kbsequence.C src/language.C +src/LaTeX.C +src/LColor.C src/lengthcommon.C +src/LyXAction.C src/lyx_cb.C -src/lyx_main.C src/lyxfind.C src/lyxfont.C src/lyxfunc.C +src/lyx_main.C src/lyxrc.C src/lyxtextclasslist.C src/lyxvc.C @@ -173,9 +174,11 @@ src/mathed/formulamacro.C src/mathed/math_hullinset.C src/mathed/math_parboxinset.C src/mathed/ref_inset.C +src/MenuBackend.C src/paragraph.C src/support/filetools.C src/tabular.C -src/text.C src/text2.C src/text3.C +src/text.C +src/ext_l10n.h diff --git a/src/BufferView.C b/src/BufferView.C index 95c03e8450..7e9eec803e 100644 --- a/src/BufferView.C +++ b/src/BufferView.C @@ -29,6 +29,7 @@ #include "lyxlex.h" #include "lyxtext.h" #include "undo_funcs.h" +#include "changes.h" #include "frontends/Alert.h" #include "frontends/Dialogs.h" @@ -154,6 +155,12 @@ bool BufferView::available() const } +Change const BufferView::getCurrentChange() +{ + return pimpl_->getCurrentChange(); +} + + void BufferView::beforeChange(LyXText * text) { pimpl_->beforeChange(text); @@ -664,7 +671,7 @@ void BufferView::replaceWord(string const & replacestring) toggleSelection(false); tt->replaceSelectionWithString(this, replacestring); - tt->setSelectionOverString(this, replacestring); + tt->setSelectionRange(this, replacestring.length()); // Go back so that replacement string is also spellchecked for (string::size_type i = 0; i < replacestring.length() + 1; ++i) { diff --git a/src/BufferView.h b/src/BufferView.h index 5a1b44b3a4..e4da73d12e 100644 --- a/src/BufferView.h +++ b/src/BufferView.h @@ -21,6 +21,7 @@ #include +class Change; class LyXView; class LyXText; class TeXErrors; @@ -107,6 +108,9 @@ public: void restorePosition(unsigned int i); /// does the given bookmark have a saved position ? bool isSavedPosition(unsigned int i); + + /// return the current change at the cursor + Change const getCurrentChange(); /** * This holds the mapping between buffer paragraphs and screen rows. diff --git a/src/BufferView_pimpl.C b/src/BufferView_pimpl.C index ab27294eca..0178c990b9 100644 --- a/src/BufferView_pimpl.C +++ b/src/BufferView_pimpl.C @@ -41,6 +41,8 @@ #include "ParagraphParameters.h" #include "undo_funcs.h" #include "funcrequest.h" +#include "iterators.h" +#include "lyxfind.h" #include "insets/insetbib.h" #include "insets/insettext.h" @@ -624,6 +626,21 @@ bool BufferView::Pimpl::available() const } +Change const BufferView::Pimpl::getCurrentChange() +{ + if (!bv_->buffer()->params.tracking_changes) + return Change(Change::UNCHANGED); + + LyXText * t(bv_->getLyXText()); + + if (!t->selection.set()) + return Change(Change::UNCHANGED); + + LyXCursor const & cur(t->selection.start); + return cur.par()->lookupChangeFull(cur.pos()); +} + + void BufferView::Pimpl::beforeChange(LyXText * text) { toggleSelection(); @@ -915,6 +932,43 @@ void BufferView::Pimpl::MenuInsertLyXFile(string const & filen) } +void BufferView::Pimpl::trackChanges() +{ + Buffer * buf(bv_->buffer()); + bool const tracking(buf->params.tracking_changes); + + if (!tracking) { + ParIterator const end = buf->par_iterator_end(); + for (ParIterator it = buf->par_iterator_begin(); it != end; ++it) { + (*it)->trackChanges(); + } + buf->params.tracking_changes = true; + + // we cannot allow undos beyond the freeze point + buf->undostack.clear(); + } else { + bv_->update(bv_->text, BufferView::SELECT | BufferView::FITCUR); + bv_->text->setCursor(bv_, &(*buf->paragraphs.begin()), 0); +#warning changes FIXME + //moveCursorUpdate(false); + + bool found = lyxfind::findNextChange(bv_); + if (found) { + owner_->getDialogs().showMergeChanges(); + return; + } + + ParIterator const end = buf->par_iterator_end(); + for (ParIterator it = buf->par_iterator_begin(); it != end; ++it) { + (*it)->untrackChanges(); + } + buf->params.tracking_changes = false; + } + + buf->redostack.clear(); +} + + bool BufferView::Pimpl::dispatch(FuncRequest const & ev) { lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:" @@ -1249,6 +1303,56 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & ev) } break; + case LFUN_TRACK_CHANGES: + trackChanges(); + break; + + case LFUN_MERGE_CHANGES: + owner_->getDialogs().showMergeChanges(); + break; + + case LFUN_ACCEPT_ALL_CHANGES: { + bv_->update(bv_->text, BufferView::SELECT | BufferView::FITCUR); + bv_->text->setCursor(bv_, &(*bv_->buffer()->paragraphs.begin()), 0); +#warning FIXME changes + //moveCursorUpdate(false); + + while (lyxfind::findNextChange(bv_)) { + bv_->getLyXText()->acceptChange(bv_); + } + update(bv_->text, + BufferView::SELECT | BufferView::FITCUR | BufferView::CHANGE); + break; + } + + case LFUN_REJECT_ALL_CHANGES: { + bv_->update(bv_->text, BufferView::SELECT | BufferView::FITCUR); + bv_->text->setCursor(bv_, &(*bv_->buffer()->paragraphs.begin()), 0); +#warning FIXME changes + //moveCursorUpdate(false); + + while (lyxfind::findNextChange(bv_)) { + bv_->getLyXText()->rejectChange(bv_); + } + update(bv_->text, + BufferView::SELECT | BufferView::FITCUR | BufferView::CHANGE); + break; + } + + case LFUN_ACCEPT_CHANGE: { + bv_->getLyXText()->acceptChange(bv_); + update(bv_->text, + BufferView::SELECT | BufferView::FITCUR | BufferView::CHANGE); + break; + } + + case LFUN_REJECT_CHANGE: { + bv_->getLyXText()->rejectChange(bv_); + update(bv_->text, + BufferView::SELECT | BufferView::FITCUR | BufferView::CHANGE); + break; + } + case LFUN_UNKNOWN_ACTION: ev.errorMessage(N_("Unknown function!")); break; diff --git a/src/BufferView_pimpl.h b/src/BufferView_pimpl.h index 9a77eba226..ed39aa1cc3 100644 --- a/src/BufferView_pimpl.h +++ b/src/BufferView_pimpl.h @@ -23,6 +23,7 @@ #pragma interface #endif +class Change; class LyXView; class WorkArea; class LyXScreen; @@ -74,6 +75,8 @@ struct BufferView::Pimpl : public boost::signals::trackable { void cursorToggle(); /// bool available() const; + /// get the change at the cursor position + Change const getCurrentChange(); /// void beforeChange(LyXText *); /// @@ -103,6 +106,9 @@ struct BufferView::Pimpl : public boost::signals::trackable { /// bool dispatch(FuncRequest const & ev); private: + /// track changes for the document + void trackChanges(); + /// friend class BufferView; diff --git a/src/ChangeLog b/src/ChangeLog index a0f9d3d831..6c54f2a6e9 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,95 @@ +2003-02-08 John Levon + + * lyxfind.C: + * lyxtext.h: + * text2.C: + * BufferView.C: change setSelectionOverString() to setSelectionRange() + and pass the size in explicitly + + * BufferView_pimpl.h: + * BufferView_pimpl.C: + * BufferView.h: + * BufferView.C: add getCurrentChange() + + * BufferView_pimpl.h: + * BufferView_pimpl.C: handle change lfuns + + * CutAndPaste.C: merge the cut and copy code. Rework the cut code + for changes. Mark pasted paragraphs as new. + + * support/lyxtime.h: + * support/lyxtime.C: + * DepTable.C: abstract time_t as lyx::time_type + + * LColor.h: + * LColor.C: add colours for new text, deleted text, changebars + + * LaTeXFeatures.C: add dvipost as a simple feature. Make the color + package use "usenames" option. + + * commandtags.h: + * lyxfunc.C: + * LyXAction.C: add change lfuns + + * Makefile.am: + * author.h: + * author.C: author handling + + * buffer.h: + * buffer.C: add a per-buffer author list, with first entry as + current author. Handle new .lyx tokens for change tracking. Output + author list to .lyx file. Output dvipost stuff to .tex preamble. + Bump lyx format to 222. + + * bufferlist.h: + * bufferlist.C: add setCurrentAuthor() to reset current author details + in all buffers. + + * bufferparams.h: + * bufferparams.C: add param for tracking + + * bufferview_funcs.C: output change info in minibuffer + + * Makefile.am: + * changes.h: + * changes.C: add change-tracking structure + + * debug.h: + * debug.C: add CHANGES debug flag + + * lyxfind.h: + * lyxfind.C: add code for finding the next change piece + + * lyxrc.h: + * lyxrc.C: add user_name and user_email + + * lyxrow.h: + * lyxrow.C: add a metric for the top of the text line + + * lyxtext.h: + * text.C: implement accept/rejectChange() + + * lyxtext.h: + * text.C: paint changebars. Paint new/deleted text in the chosen colours. + Strike through deleted text. + + * paragraph.h: + * paragraph.C: + * paragraph_pimpl.h: + * paragraph_pimpl.C: output change markers in .lyx and .tex. Pass in the current change + to the insert functions. Rework erase to mark text as deleted, adding + an eraseIntern() and a range-based erase(). Implement + per-paragraph change lookup and accept/reject. + + * paragraph_funcs.C: Fixup paste for change tracking. + + * tabular.C: mark added row/columns as new. + + * text.C: fix rowLast() to never return -1. Don't allow spellchecking of deleted + text. Track transpose changes. Don't allow paragraph break or merge where appropriate. + + * text2.C: leave cursor at end of selection after a cut. + 2003-02-07 Jean-Marc Lasgouttes * text.C (getLengthMarkerHeight): diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index 5dca9f0cc4..0f3a66c47a 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -14,7 +14,6 @@ #endif #include "CutAndPaste.h" -//#include "debug.h" #include "BufferView.h" #include "buffer.h" #include "paragraph.h" @@ -26,11 +25,13 @@ #include "lyxtextclasslist.h" #include "undo_funcs.h" #include "paragraph_funcs.h" +#include "debug.h" #include "insets/inseterror.h" #include "BoostFormat.h" +using std::endl; using std::pair; using lyx::pos_type; using lyx::textclass_type; @@ -58,6 +59,7 @@ extern BufferView * current_view; namespace { +// FIXME: stupid name Paragraph * buf = 0; textclass_type textclass = 0; @@ -90,81 +92,93 @@ bool CutAndPaste::cutSelection(Paragraph * startpar, Paragraph ** endpar, if (!startpar || (start > startpar->size())) return false; - if (realcut) - DeleteBuffer(); - - textclass = tc; - - if (!(*endpar) || startpar == (*endpar)) { - // only within one paragraph - if (realcut) { - buf = new Paragraph; - buf->layout(startpar->layout()); - } - pos_type i = start; - if (end > startpar->size()) - end = startpar->size(); - for (; i < end; ++i) { - if (realcut) - startpar->copyIntoMinibuffer(*current_view->buffer(), - start); - startpar->erase(start); - if (realcut) - buf->insertFromMinibuffer(buf->size()); - } - end = start - 1; - } else { - // more than one paragraph - breakParagraphConservative(current_view->buffer()->params, - *endpar, - end); - *endpar = (*endpar)->next(); - end = 0; - - breakParagraphConservative(current_view->buffer()->params, - startpar, - start); - - // store the selection - if (realcut) { - buf = startpar->next(); - buf->previous(0); - } else { - startpar->next()->previous(0); - } - (*endpar)->previous()->next(0); - - // cut the selection - startpar->next(*endpar); - - (*endpar)->previous(startpar); - - // the cut selection should begin with standard layout - if (realcut) { - buf->params().clear(); - buf->bibkey = 0; - buf->layout(current_view->buffer()->params.getLyXTextClass().defaultLayout()); - } - - // paste the paragraphs again, if possible - if (doclear) - startpar->next()->stripLeadingSpaces(); - if (startpar->hasSameLayout(startpar->next()) || - startpar->next()->empty()) { - mergeParagraph(current_view->buffer()->params, startpar); - (*endpar) = startpar; // this because endpar gets deleted here! - } - // this paragraph's are of noone's owner! - Paragraph * p = buf; - while (p) { - p->setInsetOwner(0); - p = p->next(); - } + if (realcut) { + copySelection(startpar, *endpar, start, end, tc); } + + if (!endpar || startpar == *endpar) { + if (startpar->erase(start, end)) { + // Some chars were erased, go to start to be safe + end = start; + } + return true; + } + + bool actually_erased = false; + + // clear end/begin fragments of the first/last par in selection + actually_erased |= (startpar)->erase(start, startpar->size()); + if ((*endpar)->erase(0, end)) { + actually_erased = true; + end = 0; + } + + // Loop through the deleted pars if any, erasing as needed + + Paragraph * pit = startpar->next(); + + while (1) { + // *endpar can be 0 + if (!pit) + break; + + Paragraph * next = pit->next(); + + // "erase" the contents of the par + if (pit != *endpar) { + actually_erased |= pit->erase(0, pit->size()); + + // remove the par if it's now empty + if (actually_erased) { + pit->previous()->next(pit->next()); + if (next) { + next->previous(pit->previous()); + } + lyxerr << "deleting pit " << pit << endl; + + delete pit; + } + } + + if (pit == *endpar) + break; + + pit = next; + } + +#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 (!startpar->next()) + return true; + + Buffer * buffer = current_view->buffer(); + + if (doclear) { + startpar->next()->stripLeadingSpaces(); + } + + if (!actually_erased) + return true; + + // paste the paragraphs again, if possible + if (startpar->hasSameLayout(startpar->next()) || + startpar->next()->empty()) { + mergeParagraph(buffer->params, startpar); + // this because endpar gets deleted here! + (*endpar) = startpar; + } + return true; } - + bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar, int start, int end, char tc) { @@ -192,6 +206,7 @@ bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar, Paragraph * tmppar = startpar; buf = new Paragraph(*tmppar, false); Paragraph * tmppar2 = buf; + tmppar2->cleanChanges(); while (tmppar != endpar && tmppar->next()) { @@ -199,6 +214,8 @@ bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar, tmppar2->next(new Paragraph(*tmppar, false)); tmppar2->next()->previous(tmppar2); tmppar2 = tmppar2->next(); + // reset change info + tmppar2->cleanChanges(); } tmppar2->next(0); @@ -349,9 +366,7 @@ bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, // if necessary if (((*par)->size() > pos) || !(*par)->next()) { breakParagraphConservative( - current_view->buffer()->params, - *par, - pos); + current_view->buffer()->params, *par, pos); paste_the_end = true; } // set the end for redoing later @@ -375,10 +390,10 @@ bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, if (lastbuffer->next() && paste_the_end) { if (lastbuffer->next()->hasSameLayout(lastbuffer)) { mergeParagraph(current_view->buffer()->params, lastbuffer); - } else if (lastbuffer->next()->empty()) { + } else if (!lastbuffer->next()->size()) { lastbuffer->next()->makeSameLayout(lastbuffer); mergeParagraph(current_view->buffer()->params, lastbuffer); - } else if (lastbuffer->empty()) { + } else if (!lastbuffer->size()) { lastbuffer->makeSameLayout(lastbuffer->next()); mergeParagraph(current_view->buffer()->params, lastbuffer); } else diff --git a/src/CutAndPaste.h b/src/CutAndPaste.h index 88a275fd37..07a0dc910f 100644 --- a/src/CutAndPaste.h +++ b/src/CutAndPaste.h @@ -24,7 +24,7 @@ class LyXTextClass; /// class CutAndPaste { public: - /// + /// realcut == false is we actually want a delete static bool cutSelection(Paragraph * startpar, Paragraph ** endpar, int start, int & end, char tc, bool doclear = false, diff --git a/src/DepTable.C b/src/DepTable.C index c148a7e865..8e0997f8ce 100644 --- a/src/DepTable.C +++ b/src/DepTable.C @@ -24,6 +24,7 @@ #include "support/lyxlib.h" #include "support/filetools.h" #include "support/lstrings.h" +#include "support/lyxtime.h" #include #include @@ -79,7 +80,7 @@ void DepTable::insert(string const & fi, bool upd) void DepTable::update() { lyxerr[Debug::DEPEND] << "Updating DepTable..." << endl; - time_t start_time = time(0); + lyx::time_type const start_time = lyx::current_time(); DepList::iterator itr = deplist.begin(); while (itr != deplist.end()) { @@ -114,7 +115,7 @@ void DepTable::update() } ++itr; } - time_t time_sec = time(0) - start_time; + lyx::time_type const time_sec = lyx::current_time() - start_time; lyxerr[Debug::DEPEND] << "Finished updating DepTable (" << time_sec << " sec)." << endl; } diff --git a/src/LColor.C b/src/LColor.C index 6420afde2e..58c3cf447c 100644 --- a/src/LColor.C +++ b/src/LColor.C @@ -84,6 +84,9 @@ LColor::LColor() { error, N_("LaTeX error"), "error", "Red", "error" }, { eolmarker, N_("end-of-line marker"), "eolmarker", "Brown", "eolmarker" }, { appendixline, N_("appendix line"), "appendixline", "Brown", "appendixline" }, + { changebar, N_("change bar"), "changebar", "Blue", "changebar" }, + { strikeout, N_("Deleted text"), "strikeout", "Red", "strikeout" }, + { newtext, N_("Added text"), "newtext", "Blue", "newtext" }, { added_space, N_("added space markers"), "added_space", "Brown", "added_space" }, { topline, N_("top/bottom line"), "topline", "Brown", "topline" }, { tabularline, N_("tabular line"), "tabularline", "black", diff --git a/src/LColor.h b/src/LColor.h index da75feef92..3c7ea8e6ec 100644 --- a/src/LColor.h +++ b/src/LColor.h @@ -137,6 +137,12 @@ public: added_space, /// Appendix line color appendixline, + /// changebar color + changebar, + /// strike-out color + strikeout, + /// added text color + newtext, /// Top and bottom line color topline, /// Table line color diff --git a/src/LaTeXFeatures.C b/src/LaTeXFeatures.C index b91ee8cbdb..bed69c6c1e 100644 --- a/src/LaTeXFeatures.C +++ b/src/LaTeXFeatures.C @@ -174,10 +174,11 @@ char const * simplefeatures[] = { "varioref", "prettyref", "float", - "wasy" + "wasy", + "dvipost" }; -const int nb_simplefeatures = sizeof(simplefeatures) / sizeof(char const *); +int const nb_simplefeatures = sizeof(simplefeatures) / sizeof(char const *); } @@ -210,10 +211,11 @@ string const LaTeXFeatures::getPackages() const // color.sty if (isRequired("color")) { if (params.graphicsDriver == "default") - packages << "\\usepackage{color}\n"; + packages << "\\usepackage[usenames]{color}\n"; else packages << "\\usepackage[" << params.graphicsDriver + << ",usenames" << "]{color}\n"; } diff --git a/src/LyXAction.C b/src/LyXAction.C index a2304e7018..15fdc2d2ea 100644 --- a/src/LyXAction.C +++ b/src/LyXAction.C @@ -413,6 +413,12 @@ void LyXAction::init() { LFUN_FORKS_KILL, "kill-forks", N_("Kill the forked process with this PID"), NoBuffer }, { LFUN_TOOLTIPS_TOGGLE, "toggle-tooltips", "", NoBuffer }, + { LFUN_TRACK_CHANGES, "track-changes", N_("Begin tracking changes"), Noop }, + { LFUN_MERGE_CHANGES, "merge-changes", N_("Merge changes"), Noop }, + { LFUN_ACCEPT_CHANGE, "accept-change", N_("Accept selected change"), Noop }, + { LFUN_REJECT_CHANGE, "reject-change", N_("Reject selected change"), Noop }, + { LFUN_ACCEPT_ALL_CHANGES, "accept-all-changes", N_("Accept all changes"), Noop }, + { LFUN_REJECT_ALL_CHANGES, "reject-all-changes", N_("Reject all changes"), Noop }, { LFUN_NOACTION, "", "", Noop } }; diff --git a/src/Makefile.am b/src/Makefile.am index 5318e78ccd..da66e4a807 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -87,6 +87,8 @@ lyx_SOURCES = \ ToolbarDefaults.C \ ToolbarDefaults.h \ WordLangTuple.h \ + author.C \ + author.h \ boost.C \ boost-inst.C \ box.h \ @@ -100,6 +102,8 @@ lyx_SOURCES = \ bufferparams.h \ bufferview_funcs.C \ bufferview_funcs.h \ + changes.C \ + changes.h \ chset.C \ chset.h \ commandtags.h \ diff --git a/src/author.C b/src/author.C new file mode 100644 index 0000000000..2cbd6986fe --- /dev/null +++ b/src/author.C @@ -0,0 +1,94 @@ +/** + * \file author.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#include + +#include "author.h" + +#include "debug.h" + +#include "support/LAssert.h" +#include "support/LOstream.h" +#include "support/LIstream.h" +#include "support/lstrings.h" + +using std::endl; + +namespace { + int cur_id; +} + + +bool operator==(Author const & l, Author const & r) +{ + return l.name() == r.name() && l.email() == r.email(); +} + + +std::ostream & operator<<(std::ostream & os, Author const & a) +{ + os << "\"" << a.name() << "\" " << a.email(); + return os; +} + +std::istream & operator>>(std::istream & is, Author & a) +{ + string s; + getline(is, s); + a.name_ = trim(token(s, '\"', 1)); + a.email_ = trim(token(s, '\"', 2)); + lyxerr << "Read name " << a.name_ << " email " << a.email_ << endl; + return is; +} + + +int AuthorList::record(Author const & a) +{ + Authors::const_iterator it(authors_.begin()); + Authors::const_iterator itend(authors_.end()); + + for (; it != itend; ++it) { + if (it->second == a) + return it->first; + } + + lyxerr[Debug::CHANGES] << "Adding author " << a << endl; + + authors_[cur_id++] = a; + return cur_id - 1; +} + + +void AuthorList::record(int id, Author const & a) +{ + lyx::Assert(id < authors_.size()); + + authors_[id] = a; +} + + +Author const & AuthorList::get(int id) +{ + Authors::const_iterator it(authors_.find(id)); + lyx::Assert(it != authors_.end()); + return it->second; +} + + +AuthorList::Authors::const_iterator AuthorList::begin() const +{ + return authors_.begin(); +} + + +AuthorList::Authors::const_iterator AuthorList::end() const +{ + return authors_.end(); +} diff --git a/src/author.h b/src/author.h new file mode 100644 index 0000000000..d362d7f9d5 --- /dev/null +++ b/src/author.h @@ -0,0 +1,67 @@ +/** + * \file author.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#ifndef AUTHOR_H +#define AUTHOR_H + +#include +#include + +#include "LString.h" + +class Author { +public: + Author() {} + + Author(string n, string e) + : name_(n), email_(e) {} + + string const name() const { + return name_; + } + + string const email() const { + return email_; + } + + friend std::istream & operator>>(std::istream & os, Author & a); + +private: + string name_; + + string email_; +}; + + +class AuthorList { +public: + int record(Author const & a); + + void record(int id, Author const & a); + + Author const & get(int id); + + typedef std::map Authors; + + Authors::const_iterator begin() const; + + Authors::const_iterator end() const; + +private: + Authors authors_; +}; + +bool operator==(Author const & l, Author const & r); + +std::ostream & operator<<(std::ostream & os, Author const & a); + +std::istream & operator>>(std::istream & os, Author & a); + +#endif // AUTHOR_H diff --git a/src/buffer.C b/src/buffer.C index 14fd2a462b..60ec905f47 100644 --- a/src/buffer.C +++ b/src/buffer.C @@ -45,6 +45,7 @@ #include "lyxtextclasslist.h" #include "sgml.h" #include "paragraph_funcs.h" +#include "author.h" #include "frontends/LyXView.h" @@ -97,6 +98,7 @@ #include "support/FileInfo.h" #include "support/lyxmanip.h" #include "support/lyxalgo.h" // for lyx::count +#include "support/lyxtime.h" #include #include @@ -148,7 +150,7 @@ extern BufferList bufferlist; namespace { -const int LYX_FORMAT = 221; +const int LYX_FORMAT = 222; } // namespace anon @@ -165,6 +167,9 @@ Buffer::Buffer(string const & file, bool ronly) } else { tmppath.erase(); } + + // set initial author + authorlist.record(Author(lyxrc.user_name, lyxrc.user_email)); } @@ -245,6 +250,12 @@ void Buffer::setReadonly(bool flag) } +AuthorList & Buffer::authors() +{ + return authorlist; +} + + /// Update window titles of all users // Should work on a list void Buffer::updateTitles() const @@ -282,6 +293,7 @@ string last_inset_read; #endif int unknown_layouts; int unknown_tokens; +vector author_ids; } // anon @@ -298,6 +310,7 @@ bool Buffer::readLyXformat2(LyXLex & lex, Paragraph * par) { unknown_layouts = 0; unknown_tokens = 0; + author_ids.clear(); int pos = 0; Paragraph::depth_type depth = 0; @@ -390,6 +403,13 @@ bool Buffer::readLyXformat2(LyXLex & lex, Paragraph * par) } +namespace { + // This stuff is, in the traditional LyX terminology, Super UGLY + // but this code is too b0rken to admit of a better solution yet + Change current_change; +}; + + bool Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, Paragraph *& first_par, @@ -408,7 +428,7 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, if (token[0] != '\\') { for (string::const_iterator cit = token.begin(); cit != token.end(); ++cit) { - par->insertChar(pos, (*cit), font); + par->insertChar(pos, (*cit), font, current_change); ++pos; } } else if (token == "\\layout") { @@ -498,6 +518,8 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, else { par = new Paragraph(par); par->layout(params.getLyXTextClass().defaultLayout()); + if (params.tracking_changes) + par->trackChanges(); } pos = 0; par->layout(params.getLyXTextClass()[layoutname]); @@ -576,9 +598,9 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, lex.next(); string const next_token = lex.getString(); if (next_token == "\\-") { - par->insertChar(pos, '-', font); + par->insertChar(pos, '-', font, current_change); } else if (next_token == "~") { - par->insertChar(pos, ' ', font); + par->insertChar(pos, ' ', font, current_change); } else { lex.printError("Token `$$Token' " "is in free space " @@ -589,16 +611,16 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, } else { Inset * inset = new InsetSpecialChar; inset->read(this, lex); - par->insertInset(pos, inset, font); + par->insertInset(pos, inset, font, current_change); } ++pos; } else if (token == "\\i") { Inset * inset = new InsetLatexAccent; inset->read(this, lex); - par->insertInset(pos, inset, font); + par->insertInset(pos, inset, font, current_change); ++pos; } else if (token == "\\backslash") { - par->insertChar(pos, '\\', font); + par->insertChar(pos, '\\', font, current_change); ++pos; } else if (token == "\\begin_deeper") { ++depth; @@ -756,6 +778,21 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, } else if (token == "\\use_numerical_citations") { lex.nextToken(); params.use_numerical_citations = lex.getInteger(); + } else if (token == "\\tracking_changes") { + lex.nextToken(); + params.tracking_changes = lex.getInteger(); + // mark the first paragraph + if (params.tracking_changes) + par->trackChanges(); + } else if (token == "\\author") { + lex.nextToken(); + istringstream ss(lex.getString()); + Author a; + ss >> a; + int aid(authorlist.record(a)); + lyxerr << "aid is " << aid << endl; + lyxerr << "listed aid is " << author_ids.size() << endl; + author_ids.push_back(authorlist.record(a)); } else if (token == "\\paperorientation") { int tmpret = lex.findToken(string_orientation); if (tmpret == -1) @@ -929,16 +966,37 @@ Buffer::parseSingleLyXformat2Token(LyXLex & lex, Paragraph *& par, par->params().labelWidthString(lex.getString()); // do not delete this token, it is still needed! } else if (token == "\\newline") { - par->insertChar(pos, Paragraph::META_NEWLINE, font); + par->insertChar(pos, Paragraph::META_NEWLINE, font, current_change); ++pos; } else if (token == "\\LyXTable") { Inset * inset = new InsetTabular(*this); inset->read(this, lex); - par->insertInset(pos, inset, font); + par->insertInset(pos, inset, font, current_change); ++pos; } else if (token == "\\hfill") { - par->insertChar(pos, Paragraph::META_HFILL, font); + par->insertChar(pos, Paragraph::META_HFILL, font, current_change); ++pos; + } else if (token == "\\change_unchanged") { + // Hack ! Needed for empty paragraphs :/ + if (!pos) + par->cleanChanges(); + current_change = Change(Change::UNCHANGED); + } else if (token == "\\change_inserted") { + lex.nextToken(); + istringstream istr(lex.getString()); + int aid; + lyx::time_type ct; + istr >> aid; + istr >> ct; + current_change = Change(Change::INSERTED, author_ids[aid], ct); + } else if (token == "\\change_deleted") { + lex.nextToken(); + istringstream istr(lex.getString()); + int aid; + lyx::time_type ct; + istr >> aid; + istr >> ct; + current_change = Change(Change::DELETED, author_ids[aid], ct); } else if (token == "\\bibitem") { // ale970302 if (!par->bibkey) { InsetCommandParams p("bibitem", "dummy"); @@ -1005,13 +1063,13 @@ void Buffer::insertStringAsLines(Paragraph *& par, pos_type & pos, } else if (*cit == '\t') { if (!layout->free_spacing && !par->isFreeSpacing()) { // tabs are like spaces here - par->insertChar(pos, ' ', font); + par->insertChar(pos, ' ', font, current_change); ++pos; space_inserted = true; } else { const pos_type nb = 8 - pos % 8; for (pos_type a = 0; a < nb ; ++a) { - par->insertChar(pos, ' ', font); + par->insertChar(pos, ' ', font, current_change); ++pos; } space_inserted = true; @@ -1155,7 +1213,7 @@ void Buffer::readInset(LyXLex & lex, Paragraph *& par, } if (inset) { - par->insertInset(pos, inset, font); + par->insertInset(pos, inset, font, current_change); ++pos; } } @@ -1356,6 +1414,15 @@ bool Buffer::writeFile(string const & fname) const // now write out the buffer paramters. params.writeFile(ofs); + // if we're tracking, list all possible authors + if (params.tracking_changes) { + AuthorList::Authors::const_iterator it = authorlist.begin(); + AuthorList::Authors::const_iterator end = authorlist.end(); + for (; it != end; ++it) { + ofs << "\\author " << it->second << "\n"; + } + } + Paragraph::depth_type depth = 0; // this will write out all the paragraphs @@ -2096,6 +2163,15 @@ void Buffer::makeLaTeXFile(ostream & os, preamble += "\\makeatother\n"; + // dvipost settings come after everything else + if (params.tracking_changes) { + preamble += "\\dvipostlayout\n"; + preamble += "\\dvipost{osstart color push Red}\n"; + preamble += "\\dvipost{osend color pop}\n"; + preamble += "\\dvipost{cbstart color push Blue}\n"; + preamble += "\\dvipost{cbend color pop} \n"; + } + os << preamble; if (only_preamble) @@ -3113,6 +3189,11 @@ void Buffer::validate(LaTeXFeatures & features) const { LyXTextClass const & tclass = params.getLyXTextClass(); + if (params.tracking_changes) { + features.require("dvipost"); + features.require("color"); + } + // AMS Style is at document level if (params.use_amsmath || tclass.provides(LyXTextClass::amsmath)) features.require("amsmath"); diff --git a/src/buffer.h b/src/buffer.h index b3f1feeb0a..e3fc7d69a6 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -26,6 +26,7 @@ #include "texrow.h" #include "ParagraphList.h" #include "paragraph.h" +#include "author.h" #include @@ -302,7 +303,14 @@ public: /// Used when typesetting to place errorboxes. TexRow texrow; + + /// the author list for the document + AuthorList & authors(); + private: + /// the author list + AuthorList authorlist; + /// is save needed mutable bool lyx_clean; diff --git a/src/bufferlist.C b/src/bufferlist.C index 1056443336..caba200f85 100644 --- a/src/bufferlist.C +++ b/src/bufferlist.C @@ -569,3 +569,13 @@ Buffer * BufferList::loadLyXFile(string const & filename, bool tolastfiles) return b; } + + +void BufferList::setCurrentAuthor(string const & name, string const & email) +{ + BufferStorage::iterator it = bstore.begin(); + BufferStorage::iterator end = bstore.end(); + for (; it != end; ++it) { + (*it)->authors().record(0, Author(name, email)); + } +} diff --git a/src/bufferlist.h b/src/bufferlist.h index b24f813d20..f9fb6e3b35 100644 --- a/src/bufferlist.h +++ b/src/bufferlist.h @@ -160,6 +160,10 @@ public: Buffer * getBuffer(string const &); /// returns a pointer to the buffer with the given number. Buffer * getBuffer(unsigned int); + + /// reset current author for all buffers + void setCurrentAuthor(string const & name, string const & email); + private: /// ask to save a buffer on quit bool qwriteOne(Buffer * buf, string const & fname, diff --git a/src/bufferparams.C b/src/bufferparams.C index 3ccf77567b..60e458f1e0 100644 --- a/src/bufferparams.C +++ b/src/bufferparams.C @@ -54,6 +54,7 @@ BufferParams::BufferParams() use_amsmath = false; use_natbib = false; use_numerical_citations = false; + tracking_changes = false; secnumdepth = 3; tocdepth = 3; language = default_language; @@ -178,6 +179,8 @@ void BufferParams::writeFile(ostream & os) const } } } + + os << "\\tracking_changes " << tracking_changes << "\n"; } diff --git a/src/bufferparams.h b/src/bufferparams.h index a77fb04c3e..5baf569151 100644 --- a/src/bufferparams.h +++ b/src/bufferparams.h @@ -214,6 +214,8 @@ public: bool use_natbib; /// bool use_numerical_citations; + /// revision tracking for this buffer ? + bool tracking_changes; /// Time ago we agreed that this was a buffer property [ale990407] string parentname; private: diff --git a/src/bufferview_funcs.C b/src/bufferview_funcs.C index 17b6893557..35cbf78e85 100644 --- a/src/bufferview_funcs.C +++ b/src/bufferview_funcs.C @@ -25,6 +25,8 @@ #include "language.h" #include "gettext.h" #include "ParagraphParameters.h" +#include "author.h" +#include "changes.h" #include "frontends/Alert.h" @@ -148,12 +150,30 @@ string const currentState(BufferView * bv) ostringstream state; if (!bv->available()) - return ""; + return string(); - // I think we should only show changes from the default - // font. (Asger) LyXText * text = bv->getLyXText(); Buffer * buffer = bv->buffer(); + LyXCursor const & c(text->cursor); + + bool const show_change = buffer->params.tracking_changes + && c.pos() != c.par()->size() + && c.par()->lookupChange(c.pos()) != Change::UNCHANGED; + + if (show_change) { + Change change(c.par()->lookupChangeFull(c.pos())); + Author const & a(bv->buffer()->authors().get(change.author)); + state << _("Change: ") << a.name(); + if (!a.email().empty()) { + state << " (" << a.email() << ")"; + } + if (change.changetime) + state << _(" at ") << ctime(&change.changetime); + state << " : "; + } + + // I think we should only show changes from the default + // font. (Asger) LyXFont font = text->real_current_font; LyXFont const & defaultfont = buffer->params.getLyXTextClass().defaultfont(); diff --git a/src/changes.C b/src/changes.C new file mode 100644 index 0000000000..3f827db07d --- /dev/null +++ b/src/changes.C @@ -0,0 +1,526 @@ +/** + * \file changes.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * Record changes in a paragraph. + * + * \author John Levon + */ + +#include + +#include "changes.h" +#include "debug.h" +#include "author.h" + +#include "support/LAssert.h" +#include "support/LOstream.h" + +using std::vector; +using std::endl; +using lyx::pos_type; + +bool operator==(Change const & l, Change const & r) +{ + return l.type == r.type && l.author == r.author + && l.changetime == r.changetime; +} + + +bool operator!=(Change const & l, Change const & r) +{ + return !(l == r); +} + + +bool operator==(Changes::Range const & r1, Changes::Range const & r2) +{ + return r1.start == r2.start && r1.end == r2.end; +} + + +bool operator!=(Changes::Range const & r1, Changes::Range const & r2) +{ + return !(r1 == r2); +} + + +bool Changes::Range::contains(Range const & r) const +{ + return r.start >= start && r.end <= end; +} + + +bool Changes::Range::contained(Range const & r) const +{ + return r.contains(*this); +} + + +bool Changes::Range::contains(pos_type pos) const +{ + return pos >= start && pos < end; +} + + +bool Changes::Range::loose_contains(pos_type pos) const +{ + return pos >= start && pos <= end; +} + + +bool Changes::Range::intersects(Range const & r) const +{ + return contained(r) || contains(r) + || contains(r.start) || contains(r.end); +} + + +Changes::Changes(Change::Type type) + : empty_type_(type) +{ +} + + +Changes::~Changes() +{ +} + + +Changes::Changes(Changes const & c) +{ + table_ = c.table_; +} + + +void Changes::record(Change change, pos_type pos) +{ + lyxerr[Debug::CHANGES] << "record " << change.type + << " at pos " << pos << " with total " + << table_.size() << " changes." << endl; + + switch (change.type) { + case Change::INSERTED: + add(change, pos); + break; + case Change::DELETED: + del(change, pos); + break; + case Change::UNCHANGED: + set(Change::UNCHANGED, pos); + break; + } +} + + +void Changes::set(Change change, pos_type pos) +{ + set(change, pos, pos + 1); +} + + +void Changes::set(Change::Type type, pos_type pos) +{ + set(type, pos, pos + 1); +} + + +void Changes::set(Change::Type type, pos_type start, pos_type end) +{ + set(Change(type), start, end); +} + + +void Changes::set(Change change, pos_type start, pos_type end) +{ + ChangeTable::iterator it = table_.begin(); + + lyxerr[Debug::CHANGES] << "changeset of " << change.type + << " author " << change.author << " time " << change.changetime + << " in range " << start << "," << end << endl; + + Range const new_range(start, end); + + // remove all sub-ranges + for (; it != table_.end();) { + if (new_range != it->range && it->range.contained(new_range)) { + lyxerr[Debug::CHANGES] << "Removing subrange " + << it->range.start << "," << it->range.end << endl; + it = table_.erase(it); + } else { + ++it; + } + } + + it = table_.begin(); + ChangeTable::iterator itend = table_.end(); + + // find a super-range + for (; it != itend; ++it) { + if (it->range.contains(new_range)) + break; + } + + if (it == itend) { + lyxerr[Debug::CHANGES] << "Inserting change at end" << endl; + table_.push_back(ChangeRange(start, end, change)); + merge(); + return; + } + + if (change.type == it->change.type) { + lyxerr[Debug::CHANGES] << "Change set already." << endl; + it->change = change; + return; + } + + ChangeRange c(*it); + + lyxerr[Debug::CHANGES] << "Using change of type " << c.change.type + << " over " << c.range.start << "," << c.range.end << endl; + + // split head + if (c.range.start < start) { + it = table_.insert(it, ChangeRange(c.range.start, start, c.change)); + lyxerr[Debug::CHANGES] << "Splitting head of type " << c.change.type + << " over " << c.range.start << "," << start << endl; + ++it; + } + + // reset this as new type + it->range.start = start; + it->range.end = end; + it->change = change; + lyxerr[Debug::CHANGES] << "Resetting to new change" << endl; + + // split tail + if (c.range.end > end) { + ++it; + table_.insert(it, ChangeRange(end, c.range.end, c.change)); + lyxerr[Debug::CHANGES] << "Splitting tail of type " << c.change.type + << " over " << end << "," << c.range.end << endl; + } + + check(); + merge(); +} + + +void Changes::erase(pos_type pos) +{ + ChangeTable::iterator it = table_.begin(); + ChangeTable::iterator end = table_.end(); + + bool found = false; + + for (; it != end; ++it) { + Range & range(it->range); + + lyxerr[Debug::CHANGES] << "era:Range of type " << it->change.type << " is " + << it->range.start << "," << it->range.end << endl; + + if (range.contains(pos)) { + found = true; + --range.end; + continue; + } + + if (found) { + --range.start; + --range.end; + } + } + check(); + merge(); +} + + +void Changes::del(Change change, ChangeTable::size_type pos) +{ + // this case happens when building from .lyx + if (table_.empty()) { + set(change, pos); + return; + } + + ChangeTable::iterator it = table_.begin(); + + for (; it != table_.end(); ++it) { + Range & range(it->range); + + if (range.contains(pos)) { + if (it->change.type != Change::INSERTED) { + set(change, pos); + } else { + erase(pos); + } + break; + } else if (range.loose_contains(pos) && it + 1 == table_.end()) { + // this case happens when building from .lyx + set(change, pos); + break; + } + } +} + + +void Changes::add(Change change, ChangeTable::size_type pos) +{ + ChangeTable::iterator it = table_.begin(); + ChangeTable::iterator end = table_.end(); + + bool found = false; + + for (; it != end; ++it) { + Range & range(it->range); + + if (!found && range.loose_contains(pos)) { + found = true; + lyxerr[Debug::CHANGES] << "Found range of " + << range.start << "," << range.end << endl; + ++range.end; + continue; + } + + if (found) { + ++range.start; + ++range.end; + } + } + set(change, pos); +} + + +Change const Changes::lookupFull(pos_type pos) const +{ + if (!table_.size()) { + lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl; + return Change(empty_type_); + } + + ChangeTable::const_iterator it = table_.begin(); + ChangeTable::const_iterator end = table_.end(); + + for (; it != end; ++it) { + if (it->range.contains(pos)) + return it->change; + } + + check(); + lyx::Assert(0); + return Change(Change::UNCHANGED); +} + + +Change::Type Changes::lookup(pos_type pos) const +{ + if (!table_.size()) { + lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl; + return empty_type_; + } + + ChangeTable::const_iterator it = table_.begin(); + ChangeTable::const_iterator end = table_.end(); + + for (; it != end; ++it) { + if (it->range.contains(pos)) + return it->change.type; + } + + check(); + lyx::Assert(0); + return Change::UNCHANGED; +} + + +bool Changes::isChange(pos_type start, pos_type end) const +{ + if (!table_.size()) { + lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl; + return empty_type_ != Change::UNCHANGED; + } + + ChangeTable::const_iterator it = table_.begin(); + ChangeTable::const_iterator itend = table_.end(); + + for (; it != itend; ++it) { + lyxerr[Debug::CHANGES] << "Looking for " << start << "," + << end << " in " << it->range.start << "," + << it->range.end << "of type " << it->change.type << endl; + if (it->range.intersects(Range(start, end)) + && it->change.type != Change::UNCHANGED) { + lyxerr[Debug::CHANGES] << "Found intersection of " + << start << "," << end << " with " + << it->range.start << "," << it->range.end + << " of type " << it->change.type << endl; + return true; + } + } + + return false; +} + + +bool Changes::isChangeEdited(lyx::pos_type start, lyx::pos_type end) const +{ + if (!table_.size()) { + lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl; + return empty_type_ != Change::INSERTED; + } + + ChangeTable::const_iterator it = table_.begin(); + ChangeTable::const_iterator itend = table_.end(); + + for (; it != itend; ++it) { + if (it->range.intersects(Range(start, end ? end - 1 : 0)) + && it->change.type != Change::INSERTED) { + return true; + } + } + return false; +} + + +void Changes::merge() +{ + lyxerr[Debug::CHANGES] << "Starting merge" << endl; + ChangeTable::iterator it = table_.begin(); + + while (it != table_.end()) { + lyxerr[Debug::CHANGES] << "Range of type " << it->change.type << " is " + << it->range.start << "," << it->range.end << endl; + + if (it->range.start == it->range.end) { + lyxerr[Debug::CHANGES] << "Removing empty range for pos " + << it->range.start << endl; + table_.erase(it); + // start again + it = table_.begin(); + continue; + } + + if (it + 1 == table_.end()) + break; + + if (it->change == (it + 1)->change) { + lyxerr[Debug::CHANGES] << "Merging equal ranges " + << it->range.start << "," << it->range.end + << " and " << (it + 1)->range.start << "," + << (it + 1)->range.end << endl; + (it + 1)->range.start = it->range.start; + table_.erase(it); + // start again + it = table_.begin(); + continue; + } + + ++it; + } + + lyxerr[Debug::CHANGES] << "Merge ended" << endl; + check(); +} + + +void Changes::check() const +{ + ChangeTable::const_iterator it = table_.begin(); + ChangeTable::const_iterator end = table_.end(); + + bool dont_assert(true); + + lyxerr[Debug::CHANGES] << "Changelist:" << endl; + for (; it != end; ++it) { + lyxerr[Debug::CHANGES] << "Range of type " << it->change.type << " is " + << it->range.start << "," << it->range.end << " author " + << it->change.author << " time " << it->change.changetime << endl; + if (it + 1 == end) + break; + + Range const & range(it->range); + Range const & next((it + 1)->range); + if (range.end != next.start) + dont_assert = false; + } + lyxerr[Debug::CHANGES] << "End" << endl; + lyx::Assert(dont_assert); +} + + +int Changes::latexMarkChange(std::ostream & os, Change::Type old, Change::Type change) +{ + if (old == change) + return 0; + + string const start("\\changestart{}"); + string const end("\\changeend{}"); + string const son("\\overstrikeon{}"); + string const soff("\\overstrikeoff{}"); + + int column = 0; + + if (old == Change::DELETED) { + os << soff; + column += soff.length(); + } + + switch (change) { + case Change::UNCHANGED: + os << end; + column += end.length(); + break; + + case Change::DELETED: + if (old == Change::UNCHANGED) { + os << start; + column += start.length(); + } + os << son; + column += son.length(); + break; + + case Change::INSERTED: + if (old == Change::UNCHANGED) { + os << start; + column += start.length(); + } + break; + } + + return column; +} + + +void Changes::lyxMarkChange(std::ostream & os, int & column, lyx::time_type curtime, + Change const & old, Change const & change) +{ + if (old == change) + return; + + column = 0; + + switch (change.type) { + case Change::UNCHANGED: + os << "\n\\change_unchanged\n"; + break; + + case Change::DELETED: { + lyx::time_type t(change.changetime); + if (!t) + t = curtime; + os << "\n\\change_deleted " << change.author + << " " << t << "\n"; + + break; + } + + case Change::INSERTED: + lyx::time_type t(change.changetime); + if (!t) + t = curtime; + os << "\n\\change_inserted " << change.author + << " " << t << "\n"; + break; + } +} diff --git a/src/changes.h b/src/changes.h new file mode 100644 index 0000000000..ac378e5925 --- /dev/null +++ b/src/changes.h @@ -0,0 +1,150 @@ +/** + * \file changes.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * Record changes in a paragraph. + * + * \author John Levon + */ + +#ifndef CHANGES_H +#define CHANGES_H + +#include "support/types.h" +#include "support/lyxtime.h" + +#include +#include +#include + +struct Change { + /// the type of change + enum Type { + UNCHANGED, // no change + INSERTED, // new text + DELETED // deleted text + }; + + Change(Type t = UNCHANGED, int a = 0, lyx::time_type ct = 0) + : type(t), author(a), changetime(ct) {} + + Type type; + + int author; + + lyx::time_type changetime; +}; + +bool operator==(Change const & l, Change const & r); +bool operator!=(Change const & l, Change const & r); + +class Changes { +public: + + Changes(Change::Type type); + + ~Changes(); + + Changes(Changes const &); + + /// reset "default" change type (for empty pars) + void reset(Change::Type type) { + empty_type_ = type; + } + + /// set the position to the given change + void set(Change change, lyx::pos_type pos); + + /// set the position to the given change + void set(Change::Type, lyx::pos_type pos); + + /// set the range to the given change + void set(Change::Type, lyx::pos_type start, lyx::pos_type end); + + /// set the range to the given change + void set(Change, lyx::pos_type start, lyx::pos_type end); + + /// mark the given change and adjust + void record(Change, lyx::pos_type pos); + + /// return the change type at the given position + Change::Type lookup(lyx::pos_type pos) const; + + /// return the change at the given position + Change const lookupFull(lyx::pos_type pos) const; + + /// return true if there is a change in the given range + bool isChange(lyx::pos_type start, lyx::pos_type end) const; + + /// return true if there is a deleted or unchanged range contained + bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; + + /// remove the given entry + void erase(lyx::pos_type pos); + + /// output latex to mark a transition between two changetypes + /// returns length of text outputted + static int latexMarkChange(std::ostream & os, Change::Type old, Change::Type change); + + /// output .lyx file format for transitions between changes + static void lyxMarkChange(std::ostream & os, int & column, + lyx::time_type curtime, Change const & old, Change const & change); + +private: + struct Range { + Range(lyx::pos_type s, lyx::pos_type e) + : start(s), end(e) {} + + // does this range contain r ? + bool contains(Range const & r) const; + + // does this range contain pos ? + bool contains(lyx::pos_type pos) const; + + // does this range contain pos, or can it be appended ? + bool loose_contains(lyx::pos_type pos) const; + + // is this range contained within r ? + bool contained(Range const & r) const; + + // do the ranges intersect ? + bool intersects(Range const & r) const; + + lyx::pos_type start; + lyx::pos_type end; + }; + + friend bool operator==(Range const & r1, Range const & r2); + friend bool operator!=(Range const & r1, Range const & r2); + + struct ChangeRange { + ChangeRange(lyx::pos_type s, lyx::pos_type e, Change c) + : range(Range(s, e)), change(c) {} + Range range; + Change change; + }; + + typedef std::vector ChangeTable; + + /// our table of changes + ChangeTable table_; + + /// change type for an empty paragraph + Change::Type empty_type_; + + /// handle a delete + void del(Change change, ChangeTable::size_type pos); + + /// handle an add + void add(Change change, ChangeTable::size_type pos); + + /// merge neighbouring ranges + void merge(); + + /// consistency check + void check() const; + +}; + +#endif // CHANGES_H diff --git a/src/commandtags.h b/src/commandtags.h index 93ec20391e..6fd422aee0 100644 --- a/src/commandtags.h +++ b/src/commandtags.h @@ -290,6 +290,12 @@ enum kb_action { LFUN_MOUSE_TRIPLE, // André 9 Aug 2002 LFUN_EDIT, // André 16 Aug 2002 LFUN_INSET_WRAP, // Dekel 7 Apr 2002 + LFUN_TRACK_CHANGES, // Levon 20021001 (cool date !) + LFUN_MERGE_CHANGES, // Levon 20021016 + LFUN_ACCEPT_CHANGE, // Levon 20021016 + LFUN_REJECT_CHANGE, // Levon 20021016 + LFUN_ACCEPT_ALL_CHANGES, // Levon 20021016 + LFUN_REJECT_ALL_CHANGES, // Levon 20021016 LFUN_LASTACTION /* this marks the end of the table */ }; diff --git a/src/debug.C b/src/debug.C index 389efd5a85..6117f282c2 100644 --- a/src/debug.C +++ b/src/debug.C @@ -58,6 +58,7 @@ error_item errorTags[] = { { Debug::WORKAREA, "workarea", N_("Workarea events")}, { Debug::INSETTEXT, "insettext", N_("Insettext/tabular messages")}, { Debug::GRAPHICS, "graphics", N_("Graphics conversion and loading")}, + { Debug::CHANGES, "changes", N_("Change tracking")}, { Debug::ANY, "any", N_("All debugging messages")} }; @@ -73,7 +74,7 @@ Debug::type const Debug::ANY = Debug::type( Debug::MATHED | Debug::FONT | Debug::TCLASS | Debug::LYXVC | Debug::LYXSERVER | Debug::ROFF | Debug::ACTION | Debug::LYXLEX | Debug::DEPEND | Debug::INSETS | Debug::FILES | Debug::WORKAREA | - Debug::INSETTEXT | Debug::GRAPHICS); + Debug::INSETTEXT | Debug::GRAPHICS | Debug::CHANGES); Debug::type Debug::value(string const & val) diff --git a/src/debug.h b/src/debug.h index cb3d87e799..8762fbd66f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -72,7 +72,9 @@ struct Debug { /// INSETTEXT = (1 << 20), /// - GRAPHICS = (1 << 21) + GRAPHICS = (1 << 21), + /// change tracking + CHANGES = (1 << 22) }; /// static type const ANY; diff --git a/src/frontends/ChangeLog b/src/frontends/ChangeLog index e6c9967f3e..6a70d7246b 100644 --- a/src/frontends/ChangeLog +++ b/src/frontends/ChangeLog @@ -1,3 +1,7 @@ +2003-02-08 John Levon + + * Dialogs.h: add showMergeChanges() + 2003-01-11 Juergen Spitzmueller * FileDialog.h: implement opendir (browse directory) [bug 824] diff --git a/src/frontends/Dialogs.h b/src/frontends/Dialogs.h index cb8a929dd4..ca96da9be6 100644 --- a/src/frontends/Dialogs.h +++ b/src/frontends/Dialogs.h @@ -126,6 +126,8 @@ public: void showLogFile(); /// display the top-level maths panel void showMathPanel(); + /// show the merge changes dialog + void showMergeChanges(); /// void showMinipage(InsetMinipage *); /// diff --git a/src/frontends/controllers/ChangeLog b/src/frontends/controllers/ChangeLog index 41907bffd5..e9bae13ade 100644 --- a/src/frontends/controllers/ChangeLog +++ b/src/frontends/controllers/ChangeLog @@ -1,3 +1,12 @@ +2003-02-08 John Levon + + * Makefile.am: + * ControlChanges.h: + * ControlChanges.C: add merge changes dialog + + * ControlPrefs.h: + * ControlPrefs.C: add setCurrentAuthor() + 2003-01-31 Angus Leeming * ViewBase.h: add an isVisible() pure virtual method. diff --git a/src/frontends/controllers/ControlChanges.C b/src/frontends/controllers/ControlChanges.C new file mode 100644 index 0000000000..3d173d6242 --- /dev/null +++ b/src/frontends/controllers/ControlChanges.C @@ -0,0 +1,82 @@ +/** + * \file ControlChanges.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#include + +#ifdef __GNUG__ +#pragma implementation +#endif + +#include "ViewBase.h" +#include "ButtonControllerBase.h" +#include "ControlChanges.h" +#include "frontends/Dialogs.h" +#include "frontends/LyXView.h" +#include "buffer.h" +#include "lyxfind.h" +#include "lyxfunc.h" +#include "debug.h" +#include "BufferView.h" +#include "support/lstrings.h" +#include "funcrequest.h" +#include "author.h" + +ControlChanges::ControlChanges(LyXView & lv, Dialogs & d) + : ControlDialogBD(lv, d) +{ +} + + +void ControlChanges::find() +{ + lyxfind::findNextChange(bufferview()); +} + + +string const ControlChanges::getChangeDate() +{ + Change c(bufferview()->getCurrentChange()); + if (c.type == Change::UNCHANGED || !c.changetime) + return string(); + return ctime(&c.changetime); +} + + +string const ControlChanges::getChangeAuthor() +{ + Change c(bufferview()->getCurrentChange()); + if (c.type == Change::UNCHANGED) + return string(); + + Author const & a(bufferview()->buffer()->authors().get(c.author)); + + string author(a.name()); + + if (!a.email().empty()) { + author += " ("; + author += a.email() + ")"; + } + + return author; +} + + +void ControlChanges::accept() +{ + lv_.dispatch(FuncRequest(LFUN_ACCEPT_CHANGE)); + lyxfind::findNextChange(bufferview()); +} + + +void ControlChanges::reject() +{ + lv_.dispatch(FuncRequest(LFUN_REJECT_CHANGE)); + lyxfind::findNextChange(bufferview()); +} diff --git a/src/frontends/controllers/ControlChanges.h b/src/frontends/controllers/ControlChanges.h new file mode 100644 index 0000000000..d7d7f383c6 --- /dev/null +++ b/src/frontends/controllers/ControlChanges.h @@ -0,0 +1,49 @@ +// -*- C++ -*- +/** + * \file ControlChanges.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#ifndef CONTROLCHANGES_H +#define CONTROLCHANGES_H + +#ifdef __GNUG__ +#pragma interface +#endif + +#include "ControlDialog_impl.h" +#include "LString.h" + +/** + * A controller for the merge changes dialog. + */ +class ControlChanges : public ControlDialogBD { +public: + ControlChanges(LyXView &, Dialogs &); + + /// find the next merge chunk and highlight it + void find(); + + /// return date of change + string const getChangeDate(); + + /// return author of change + string const getChangeAuthor(); + + /// accept the current merge + void accept(); + + /// reject the current merge + void reject(); + +private: + /// not needed. + virtual void apply() {} +}; + +#endif // CONTROLCHANGES_H diff --git a/src/frontends/controllers/ControlPrefs.C b/src/frontends/controllers/ControlPrefs.C index 660149bc9f..7179f5a2d6 100644 --- a/src/frontends/controllers/ControlPrefs.C +++ b/src/frontends/controllers/ControlPrefs.C @@ -20,6 +20,7 @@ #include "ViewBase.h" #include "frontends/LyXView.h" +#include "bufferlist.h" #include "helper_funcs.h" #include "gettext.h" #include "support/filetools.h" @@ -29,6 +30,7 @@ extern string system_lyxdir; extern string user_lyxdir; +extern BufferList bufferlist; using std::endl; using std::pair; @@ -153,3 +155,9 @@ void ControlPrefs::setFormats(Formats const & form) { formats = form; } + + +void ControlPrefs::setCurrentAuthor() +{ + bufferlist.setCurrentAuthor(rc_.user_name, rc_.user_email); +} diff --git a/src/frontends/controllers/ControlPrefs.h b/src/frontends/controllers/ControlPrefs.h index 10d9a74e5c..54670051c8 100644 --- a/src/frontends/controllers/ControlPrefs.h +++ b/src/frontends/controllers/ControlPrefs.h @@ -66,6 +66,9 @@ public: /// set global formats void setFormats(Formats const & form); + /// reset the details for the current author for all buffers + void setCurrentAuthor(); + private: /// get current lyxrc virtual void setParams(); diff --git a/src/frontends/controllers/Makefile.am b/src/frontends/controllers/Makefile.am index 4f53db5df2..437b524a75 100644 --- a/src/frontends/controllers/Makefile.am +++ b/src/frontends/controllers/Makefile.am @@ -31,6 +31,8 @@ libcontrollers_la_SOURCES= \ ControlButtons.h \ ControlCharacter.C \ ControlCharacter.h \ + ControlChanges.C \ + ControlChanges.h \ ControlCitation.C \ ControlCitation.h \ ControlCommand.C \ diff --git a/src/frontends/xforms/ChangeLog b/src/frontends/xforms/ChangeLog index f15d62ce47..ddb79904a9 100644 --- a/src/frontends/xforms/ChangeLog +++ b/src/frontends/xforms/ChangeLog @@ -1,3 +1,18 @@ +2003-02-08 John Levon + + * Makefile.am: + * forms/Makefile.am: + * forms/form_changes.fd: + * Dialogs.C: + * Dialogs2.C: + * Dialogs_impl.h: + * FormChanges.h: + * FormChanges.C: add changes dialog + + * FormPreferences.h: + * FormPreferences.C: + * forms/form_preferences.fd: add Identity prefs + 2003-01-31 Michael Schmitt * FormDocument.C: diff --git a/src/frontends/xforms/Dialogs.C b/src/frontends/xforms/Dialogs.C index abb14fbe85..f4483db43f 100644 --- a/src/frontends/xforms/Dialogs.C +++ b/src/frontends/xforms/Dialogs.C @@ -48,6 +48,7 @@ Dialogs::Impl::Impl(LyXView & lv, Dialogs & d) : aboutlyx(lv, d), bibitem(lv, d), bibtex(lv, d), + changes(lv, d), character(lv, d), citation(lv, d), document(lv, d), diff --git a/src/frontends/xforms/Dialogs2.C b/src/frontends/xforms/Dialogs2.C index 0a7f9f4f58..0a6f298b00 100644 --- a/src/frontends/xforms/Dialogs2.C +++ b/src/frontends/xforms/Dialogs2.C @@ -37,6 +37,12 @@ void Dialogs::showBibtex(InsetCommand * ic) } +void Dialogs::showMergeChanges() +{ + pimpl_->changes.controller().show(); +} + + void Dialogs::showCharacter() { pimpl_->character.controller().show(); diff --git a/src/frontends/xforms/Dialogs_impl.h b/src/frontends/xforms/Dialogs_impl.h index de03a22563..342f101304 100644 --- a/src/frontends/xforms/Dialogs_impl.h +++ b/src/frontends/xforms/Dialogs_impl.h @@ -37,6 +37,10 @@ #include "FormBrowser.h" #include "forms/form_browser.h" +#include "ControlChanges.h" +#include "FormChanges.h" +#include "forms/form_changes.h" + #include "ControlCharacter.h" #include "FormCharacter.h" #include "forms/form_character.h" @@ -170,6 +174,9 @@ BibitemDialog; typedef GUI BibtexDialog; +typedef GUI +ChangesDialog; + typedef GUI CharacterDialog; @@ -270,8 +277,9 @@ struct Dialogs::Impl { AboutlyxDialog aboutlyx; BibitemDialog bibitem; - BibtexDialog bibtex; - CharacterDialog character; + BibtexDialog bibtex; + ChangesDialog changes; + CharacterDialog character; CitationDialog citation; DocumentDialog document; ErrorDialog error; diff --git a/src/frontends/xforms/FormChanges.C b/src/frontends/xforms/FormChanges.C new file mode 100644 index 0000000000..621967256f --- /dev/null +++ b/src/frontends/xforms/FormChanges.C @@ -0,0 +1,81 @@ +/** + * \file FormChanges.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#include + +#ifdef __GNUG__ +#pragma implementation +#endif + +#include "xformsBC.h" +#include "ControlChanges.h" +#include "FormChanges.h" +#include "forms/form_changes.h" + +#include FORMS_H_LOCATION + +typedef FormCB > base_class; + +FormChanges::FormChanges() + : base_class(_("LyX: Merge changes")) +{} + + +void FormChanges::build() +{ + dialog_.reset(build_changes(this)); + + bc().setCancel(dialog_->button_close); + bc().addReadOnly(dialog_->button_accept); + bc().addReadOnly(dialog_->button_reject); +} + + +void FormChanges::update() +{ + fl_set_object_label(dialog_->author, ""); + fl_set_object_label(dialog_->date, ""); + // FIXME: enable/disable accept/reject +} + + +ButtonPolicy::SMInput FormChanges::input(FL_OBJECT * obj, long) +{ + if (obj == dialog_->button_accept) { + controller().accept(); + return ButtonPolicy::SMI_VALID; + } + + if (obj == dialog_->button_reject) { + controller().reject(); + return ButtonPolicy::SMI_VALID; + } + + if (obj != dialog_->button_next) + return ButtonPolicy::SMI_VALID; + + controller().find(); + + string author(controller().getChangeAuthor()); + string date(controller().getChangeDate()); + if (!date.empty()) { + date = _("Changed at : ") + date; + } + if (!author.empty()) { + author = _("Change made by : ") + author; + } + fl_set_object_label(dialog_->author, author.c_str()); + fl_set_object_label(dialog_->date, date.c_str()); + + // Yes, this is needed. + fl_redraw_form(form()); + + return ButtonPolicy::SMI_VALID; +} diff --git a/src/frontends/xforms/FormChanges.h b/src/frontends/xforms/FormChanges.h new file mode 100644 index 0000000000..7bcf7cf20c --- /dev/null +++ b/src/frontends/xforms/FormChanges.h @@ -0,0 +1,43 @@ +// -*- C++ -*- +/** + * \file FormChanges.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#ifndef FORMCHANGES_H +#define FORMCHANGES_H + +#ifdef __GNUG__ +#pragma interface +#endif + +#include "FormBase.h" + +class ControlChanges; +struct FD_changes; + +/** + * This class provides an XForms implementation of the Merge Changes Dialog. + */ +class FormChanges : public FormCB > { +public: + FormChanges(); + +private: + /// not needed. + virtual void apply() {} + /// Build the dialog + virtual void build(); + /// update the dialog + virtual void update(); + + /// Filter the inputs + virtual ButtonPolicy::SMInput input(FL_OBJECT *, long); +}; + +#endif // FORMCHANGES_H diff --git a/src/frontends/xforms/FormPreferences.C b/src/frontends/xforms/FormPreferences.C index 7647255cb9..ef63096b0e 100644 --- a/src/frontends/xforms/FormPreferences.C +++ b/src/frontends/xforms/FormPreferences.C @@ -36,6 +36,7 @@ #include "LColor.h" #include "Lsstream.h" #include "funcrequest.h" +#include "author.h" #include "support/lyxfunctional.h" #include "support/lyxmanip.h" @@ -97,8 +98,9 @@ FormPreferences::FormPreferences() : base_class(_("Preferences"), false), colors_(*this), converters_(*this), inputs_misc_(*this), formats_(*this), interface_(*this), language_(*this), - lnf_misc_(*this), outputs_misc_(*this), paths_(*this), - printer_(*this), screen_fonts_(*this), spelloptions_(*this) + lnf_misc_(*this), identity_(*this), outputs_misc_(*this), + paths_(*this), printer_(*this), screen_fonts_(*this), + spelloptions_(*this) { } @@ -175,6 +177,7 @@ void FormPreferences::build() interface_.build(); language_.build(); lnf_misc_.build(); + identity_.build(); outputs_misc_.build(); paths_.build(); printer_.build(); @@ -212,6 +215,9 @@ void FormPreferences::build() fl_addto_tabfolder(look_n_feel_tab_->tabfolder_inner, _("Misc"), lnf_misc_.dialog()->form); + fl_addto_tabfolder(look_n_feel_tab_->tabfolder_inner, + _("Identity"), + identity_.dialog()->form); // then build converters fl_addto_tabfolder(converters_tab_->tabfolder_inner, @@ -277,6 +283,7 @@ void FormPreferences::apply() interface_.apply(rc); language_.apply(rc); lnf_misc_.apply(rc); + identity_.apply(rc); outputs_misc_.apply(rc); paths_.apply(rc); printer_.apply(rc); @@ -371,6 +378,7 @@ void FormPreferences::update() interface_.update(rc); language_.update(rc); lnf_misc_.update(rc); + identity_.update(rc); outputs_misc_.update(rc); paths_.update(rc); printer_.update(rc); @@ -1424,6 +1432,40 @@ bool FormPreferences::Formats::Input() } +FormPreferences::Identity::Identity(FormPreferences & p) + : parent_(p) +{} + + +FD_preferences_identity const * FormPreferences::Identity::dialog() +{ + return dialog_.get(); +} + + +void FormPreferences::Identity::apply(LyXRC & rc) const +{ + rc.user_name = fl_get_input(dialog_->input_user_name); + rc.user_email = fl_get_input(dialog_->input_user_email); + parent_.controller().setCurrentAuthor(); +} + + +void FormPreferences::Identity::build() +{ + dialog_.reset(build_preferences_identity(&parent_)); + fl_set_input_return(dialog_->input_user_name, FL_RETURN_CHANGED); + fl_set_input_return(dialog_->input_user_email, FL_RETURN_CHANGED); +} + + +void FormPreferences::Identity::update(LyXRC const & rc) +{ + fl_set_input(dialog_->input_user_name, rc.user_name.c_str()); + fl_set_input(dialog_->input_user_email, rc.user_email.c_str()); +} + + FormPreferences::InputsMisc::InputsMisc(FormPreferences & p) : parent_(p) {} diff --git a/src/frontends/xforms/FormPreferences.h b/src/frontends/xforms/FormPreferences.h index eb47395589..0f235462d2 100644 --- a/src/frontends/xforms/FormPreferences.h +++ b/src/frontends/xforms/FormPreferences.h @@ -41,6 +41,7 @@ struct FD_preferences_inputs_misc; struct FD_preferences_interface; struct FD_preferences_language; struct FD_preferences_lnf_misc; +struct FD_preferences_identity; struct FD_preferences_inner_tab; struct FD_preferences_outputs_misc; struct FD_preferences_paths; @@ -336,6 +337,29 @@ private: /// friend class LnFmisc; + class Identity { + public: + /// + Identity(FormPreferences & p); + /// + FD_preferences_identity const * dialog(); + /// + void apply(LyXRC & rc) const; + /// + void build(); + /// + string const feedback(FL_OBJECT const * const) const; + /// + void update(LyXRC const & rc); + + private: + /// + FormPreferences & parent_; + /// + boost::scoped_ptr dialog_; + }; + friend class Identity; + /// class OutputsMisc { public: @@ -485,6 +509,8 @@ private: /// LnFmisc lnf_misc_; /// + Identity identity_; + /// OutputsMisc outputs_misc_; /// Paths paths_; diff --git a/src/frontends/xforms/Makefile.am b/src/frontends/xforms/Makefile.am index 58e403ab42..be6da8f9bf 100644 --- a/src/frontends/xforms/Makefile.am +++ b/src/frontends/xforms/Makefile.am @@ -66,6 +66,8 @@ libxforms_la_SOURCES = \ FormBibtex.h \ FormBrowser.C \ FormBrowser.h \ + FormChanges.C \ + FormChanges.h \ FormCharacter.C \ FormCharacter.h \ FormCitation.C \ diff --git a/src/frontends/xforms/forms/Makefile.am b/src/frontends/xforms/forms/Makefile.am index e81a8a7c4f..615ad924f0 100644 --- a/src/frontends/xforms/forms/Makefile.am +++ b/src/frontends/xforms/forms/Makefile.am @@ -11,6 +11,7 @@ SRCS = form_aboutlyx.fd \ form_bibitem.fd \ form_bibtex.fd \ form_browser.fd \ + form_changes.fd \ form_character.fd \ form_citation.fd \ form_document.fd \ diff --git a/src/frontends/xforms/forms/form_changes.fd b/src/frontends/xforms/forms/form_changes.fd new file mode 100644 index 0000000000..a9a5cbf5cd --- /dev/null +++ b/src/frontends/xforms/forms/form_changes.fd @@ -0,0 +1,160 @@ +Magic: 13000 + +Internal Form Definition File + (do not change) + +Number of forms: 1 +Unit of measure: FL_COORD_PIXEL + +=============== FORM =============== +Name: form_changes +Width: 400 +Height: 190 +Number of Objects: 8 + +-------------------- +class: FL_BOX +type: UP_BOX +box: 0 0 400 190 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_DEFAULT_SIZE +lcol: FL_BLACK +label: +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: +callback: +argument: + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 270 110 120 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Reject change|#R +shortcut: +resize: FL_RESIZE_NONE +gravity: FL_SouthEast FL_SouthEast +name: button_reject +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 10 110 100 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Next change|#N +shortcut: +resize: FL_RESIZE_NONE +gravity: FL_SouthEast FL_SouthEast +name: button_next +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 130 110 120 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Accept change|#A +shortcut: +resize: FL_RESIZE_NONE +gravity: FL_SouthEast FL_SouthEast +name: button_accept +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 310 150 80 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Close|^[ +shortcut: +resize: FL_RESIZE_NONE +gravity: FL_SouthEast FL_SouthEast +name: button_close +callback: C_FormBaseCancelCB +argument: 0 + +-------------------- +class: FL_TEXT +type: NORMAL_TEXT +box: 10 10 150 20 +boxtype: FL_FLAT_BOX +colors: FL_COL1 FL_MCOL +alignment: FL_ALIGN_LEFT|FL_ALIGN_INSIDE +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Accept change ? +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: +callback: +argument: + +-------------------- +class: FL_TEXT +type: NORMAL_TEXT +box: 10 40 380 30 +boxtype: FL_NO_BOX +colors: FL_COL1 FL_MCOL +alignment: FL_ALIGN_LEFT|FL_ALIGN_INSIDE +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: text +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: author +callback: +argument: + +-------------------- +class: FL_TEXT +type: NORMAL_TEXT +box: 10 70 380 30 +boxtype: FL_NO_BOX +colors: FL_COL1 FL_MCOL +alignment: FL_ALIGN_LEFT|FL_ALIGN_INSIDE +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: text +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: date +callback: +argument: + +============================== +-------------------- diff --git a/src/frontends/xforms/forms/form_preferences.fd b/src/frontends/xforms/forms/form_preferences.fd index 0e08ad6790..ef0ce498f3 100644 --- a/src/frontends/xforms/forms/form_preferences.fd +++ b/src/frontends/xforms/forms/form_preferences.fd @@ -3,7 +3,7 @@ Magic: 13000 Internal Form Definition File (do not change) -Number of forms: 14 +Number of forms: 15 Unit of measure: FL_COORD_PIXEL SnapGrid: 5 @@ -1222,6 +1222,66 @@ name: choice_display callback: C_FormBaseInputCB argument: 0 +=============== FORM =============== +Name: form_preferences_identity +Width: 450 +Height: 350 +Number of Objects: 3 + +-------------------- +class: FL_BOX +type: FLAT_BOX +box: 0 0 450 350 +boxtype: FL_FLAT_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_DEFAULT_SIZE +lcol: FL_BLACK +label: +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: +callback: +argument: + +-------------------- +class: FL_INPUT +type: NORMAL_INPUT +box: 125 25 220 35 +boxtype: FL_DOWN_BOX +colors: FL_COL1 FL_MCOL +alignment: FL_ALIGN_LEFT +style: FL_NORMAL_STYLE +size: FL_DEFAULT_SIZE +lcol: FL_BLACK +label: Real name : |#R +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: input_user_name +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_INPUT +type: NORMAL_INPUT +box: 125 80 220 35 +boxtype: FL_DOWN_BOX +colors: FL_COL1 FL_MCOL +alignment: FL_ALIGN_LEFT +style: FL_NORMAL_STYLE +size: FL_DEFAULT_SIZE +lcol: FL_BLACK +label: Email address : |#E +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: input_user_email +callback: C_FormBaseInputCB +argument: 0 + =============== FORM =============== Name: form_preferences_spelloptions Width: 450 diff --git a/src/insets/ChangeLog b/src/insets/ChangeLog index 9df1ee800d..93de8b4043 100644 --- a/src/insets/ChangeLog +++ b/src/insets/ChangeLog @@ -1,3 +1,20 @@ +2003-02-08 John Levon + + * inset.h: + * inset.C: + * insetcollapsable.h: + * insetcollapsable.C: + * insettabular.h: + * insettabular.C: + * insettext.h: + * insettext.C: + add nextChange(). Make allowSpellcheck() const. Add markErased(). + + * insetert.C: ignore deleted text + + * insettabular.C: make sure to keep change tracking working + properly. + 2003-01-20 Michael Schmitt * insetert.C: diff --git a/src/insets/inset.C b/src/insets/inset.C index 650b6b4510..a6ae3fd32e 100644 --- a/src/insets/inset.C +++ b/src/insets/inset.C @@ -361,6 +361,14 @@ UpdatableInset::selectNextWordToSpellcheck(BufferView *bv, float & value) const } +bool UpdatableInset::nextChange(BufferView * bv, lyx::pos_type &) +{ + // we have to unlock ourself in this function by default! + bv->unlockInset(const_cast(this)); + return false; +} + + bool UpdatableInset::searchForward(BufferView * bv, string const &, bool, bool) { diff --git a/src/insets/inset.h b/src/insets/inset.h index ef8ccd31b7..3b6f241f18 100644 --- a/src/insets/inset.h +++ b/src/insets/inset.h @@ -23,6 +23,7 @@ #include "LString.h" #include "LColor.h" #include "frontends/mouse_state.h" +#include "support/types.h" class LyXFont; class BufferView; @@ -335,7 +336,7 @@ public: /// // needed for spellchecking text /// - virtual bool allowSpellcheck() { return false; } + virtual bool allowSpellcheck() const { return false; } // should this inset be handled like a normal charater virtual bool isChar() const { return false; } @@ -361,6 +362,9 @@ public: minipage somewhere, it will be the width of this minipage */ virtual int latexTextWidth(BufferView *) const; + /// mark the inset contents as erased (for change tracking) + virtual void markErased() {} + /** Adds a LaTeX snippet to the Preview Loader for transformation * into a bitmap image. Does not start the laoding process. * @@ -519,16 +523,20 @@ public: /// // needed for spellchecking text /// - virtual bool allowSpellcheck() { return false; } + virtual bool allowSpellcheck() const { return false; } /// virtual WordLangTuple const selectNextWordToSpellcheck(BufferView *, float & value) const; /// - virtual void selectSelectedWord(BufferView *) { return; } + virtual void selectSelectedWord(BufferView *) {} /// virtual void toggleSelection(BufferView *, bool /*kill_selection*/) { return; } + + /// find the next change in the inset + virtual bool nextChange(BufferView * bv, lyx::pos_type & length); + /// // needed for search/replace functionality /// diff --git a/src/insets/insetcollapsable.C b/src/insets/insetcollapsable.C index 15faa9578a..a3a5380d9b 100644 --- a/src/insets/insetcollapsable.C +++ b/src/insets/insetcollapsable.C @@ -669,6 +669,24 @@ void InsetCollapsable::setLabel(string const & l) const } +void InsetCollapsable::markErased() +{ + inset.markErased(); +} + + +bool InsetCollapsable::nextChange(BufferView * bv, lyx::pos_type & length) +{ + bool found = inset.nextChange(bv, length); + + if (first_after_edit && !found) + close(bv); + else if (!found) + first_after_edit = false; + return found; +} + + bool InsetCollapsable::searchForward(BufferView * bv, string const & str, bool cs, bool mw) { diff --git a/src/insets/insetcollapsable.h b/src/insets/insetcollapsable.h index 763804af9e..5af584aef2 100644 --- a/src/insets/insetcollapsable.h +++ b/src/insets/insetcollapsable.h @@ -169,7 +169,7 @@ public: /// void close(BufferView *) const; /// - bool allowSpellcheck() { return inset.allowSpellcheck(); } + bool allowSpellcheck() const { return inset.allowSpellcheck(); } /// WordLangTuple const selectNextWordToSpellcheck(BufferView *, float &) const; @@ -181,6 +181,11 @@ public: void toggleSelection(BufferView * bv, bool kill_selection) { inset.toggleSelection(bv, kill_selection); } + + void markErased(); + + bool nextChange(BufferView * bv, lyx::pos_type & length); + /// bool searchForward(BufferView * bv, string const & str, bool = true, bool = false); diff --git a/src/insets/insetert.C b/src/insets/insetert.C index 58a796d02a..286c4f0a78 100644 --- a/src/insets/insetert.C +++ b/src/insets/insetert.C @@ -351,6 +351,10 @@ int InsetERT::latex(Buffer const *, ostream & os, bool /*fragile*/, while (par) { pos_type siz = par->size(); for (pos_type i = 0; i < siz; ++i) { + // ignore all struck out text + if (isDeletedText(par, i)) + continue; + Paragraph::value_type c = par->getChar(i); switch (c) { case Paragraph::META_NEWLINE: diff --git a/src/insets/insetert.h b/src/insets/insetert.h index f12e30699e..83c929041f 100644 --- a/src/insets/insetert.h +++ b/src/insets/insetert.h @@ -105,7 +105,7 @@ public: /// void close(BufferView *) const; /// - bool allowSpellcheck() { return false; } + bool allowSpellcheck() const { return false; } WordLangTuple const selectNextWordToSpellcheck(BufferView *, float &) const; diff --git a/src/insets/insettabular.C b/src/insets/insettabular.C index 61cc2ea0bb..c364d36aaa 100644 --- a/src/insets/insettabular.C +++ b/src/insets/insettabular.C @@ -1171,7 +1171,7 @@ Inset::RESULT InsetTabular::localDispatch(FuncRequest const & cmd) setUndo(bv, Undo::DELETE, bv->text->cursor.par(), bv->text->cursor.par()->next()); - cutSelection(); + cutSelection(bv->buffer()->params); updateLocal(bv, INIT, true); break; case LFUN_COPY: @@ -2577,13 +2577,14 @@ bool InsetTabular::pasteSelection(BufferView * bv) *(tabular->GetCellInset(n2)) = *(paste_tabular->GetCellInset(n1)); tabular->GetCellInset(n2)->setOwner(this); tabular->GetCellInset(n2)->deleteLyXText(bv); + tabular->GetCellInset(n2)->markNew(); } } return true; } -bool InsetTabular::cutSelection() +bool InsetTabular::cutSelection(BufferParams const & bp) { if (!hasSelection()) return false; @@ -2606,7 +2607,7 @@ bool InsetTabular::cutSelection() } for (int i = sel_row_start; i <= sel_row_end; ++i) { for (int j = sel_col_start; j <= sel_col_end; ++j) { - tabular->GetCellInset(tabular->GetCellNumber(i, j))->clear(); + tabular->GetCellInset(tabular->GetCellNumber(i, j))->clear(bp.tracking_changes); } } return true; @@ -2788,6 +2789,46 @@ void InsetTabular::toggleSelection(BufferView * bv, bool kill_selection) } +void InsetTabular::markErased() +{ + int cell = 0; + + while (!tabular->IsLastCell(cell)) { + ++cell; + InsetText * inset = tabular->GetCellInset(cell); + inset->markErased(); + } +} + + +bool InsetTabular::nextChange(BufferView * bv, lyx::pos_type & length) +{ + if (the_locking_inset) { + if (the_locking_inset->nextChange(bv, length)) { + updateLocal(bv, CELL, false); + return true; + } + if (tabular->IsLastCell(actcell)) + return false; + ++actcell; + } + InsetText * inset = tabular->GetCellInset(actcell); + if (inset->nextChange(bv, length)) { + updateLocal(bv, FULL, false); + return true; + } + while (!tabular->IsLastCell(actcell)) { + ++actcell; + inset = tabular->GetCellInset(actcell); + if (inset->nextChange(bv, length)) { + updateLocal(bv, FULL, false); + return true; + } + } + return false; +} + + bool InsetTabular::searchForward(BufferView * bv, string const & str, bool cs, bool mw) { @@ -2911,12 +2952,14 @@ bool InsetTabular::insertAsciiString(BufferView * bv, string const & buf, ocol = actcol; row = actrow; } + string::size_type op = 0; int cells = loctab->GetNumberOfCells(); p = 0; cols = ocol; rows = loctab->rows(); int const columns = loctab->columns(); + while ((cell < cells) && (p < len) && (row < rows) && (p = buf.find_first_of("\t\n", p)) != string::npos) { diff --git a/src/insets/insettabular.h b/src/insets/insettabular.h index fc6bc9ea11..48cd8ca651 100644 --- a/src/insets/insettabular.h +++ b/src/insets/insettabular.h @@ -63,6 +63,7 @@ class LyXLex; class Painter; class BufferView; class Buffer; +class BufferParams; class Paragraph; class InsetTabular : public UpdatableInset { @@ -208,7 +209,7 @@ public: /// LyXCursor const & cursor(BufferView *) const; /// - bool allowSpellcheck() { return true; } + bool allowSpellcheck() const { return true; } /// WordLangTuple const selectNextWordToSpellcheck(BufferView *, float & value) const; @@ -216,6 +217,11 @@ public: void selectSelectedWord(BufferView *); /// void toggleSelection(BufferView *, bool kill_selection); + + void markErased(); + + /// find next change + bool nextChange(BufferView *, lyx::pos_type & length); /// bool searchForward(BufferView *, string const &, bool = true, bool = false); @@ -316,7 +322,7 @@ private: /// bool pasteSelection(BufferView *); /// - bool cutSelection(); + bool cutSelection(BufferParams const & bp); /// bool isRightToLeft(BufferView *); /// diff --git a/src/insets/insettext.C b/src/insets/insettext.C index 0e39dbc75e..5532b398e6 100644 --- a/src/insets/insettext.C +++ b/src/insets/insettext.C @@ -146,6 +146,8 @@ InsetText::InsetText(BufferParams const & bp) { paragraphs.set(new Paragraph); paragraphs.begin()->layout(bp.getLyXTextClass().defaultLayout()); + if (bp.tracking_changes) + paragraphs.begin()->trackChanges(); init(); } @@ -208,8 +210,18 @@ InsetText::~InsetText() } -void InsetText::clear() +void InsetText::clear(bool just_mark_erased) { + if (just_mark_erased) { + ParagraphList::iterator it = paragraphs.begin(); + ParagraphList::iterator end = paragraphs.end(); + for (; it != end; ++it) { + it->markErased(); + } + need_update = FULL; + return; + } + // This is a gross hack... LyXLayout_ptr old_layout = paragraphs.begin()->layout(); @@ -255,8 +267,11 @@ void InsetText::read(Buffer const * buf, LyXLex & lex) Paragraph::depth_type depth = 0; LyXFont font(LyXFont::ALL_INHERIT); - clear(); + clear(false); + if (buf->params.tracking_changes) + paragraphs.begin()->trackChanges(); + while (lex.isOK()) { lex.nextToken(); token = lex.getString(); @@ -2129,9 +2144,24 @@ void InsetText::setParagraphData(Paragraph * p, bool same_id) } +void InsetText::markNew(bool track_changes) +{ + ParagraphList::iterator pit = paragraphs.begin(); + ParagraphList::iterator pend = paragraphs.end(); + for (; pit != pend; ++pit) { + if (track_changes) { + pit->trackChanges(); + } else { + // no-op when not tracking + pit->cleanChanges(); + } + } +} + + void InsetText::setText(string const & data, LyXFont const & font) { - clear(); + clear(false); for (unsigned int i = 0; i < data.length(); ++i) paragraphs.begin()->insertChar(i, data[i], font); reinitLyXText(); @@ -2659,6 +2689,36 @@ void InsetText::toggleSelection(BufferView * bv, bool kill_selection) } +bool InsetText::nextChange(BufferView * bv, lyx::pos_type & length) +{ + bool clear = false; + if (!lt) { + lt = getLyXText(bv); + clear = true; + } + if (the_locking_inset) { + if (the_locking_inset->nextChange(bv, length)) + return true; + lt->cursorRight(bv, true); + } + lyxfind::SearchResult result = + lyxfind::findNextChange(bv, lt, length); + + if (result == lyxfind::SR_FOUND) { + LyXCursor cur = lt->cursor; + bv->unlockInset(bv->theLockingInset()); + if (bv->lockInset(this)) + locked = true; + lt->cursor = cur; + lt->setSelectionRange(bv, length); + updateLocal(bv, SELECTION, false); + } + if (clear) + lt = 0; + return result != lyxfind::SR_NOT_FOUND; +} + + bool InsetText::searchForward(BufferView * bv, string const & str, bool cs, bool mw) { @@ -2681,7 +2741,7 @@ bool InsetText::searchForward(BufferView * bv, string const & str, if (bv->lockInset(this)) locked = true; lt->cursor = cur; - lt->setSelectionOverString(bv, str); + lt->setSelectionRange(bv, str.length()); updateLocal(bv, SELECTION, false); } if (clear) @@ -2716,7 +2776,7 @@ bool InsetText::searchBackward(BufferView * bv, string const & str, if (bv->lockInset(this)) locked = true; lt->cursor = cur; - lt->setSelectionOverString(bv, str); + lt->setSelectionRange(bv, str.length()); updateLocal(bv, SELECTION, false); } if (clear) @@ -2776,12 +2836,16 @@ void InsetText::appendParagraphs(BufferParams const & bparams, Paragraph * buf; Paragraph * tmpbuf = newpar; Paragraph * lastbuffer = buf = new Paragraph(*tmpbuf, false); + if (bparams.tracking_changes) + buf->cleanChanges(); while (tmpbuf->next()) { tmpbuf = tmpbuf->next(); lastbuffer->next(new Paragraph(*tmpbuf, false)); lastbuffer->next()->previous(lastbuffer); lastbuffer = lastbuffer->next(); + if (bparams.tracking_changes) + lastbuffer->cleanChanges(); } lastbuffer = &*(paragraphs.begin()); while (lastbuffer->next()) diff --git a/src/insets/insettext.h b/src/insets/insettext.h index 38831533a8..a0bbd6e686 100644 --- a/src/insets/insettext.h +++ b/src/insets/insettext.h @@ -82,8 +82,8 @@ public: Inset * clone(Buffer const &, bool same_id = false) const; /// InsetText & operator=(InsetText const & it); - /// - void clear(); + /// empty inset to empty par, or just mark as erased + void clear(bool just_mark_erased); /// void read(Buffer const *, LyXLex &); /// @@ -220,7 +220,7 @@ public: /// void paragraph(Paragraph *); /// - bool allowSpellcheck() { return true; } + bool allowSpellcheck() const { return true; } /// WordLangTuple const selectNextWordToSpellcheck(BufferView *, float & value) const; @@ -228,6 +228,20 @@ public: void selectSelectedWord(BufferView *); /// void toggleSelection(BufferView *, bool kill_selection); + + /// mark as erased for change tracking + void markErased() { clear(true); }; + /** + * Mark as new. Used when pasting in tabular, and adding rows + * or columns. Note that pasting will ensure that tracking already + * happens, and this just resets the changes for the copied text, + * whereas for row/col add, we need to start tracking changes + * for the (empty) paragraph contained. + */ + void markNew(bool track_changes = false); + /// find next change + bool nextChange(BufferView *, lyx::pos_type & length); + /// bool searchForward(BufferView *, string const &, bool = true, bool = false); @@ -238,8 +252,9 @@ public: bool checkInsertChar(LyXFont &); /// void getDrawFont(LyXFont &) const; - /// - void appendParagraphs(BufferParams const & bparams, Paragraph *); + /// append text onto the existing text + void appendParagraphs(BufferParams const & bp, Paragraph *); + /// void addPreview(grfx::PreviewLoader &) const; diff --git a/src/lyx_main.C b/src/lyx_main.C index 0b0e0802ac..7a189991cc 100644 --- a/src/lyx_main.C +++ b/src/lyx_main.C @@ -447,7 +447,7 @@ void LyX::init(bool gui) // If there is a preferences file we read that instead // of the old lyxrc file. if (!readRcFile("preferences")) - readRcFile("lyxrc"); + readRcFile("lyxrc"); readEncodingsFile("encodings"); readLanguagesFile("languages"); diff --git a/src/lyxfind.C b/src/lyxfind.C index 597df35b6a..2526f48a9f 100644 --- a/src/lyxfind.C +++ b/src/lyxfind.C @@ -16,8 +16,10 @@ #include "debug.h" #include "gettext.h" #include "insets/insettext.h" +#include "changes.h" using lyx::pos_type; +using std::endl; namespace lyxfind { @@ -97,7 +99,7 @@ int LyXReplace(BufferView * bv, bv->update(text, BufferView::SELECT|BufferView::FITCUR); bv->toggleSelection(false); text->replaceSelectionWithString(bv, replacestr); - text->setSelectionOverString(bv, replacestr); + text->setSelectionRange(bv, replacestr.length()); bv->update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE); ++replace_count; } @@ -156,7 +158,7 @@ bool LyXFind(BufferView * bv, if (result == SR_FOUND) { bv->unlockInset(bv->theLockingInset()); bv->update(text, BufferView::SELECT|BufferView::FITCUR); - text->setSelectionOverString(bv, searchstr); + text->setSelectionRange(bv, searchstr.length()); bv->toggleSelection(false); bv->update(text, BufferView::SELECT|BufferView::FITCUR); } else if (result == SR_NOT_FOUND) { @@ -312,4 +314,128 @@ SearchResult SearchBackward(BufferView * bv, LyXText * text, } } + +SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length) +{ + Paragraph * par = text->cursor.par(); + pos_type pos = text->cursor.pos(); + Paragraph * prev_par = par; + UpdatableInset * inset; + + while (par) { + if ((!par->size() || pos != par->size()) + && par->lookupChange(pos) != Change::UNCHANGED) + break; + + if (par->isInset(pos) && + (inset = (UpdatableInset *)par->getInset(pos)) && + (inset->isTextInset())) { + if (inset->nextChange(bv, length)) + return SR_FOUND_NOUPDATE; + } + + ++pos; + + if (pos >= par->size()) { + prev_par = par; + par = par->next(); + pos = 0; + } + } + + if (par) { + text->setCursor(bv, par, pos); + Change orig_change = par->lookupChangeFull(pos); + pos_type end = pos; + + for (; end != par->size(); ++end) { + Change change = par->lookupChangeFull(end); + 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; + } + } + length = end - pos; + return SR_FOUND; + } else { + // make sure we end up at the end of the text, + // not the start point of the last search + text->setCursor(bv, prev_par, prev_par->size()); + return SR_NOT_FOUND; + } +} + + +SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length) +{ + if (text->selection.set()) + text->cursor = text->selection.end; + + bv->toggleSelection(); + text->clearSelection(); + + return nextChange(bv, text, length); +} + + +bool findNextChange(BufferView * bv) +{ + if (!bv->available()) + return false; + + bv->hideCursor(); + bv->update(bv->getLyXText(), BufferView::SELECT | BufferView::FITCUR); + + pos_type length; + + if (bv->theLockingInset()) { + bool found = bv->theLockingInset()->nextChange(bv, length); + + // We found the stuff inside the inset so we don't have to + // do anything as the inset did all the update for us! + if (found) + return true; + + // We now are in the main text but if we did a forward + // search we have to put the cursor behind the inset. + bv->text->cursorRight(bv, true); + } + // If we arrive here we are in the main text again so we + // just start searching from the root LyXText at the position + // we are! + LyXText * text = bv->text; + + if (text->selection.set()) + text->cursor = text->selection.end; + + bv->toggleSelection(); + text->clearSelection(); + + SearchResult result = nextChange(bv, text, length); + + lyxerr << "Result is " << result << endl; + + bool found = true; + + // If we found the cursor inside an inset we will get back + // SR_FOUND_NOUPDATE and we don't have to do anything as the + // inset did it already. + if (result == SR_FOUND) { + bv->unlockInset(bv->theLockingInset()); + bv->update(text, BufferView::SELECT|BufferView::FITCUR); + text->setSelectionRange(bv, length); + bv->toggleSelection(false); + bv->update(text, BufferView::SELECT|BufferView::FITCUR); + } else if (result == SR_NOT_FOUND) { + bv->unlockInset(bv->theLockingInset()); + bv->update(text, BufferView::SELECT|BufferView::FITCUR); + found = false; + } + + return found; +} + } // end lyxfind namespace diff --git a/src/lyxfind.h b/src/lyxfind.h index f86b8dec85..7415a466bc 100644 --- a/src/lyxfind.h +++ b/src/lyxfind.h @@ -7,6 +7,7 @@ #endif #include "LString.h" +#include "support/types.h" class BufferView; class LyXText; @@ -49,5 +50,13 @@ SearchResult LyXFind(BufferView *, LyXText * text, string const & searchstr, bool forward, bool casesens = true, bool matchwrd = false); +/// find the next change in the buffer +bool findNextChange(BufferView * bv); + +SearchResult findNextChange(BufferView * bv, LyXText * text, lyx::pos_type & length); + +SearchResult nextChange(BufferView * bv, LyXText * text, lyx::pos_type & length); + } // end namespace LyXFind -#endif + +#endif // LYXFIND_H diff --git a/src/lyxfunc.C b/src/lyxfunc.C index bd5310b809..23979786b8 100644 --- a/src/lyxfunc.C +++ b/src/lyxfunc.C @@ -462,6 +462,13 @@ FuncStatus LyXFunc::getStatus(FuncRequest const & ev) const disable = !view()-> isSavedPosition(strToUnsignedInt(ev.argument)); break; + case LFUN_MERGE_CHANGES: + case LFUN_ACCEPT_CHANGE: + case LFUN_REJECT_CHANGE: + case LFUN_ACCEPT_ALL_CHANGES: + case LFUN_REJECT_ALL_CHANGES: + disable = !buf->params.tracking_changes; + break; case LFUN_INSET_TOGGLE: { LyXText * lt = view()->getLyXText(); disable = !(isEditableInset(lt->getInset()) @@ -624,6 +631,9 @@ FuncStatus LyXFunc::getStatus(FuncRequest const & ev) const if (ev.argument == buf->fileName()) flag.setOnOff(true); break; + case LFUN_TRACK_CHANGES: + flag.setOnOff(buf->params.tracking_changes); + break; default: break; } diff --git a/src/lyxrc.C b/src/lyxrc.C index 4db42ebe7e..0d45db3603 100644 --- a/src/lyxrc.C +++ b/src/lyxrc.C @@ -1,12 +1,10 @@ -/* This file is part of - * ====================================================== +/** + * \file lyxrc.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. * - * LyX, The Document Processor - * - * Copyright 1995 Matthias Ettrich - * Copyright 1995-2001 The LyX Team. - * - * ====================================================== */ + * Full author contact details are available in file CREDITS + */ #include @@ -27,6 +25,8 @@ #include "intl.h" #include "support/path.h" #include "support/filetools.h" +#include "support/LAssert.h" +#include "support/userinfo.h" #include "converter.h" #include "gettext.h" #include "lyxlex.h" @@ -148,6 +148,8 @@ keyword_item lyxrcTags[] = { { "\\use_pspell", LyXRC::RC_USE_PSPELL }, #endif { "\\use_tempdir", LyXRC::RC_USETEMPDIR }, + { "\\user_email", LyXRC::RC_USER_EMAIL }, + { "\\user_name", LyXRC::RC_USER_NAME }, { "\\view_dvi_paper_option", LyXRC::RC_VIEWDVI_PAPEROPTION }, { "\\viewer" ,LyXRC::RC_VIEWER}, { "\\wheel_jump", LyXRC::RC_WHEEL_JUMP } @@ -264,6 +266,13 @@ void LyXRC::setDefaults() { // should be moved from the LyXRC class). use_gui = true; pdf_mode = false; + + user_name = lyx::user_name(); + + user_email = lyx::user_email(); + + if (user_email.empty()) + user_email = _("email address unknown"); } @@ -1094,6 +1103,16 @@ int LyXRC::read(string const & filename) } break; + case RC_USER_NAME: + if (lexrc.next()) + user_name = lexrc.getString(); + break; + + case RC_USER_EMAIL: + if (lexrc.next()) + user_email = lexrc.getString(); + break; + case RC_LAST: break; // this is just a dummy } } @@ -1261,6 +1280,12 @@ void LyXRC::output(ostream & os) const << '\n'; } + case RC_USER_NAME: + os << "\\user_name \"" << user_name << "\"\n"; + + case RC_USER_EMAIL: + os << "\\user_email " << user_email << "\n"; + case RC_SHOW_BANNER: if (show_banner != system_lyxrc.show_banner) { os << "\\show_banner " << tostr(show_banner) << '\n'; @@ -1798,6 +1823,7 @@ void LyXRC::output(ostream & os) const if (!converters.getConverter(cit->from, cit->to)) os << "\\converter \"" << cit->from << "\" \"" << cit->to << "\" \"\" \"\"\n"; + } os.flush(); } diff --git a/src/lyxrc.h b/src/lyxrc.h index cf497b8af2..6c0648555b 100644 --- a/src/lyxrc.h +++ b/src/lyxrc.h @@ -1,13 +1,11 @@ // -*- C++ -*- -/* This file is part of - * ====================================================== +/** + * \file lyxrc.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. * - * LyX, The Document Processor - * - * Copyright 1995 Matthias Ettrich - * Copyright 1995-2001 The LyX Team. - * - * ====================================================== */ + * Full author contact details are available in file CREDITS + */ #ifndef LYXRC_H #define LYXRC_H @@ -127,6 +125,8 @@ enum LyXRCTags { #ifdef USE_PSPELL RC_USE_PSPELL, #endif + RC_USER_NAME, + RC_USER_EMAIL, RC_LAST }; @@ -357,6 +357,10 @@ enum LyXRCTags { bool preview_hashed_labels; /// float preview_scale_factor; + /// user name + string user_name; + /// user email + string user_email; private: /// Is a bind file already (or currently) read? diff --git a/src/lyxrow.C b/src/lyxrow.C index 3dbe39095f..804cbc7e99 100644 --- a/src/lyxrow.C +++ b/src/lyxrow.C @@ -83,6 +83,18 @@ unsigned short Row::ascent_of_text() const } +void Row::top_of_text(unsigned int top) +{ + top_of_text_ = top; +} + + +unsigned int Row::top_of_text() const +{ + return top_of_text_; +} + + void Row::baseline(unsigned int b) { baseline_ = b; diff --git a/src/lyxrow.h b/src/lyxrow.h index e2593fdc7c..7b6fc124cb 100644 --- a/src/lyxrow.h +++ b/src/lyxrow.h @@ -53,6 +53,10 @@ public: /// unsigned short ascent_of_text() const; /// + void top_of_text(unsigned int top); + /// + unsigned int top_of_text() const; + /// void baseline(unsigned int b); /// unsigned int baseline() const; @@ -76,8 +80,10 @@ private: unsigned short height_; /// unsigned int width_; - /// + /// ascent from baseline including prelude space unsigned short ascent_of_text_; + /// the top of the real text in the row + unsigned int top_of_text_; /// unsigned int baseline_; /// diff --git a/src/lyxtext.h b/src/lyxtext.h index e3da0556bc..7135568767 100644 --- a/src/lyxtext.h +++ b/src/lyxtext.h @@ -281,6 +281,12 @@ public: /// returns the inset at cursor (if it exists), 0 otherwise Inset * getInset() const; + /// accept selected change + void acceptChange(BufferView * bv); + + /// reject selected change + void rejectChange(BufferView * bv); + /** 'selects" the next word, where the cursor is not in and returns this word as string. THe cursor will be moved to the beginning of this word. @@ -408,10 +414,11 @@ public: /* these things are for search and replace */ - /** sets the selection over the number of characters of string, - no check!! - */ - void setSelectionOverString(BufferView *, string const & str); + /** + * Sets the selection from the current cursor position to length + * characters to the right. No safety checks. + */ + void setSelectionRange(BufferView *, lyx::pos_type length); /** simple replacing. The font of the first selected character is used @@ -574,6 +581,9 @@ private: /// paint the selection background void paintRowSelection(DrawRowParams & p); + /// paint change bar + void paintChangeBar(DrawRowParams & p); + /// paint appendix marker void paintRowAppendix(DrawRowParams & p); diff --git a/src/paragraph.C b/src/paragraph.C index bd4a227814..c541fd7ea7 100644 --- a/src/paragraph.C +++ b/src/paragraph.C @@ -28,6 +28,7 @@ #include "encoding.h" #include "ParameterStruct.h" #include "gettext.h" +#include "changes.h" #include "insets/insetbib.h" #include "insets/insetoptarg.h" @@ -42,6 +43,7 @@ #include #include #include +#include using std::ostream; using std::endl; @@ -242,6 +244,9 @@ void Paragraph::write(Buffer const * buf, ostream & os, LyXFont font1(LyXFont::ALL_INHERIT, bparams.language); + Change running_change = Change(Change::UNCHANGED); + lyx::time_type const curtime(lyx::current_time()); + int column = 0; for (pos_type i = 0; i < size(); ++i) { if (!i) { @@ -249,6 +254,10 @@ void Paragraph::write(Buffer const * buf, ostream & os, column = 0; } + Change change = pimpl_->lookupChangeFull(i); + Changes::lyxMarkChange(os, column, curtime, running_change, change); + running_change = change; + // Write font changes LyXFont font2 = getFontSettings(bparams, i); if (font2 != font1) { @@ -312,6 +321,15 @@ void Paragraph::write(Buffer const * buf, ostream & os, break; } } + + // 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)); } @@ -389,6 +407,12 @@ void Paragraph::erase(pos_type pos) } +bool Paragraph::erase(pos_type start, pos_type end) +{ + return pimpl_->erase(start, end); +} + + bool Paragraph::checkInsertChar(LyXFont & font) { if (pimpl_->inset_owner) @@ -405,9 +429,9 @@ void Paragraph::insertChar(pos_type pos, Paragraph::value_type c) void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, - LyXFont const & font) + LyXFont const & font, Change change) { - pimpl_->insertChar(pos, c, font); + pimpl_->insertChar(pos, c, font, change); } @@ -418,9 +442,9 @@ void Paragraph::insertInset(pos_type pos, Inset * inset) } -void Paragraph::insertInset(pos_type pos, Inset * inset, LyXFont const & font) +void Paragraph::insertInset(pos_type pos, Inset * inset, LyXFont const & font, Change change) { - pimpl_->insertInset(pos, inset, font); + pimpl_->insertInset(pos, inset, font, change); } @@ -736,7 +760,7 @@ int Paragraph::stripLeadingSpaces() int i = 0; while (!empty() && (isNewline(0) || isLineSeparator(0))) { - erase(0); + pimpl_->eraseIntern(0); ++i; } @@ -1358,6 +1382,8 @@ bool Paragraph::simpleTeXOnePar(Buffer const * buf, // Do we have an open font change? bool open_font = false; + Change::Type running_change = Change::UNCHANGED; + texrow.start(this, 0); // if the paragraph is empty, the loop will not be entered at all @@ -1444,7 +1470,12 @@ bool Paragraph::simpleTeXOnePar(Buffer const * buf, running_font = font; open_font = true; } - + + Change::Type change = pimpl_->lookupChange(i); + + column += Changes::latexMarkChange(os, running_change, change); + running_change = change; + if (c == Paragraph::META_NEWLINE) { // newlines are handled differently here than // the default in SimpleTeXSpecialChars(). @@ -1474,10 +1505,14 @@ bool Paragraph::simpleTeXOnePar(Buffer const * buf, os, texrow, moving_arg, font, running_font, basefont, open_font, + running_change, *style, i, column, c); } } + column += Changes::latexMarkChange(os, + running_change, Change::UNCHANGED); + // If we have an open font definition, we have to close it if (open_font) { #ifdef FIXED_LANGUAGE_END_DETECTION @@ -1808,6 +1843,68 @@ void Paragraph::setContentsFromPar(Paragraph * par) } +void Paragraph::trackChanges(Change::Type type) +{ + pimpl_->trackChanges(type); +} + + +void Paragraph::untrackChanges() +{ + pimpl_->untrackChanges(); +} + + +void Paragraph::cleanChanges() +{ + pimpl_->cleanChanges(); +} + + +Change::Type Paragraph::lookupChange(lyx::pos_type pos) const +{ + lyx::Assert(!size() || pos < size()); + return pimpl_->lookupChange(pos); +} + + +Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const +{ + lyx::Assert(!size() || pos < size()); + return pimpl_->lookupChangeFull(pos); +} + + +bool Paragraph::isChanged(pos_type start, pos_type end) const +{ + return pimpl_->isChanged(start, end); +} + + +bool Paragraph::isChangeEdited(pos_type start, pos_type end) const +{ + return pimpl_->isChangeEdited(start, end); +} + + +void Paragraph::markErased() +{ + pimpl_->markErased(); +} + + +void Paragraph::acceptChange(pos_type start, pos_type end) +{ + return pimpl_->acceptChange(start, end); +} + + +void Paragraph::rejectChange(pos_type start, pos_type end) +{ + return pimpl_->rejectChange(start, end); +} + + lyx::pos_type Paragraph::size() const { return pimpl_->size(); diff --git a/src/paragraph.h b/src/paragraph.h index 4f49050a4d..3d1f8691fd 100644 --- a/src/paragraph.h +++ b/src/paragraph.h @@ -20,6 +20,7 @@ #include "insets/inset.h" // Just for Inset::Code #include "support/types.h" +#include "changes.h" #include "LString.h" @@ -165,6 +166,36 @@ public: /// Paragraph const * next() const; + /// initialise tracking for this par + void trackChanges(Change::Type = Change::UNCHANGED); + + /// stop tracking + void untrackChanges(); + + /// set entire paragraph to new text for change tracking + void cleanChanges(); + + /// look up change type at given pos + Change::Type lookupChange(lyx::pos_type pos) const; + + /// look up change at given pos + Change const lookupChangeFull(lyx::pos_type pos) const; + + /// is there a change within the given range ? + bool isChanged(lyx::pos_type start, lyx::pos_type end) const; + + /// is there a non-addition in this range ? + bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; + + /// accept change + void acceptChange(lyx::pos_type start, lyx::pos_type end); + + /// reject change + void rejectChange(lyx::pos_type start, lyx::pos_type end); + + /// mark whole par as erased + void markErased(); + /// void previous(Paragraph *); /// @@ -197,9 +228,13 @@ public: depth_type getMaxDepthAfter() const; /// void applyLayout(LyXLayout_ptr const & new_layout); - /// + + /// erase the char at the given position void erase(lyx::pos_type pos); - /** Get unistantiated font setting. Returns the difference + /// erase the given range. Returns true if actually erased. + bool erase(lyx::pos_type start, lyx::pos_type end); + + /** Get uninstantiated font setting. Returns the difference between the characters font and the layoutfont. This is what is stored in the fonttable */ @@ -234,13 +269,13 @@ public: /// void insertChar(lyx::pos_type pos, value_type c); /// - void insertChar(lyx::pos_type pos, value_type c, LyXFont const &); + void insertChar(lyx::pos_type pos, value_type c, LyXFont const &, Change change = Change(Change::INSERTED)); /// bool checkInsertChar(LyXFont &); /// void insertInset(lyx::pos_type pos, Inset * inset); /// - void insertInset(lyx::pos_type pos, Inset * inset, LyXFont const &); + void insertInset(lyx::pos_type pos, Inset * inset, LyXFont const &, Change change = Change(Change::INSERTED)); /// bool insetAllowed(Inset::Code code); /// @@ -294,6 +329,9 @@ public: /// //Counters & counters(); + friend void breakParagraph(BufferParams const & bparams, + Paragraph * par, lyx::pos_type pos, int flag); + private: /// LyXLayout_ptr layout_; @@ -312,4 +350,16 @@ private: Pimpl * pimpl_; }; -#endif + +inline bool isInsertedText(Paragraph const * par, lyx::pos_type pos) +{ + return par->lookupChange(pos) == Change::INSERTED; +} + + +inline bool isDeletedText(Paragraph const * par, lyx::pos_type pos) +{ + return par->lookupChange(pos) == Change::DELETED; +} + +#endif // PARAGRAPH_H diff --git a/src/paragraph_funcs.C b/src/paragraph_funcs.C index 249e43088e..18e6855c76 100644 --- a/src/paragraph_funcs.C +++ b/src/paragraph_funcs.C @@ -11,6 +11,7 @@ #include #include "paragraph_funcs.h" +#include "paragraph_pimpl.h" #include "buffer.h" #include "ParagraphParameters.h" #include "lyxtextclasslist.h" @@ -34,6 +35,9 @@ void breakParagraph(BufferParams const & bparams, // remember to set the inset_owner tmp->setInsetOwner(par->inInset()); + if (bparams.tracking_changes) + tmp->trackChanges(); + // this is an idea for a more userfriendly layout handling, I will // see what the users say @@ -73,13 +77,17 @@ void breakParagraph(BufferParams const & bparams, pos_type pos_end = par->size() - 1; pos_type i = pos; pos_type j = pos; + for (; i <= pos_end; ++i) { + Change::Type change(par->lookupChange(i)); par->cutIntoMinibuffer(bparams, i); - if (tmp->insertFromMinibuffer(j - pos)) + if (tmp->insertFromMinibuffer(j - pos)) { + tmp->pimpl_->setChange(j - pos, change); ++j; + } } for (i = pos_end; i >= pos; --i) { - par->erase(i); + par->pimpl_->eraseIntern(i); } } @@ -102,6 +110,15 @@ void breakParagraph(BufferParams const & bparams, par->setLabelWidthString(tmp->params().labelWidthString()); par->params().depth(tmp->params().depth()); } + + // subtle, but needed to get empty pars working right + if (bparams.tracking_changes) { + if (!par->size()) { + par->cleanChanges(); + } else if (!tmp->size()) { + tmp->cleanChanges(); + } + } } diff --git a/src/paragraph_pimpl.C b/src/paragraph_pimpl.C index 62fd7a49ac..eef7f3d8fb 100644 --- a/src/paragraph_pimpl.C +++ b/src/paragraph_pimpl.C @@ -74,12 +74,16 @@ Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner, bool same_ids) id_ = p.id_; else id_ = paragraph_id++; + + if (p.tracking()) + changes_.reset(new Changes(*p.changes_.get())); } void Paragraph::Pimpl::clear() { text.clear(); +#warning changes ? } @@ -87,9 +91,169 @@ void Paragraph::Pimpl::setContentsFromPar(Paragraph const * par) { lyx::Assert(par); text = par->pimpl_->text; + if (par->pimpl_->tracking()) { + changes_.reset(new Changes(*(par->pimpl_->changes_.get()))); + } } +void Paragraph::Pimpl::trackChanges(Change::Type type) +{ + if (tracking()) { + lyxerr[Debug::CHANGES] << "already tracking for par " << id_ << endl; + return; + } + + lyxerr[Debug::CHANGES] << "track changes for par " + << id_ << " type " << type << endl; + changes_.reset(new Changes(type)); + changes_->set(type, 0, size()); +} + + +void Paragraph::Pimpl::untrackChanges() +{ + changes_.reset(0); +} + + +void Paragraph::Pimpl::cleanChanges() +{ + // if we're not tracking, we don't want to reset... + if (!tracking()) + return; + + changes_.reset(new Changes(Change::INSERTED)); + changes_->set(Change::INSERTED, 0, size()); +} + + +bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const +{ + if (!tracking()) + return false; + + return changes_->isChange(start, end); +} + + +bool Paragraph::Pimpl::isChangeEdited(pos_type start, pos_type end) const +{ + if (!tracking()) + return false; + + return changes_->isChangeEdited(start, end); +} + + +void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type) +{ + if (!tracking()) + return; + + changes_->set(type, pos); +} + + +Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const +{ + if (!tracking()) + return Change::UNCHANGED; + + return changes_->lookup(pos); +} + + +Change const Paragraph::Pimpl::lookupChangeFull(pos_type pos) const +{ + if (!tracking()) + return Change(Change::UNCHANGED); + + return changes_->lookupFull(pos); +} + + +void Paragraph::Pimpl::markErased() +{ + lyx::Assert(tracking()); + + // FIXME: we should actually remove INSERTED chars. + // difficult because owning insettexts/tabulars need + // to update themselves when rows etc. change + changes_->set(Change::DELETED, 0, size()); + changes_->reset(Change::DELETED); +} + + +void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end) +{ + if (!tracking()) + return; + + if (!size()) { + changes_.reset(new Changes(Change::UNCHANGED)); + return; + } + + lyxerr << "acceptchange" << endl; + pos_type i = start; + + for (; i < end; ++i) { + switch (lookupChange(i)) { + case Change::UNCHANGED: + break; + + case Change::INSERTED: + changes_->set(Change::UNCHANGED, i); + break; + + case Change::DELETED: + eraseIntern(i); + changes_->erase(i); + --end; + --i; + break; + } + } + + lyxerr << "endacceptchange" << endl; + changes_->reset(Change::UNCHANGED); +} + + +void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end) +{ + if (!tracking()) + return; + + if (!size()) { + changes_.reset(new Changes(Change::UNCHANGED)); + return; + } + + pos_type i = start; + + for (; i < end; ++i) { + switch (lookupChange(i)) { + case Change::UNCHANGED: + break; + + case Change::INSERTED: + eraseIntern(i); + changes_->erase(i); + --end; + --i; + break; + + case Change::DELETED: + changes_->set(Change::UNCHANGED, i); + break; + } + } + changes_->reset(Change::UNCHANGED); +} + + Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const { // This is in the critical path for loading! @@ -109,15 +273,20 @@ Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const void Paragraph::Pimpl::setChar(pos_type pos, value_type c) { +#warning changes text[pos] = c; } void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, - LyXFont const & font) + LyXFont const & font, Change change) { lyx::Assert(pos <= size()); + if (tracking()) { + changes_->record(change, pos); + } + // This is actually very common when parsing buffers (and // maybe inserting ascii text) if (pos == size()) { @@ -147,12 +316,12 @@ void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, void Paragraph::Pimpl::insertInset(pos_type pos, - Inset * inset, LyXFont const & font) + Inset * inset, LyXFont const & font, Change change) { lyx::Assert(inset); lyx::Assert(pos <= size()); - insertChar(pos, META_INSET, font); + insertChar(pos, META_INSET, font, change); lyx::Assert(text[pos] == META_INSET); // Add a new entry in the insetlist. @@ -164,9 +333,31 @@ void Paragraph::Pimpl::insertInset(pos_type pos, } -void Paragraph::Pimpl::erase(pos_type pos) +bool Paragraph::Pimpl::erasePos(pos_type pos) { lyx::Assert(pos < size()); + + if (tracking()) { + Change::Type changetype(changes_->lookup(pos)); + changes_->record(Change(Change::DELETED), pos); + + // only allow the actual removal if it was /new/ text + if (changetype != Change::INSERTED) { + if (text[pos] == Paragraph::META_INSET) { + Inset * i(owner_->getInset(pos)); + i->markErased(); + } + return false; + } + } + + eraseIntern(pos); + return true; +} + + +void Paragraph::Pimpl::eraseIntern(pos_type pos) +{ // if it is an inset, delete the inset entry if (text[pos] == Paragraph::META_INSET) { owner_->insetlist.erase(pos); @@ -209,6 +400,30 @@ void Paragraph::Pimpl::erase(pos_type pos) } +void Paragraph::Pimpl::erase(pos_type pos) +{ + erasePos(pos); +} + + +bool Paragraph::Pimpl::erase(pos_type start, pos_type end) +{ + pos_type i = start; + pos_type count = end - start; + bool any_erased = false; + + while (count) { + if (!erasePos(i)) { + ++i; + } else { + any_erased = true; + } + --count; + } + return any_erased; +} + + void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow, pos_type const i, unsigned int & column, @@ -269,7 +484,7 @@ bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const return true; } - + void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf, BufferParams const & bparams, ostream & os, @@ -279,6 +494,7 @@ void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf, LyXFont & running_font, LyXFont & basefont, bool & open_font, + Change::Type & running_change, LyXLayout const & style, pos_type & i, unsigned int & column, @@ -294,17 +510,27 @@ void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf, switch (c) { case Paragraph::META_INSET: { Inset * inset = owner_->getInset(i); - if (inset) { - bool close = false; - int const len = os.tellp(); - //ostream::pos_type const len = os.tellp(); - if ((inset->lyxCode() == Inset::GRAPHICS_CODE - || inset->lyxCode() == Inset::MATH_CODE - || inset->lyxCode() == Inset::URL_CODE) - && running_font.isRightToLeft()) { - os << "\\L{"; - close = true; - } + + // FIXME: remove this check + if (!inset) + break; + + if (inset->isTextInset()) { + column += Changes::latexMarkChange(os, running_change, + Change::UNCHANGED); + running_change = Change::UNCHANGED; + } + + bool close = false; + int const len = os.tellp(); + //ostream::pos_type const len = os.tellp(); + if ((inset->lyxCode() == Inset::GRAPHICS_CODE + || inset->lyxCode() == Inset::MATH_CODE + || inset->lyxCode() == Inset::URL_CODE) + && running_font.isRightToLeft()) { + os << "\\L{"; + close = true; + } #ifdef WITH_WARNINGS #warning Bug: we can have an empty font change here! @@ -312,32 +538,31 @@ void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf, // right now, which means stupid latex code like \textsf{}. AFAIK, // this does not harm dvi output. A minor bug, thus (JMarc) #endif - // some insets cannot be inside a font change command - if (open_font && inset->noFontChange()) { - column +=running_font. - latexWriteEndChanges(os, - basefont, - basefont); - open_font = false; - basefont = owner_->getLayoutFont(bparams); - running_font = basefont; - } - - int tmp = inset->latex(buf, os, moving_arg, - style.free_spacing); - - if (close) - os << '}'; - - if (tmp) { - for (int j = 0; j < tmp; ++j) { - texrow.newline(); - } - texrow.start(owner_, i + 1); - column = 0; - } else { - column += int(os.tellp()) - len; + // some insets cannot be inside a font change command + if (open_font && inset->noFontChange()) { + column +=running_font. + latexWriteEndChanges(os, + basefont, + basefont); + open_font = false; + basefont = owner_->getLayoutFont(bparams); + running_font = basefont; + } + + int tmp = inset->latex(buf, os, moving_arg, + style.free_spacing); + + if (close) + os << '}'; + + if (tmp) { + for (int j = 0; j < tmp; ++j) { + texrow.newline(); } + texrow.start(owner_, i + 1); + column = 0; + } else { + column += int(os.tellp()) - len; } } break; diff --git a/src/paragraph_pimpl.h b/src/paragraph_pimpl.h index 7fd48bafad..9f03d86636 100644 --- a/src/paragraph_pimpl.h +++ b/src/paragraph_pimpl.h @@ -1,13 +1,9 @@ // -*- C++ -*- -/* This file is part of - * ====================================================== - * - * LyX, The Document Processor - * - * Copyright 1995 Matthias Ettrich - * Copyright 1995-2001 The LyX Team. - * - * ====================================================== */ +/** + * \file paragraph_pimpl.h + * Copyright 1995-2002 the LyX Team + * Read the file COPYING + */ #ifndef PARAGRAPH_PIMPL_H #define PARAGRAPH_PIMPL_H @@ -18,8 +14,11 @@ #include "paragraph.h" #include "ParagraphParameters.h" +#include "changes.h" #include "counters.h" +#include + class LyXLayout; struct Paragraph::Pimpl { @@ -42,16 +41,52 @@ struct Paragraph::Pimpl { void clear(); /// void setContentsFromPar(Paragraph const * par); + /// set tracking mode + void trackChanges(Change::Type type = Change::UNCHANGED); + /// stop tracking + void untrackChanges(); + /// set all text as new for change mode + void cleanChanges(); + /// look up change type at given pos + Change::Type lookupChange(lyx::pos_type pos) const; + /// look up change at given pos + Change const lookupChangeFull(lyx::pos_type pos) const; + /// is there a change in the given range ? + bool isChanged(lyx::pos_type start, lyx::pos_type end) const; + /// is there a non-addition in this range ? + bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; + + /// set change at pos + void setChange(lyx::pos_type pos, Change::Type type); + + /// mark as erased + void markErased(); + + /// accept change + void acceptChange(lyx::pos_type start, lyx::pos_type end); + + /// reject change + void rejectChange(lyx::pos_type start, lyx::pos_type end); + + /// are we tracking changes ? + bool tracking() const { + return changes_.get(); + } + /// value_type getChar(lyx::pos_type pos) const; /// void setChar(lyx::pos_type pos, value_type c); /// - void insertChar(lyx::pos_type pos, value_type c, LyXFont const & font); - /// - void insertInset(lyx::pos_type pos, Inset * inset, LyXFont const & font); + void insertChar(lyx::pos_type pos, value_type c, LyXFont const & font, Change change = Change(Change::INSERTED)); /// + void insertInset(lyx::pos_type pos, Inset * inset, LyXFont const & font, Change change = Change(Change::INSERTED)); + /// definite erase + void eraseIntern(lyx::pos_type pos); + /// erase the given position void erase(lyx::pos_type pos); + /// erase the given range + bool erase(lyx::pos_type start, lyx::pos_type end); /// LyXFont const realizeFont(LyXFont const & font, BufferParams const & bparams) const; @@ -115,6 +150,7 @@ struct Paragraph::Pimpl { typedef std::vector FontList; /// FontList fontlist; + /// Paragraph * TeXDeeper(Buffer const *, BufferParams const &, std::ostream &, TexRow & texrow); @@ -130,6 +166,7 @@ struct Paragraph::Pimpl { bool moving_arg, LyXFont & font, LyXFont & running_font, LyXFont & basefont, bool & open_font, + Change::Type & running_change, LyXLayout const & style, lyx::pos_type & i, unsigned int & column, value_type const c); @@ -148,9 +185,15 @@ struct Paragraph::Pimpl { ParagraphParameters params; private: + /// erase at the given position. Returns true if it was actually erased + bool erasePos(lyx::pos_type pos); + /// match a string against a particular point in the paragraph bool isTextAt(string const & str, lyx::pos_type pos) const; + /// for recording and looking up changes in revision tracking mode + boost::scoped_ptr changes_; + /// Who owns us? Paragraph * owner_; /// diff --git a/src/support/ChangeLog b/src/support/ChangeLog index b224f38221..c6c17a5782 100644 --- a/src/support/ChangeLog +++ b/src/support/ChangeLog @@ -1,3 +1,13 @@ +2003-02-08 John Levon + + * Makefile.am: + * lyxtime.h: + * lyxtime.C: add typedef for time_t, add current_time + + * Makefile.am: + * userinfo.h: + * userinfo.C: add + 2002-12-04 Jean-Marc Lasgouttes * filetools.C (getExtFromContents): remove detection of epsi diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 6323abc821..dc457d4748 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -43,6 +43,8 @@ libsupport_la_SOURCES = \ lyxfunctional.h \ lyxlib.h \ lyxmanip.h \ + lyxtime.C \ + lyxtime.h \ $(LYXSTRING) lyxsum.C \ mkdir.C \ nt_defines.h \ @@ -58,6 +60,8 @@ libsupport_la_SOURCES = \ sstream.h \ systemcall.C \ systemcall.h \ + userinfo.C \ + userinfo.h \ tempname.C \ textutils.h \ translator.h \ diff --git a/src/support/lyxtime.C b/src/support/lyxtime.C new file mode 100644 index 0000000000..45b0cbd6ca --- /dev/null +++ b/src/support/lyxtime.C @@ -0,0 +1,22 @@ +/** + * \file lyxtime.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#include + +#include "lyxtime.h" + +namespace lyx { + +time_type current_time() +{ + return time(0); +} + +} // namespace lyx diff --git a/src/support/lyxtime.h b/src/support/lyxtime.h new file mode 100644 index 0000000000..5af9a547c6 --- /dev/null +++ b/src/support/lyxtime.h @@ -0,0 +1,25 @@ +// -*- C++ -*- +/** + * \file lyxtime.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#ifndef LYXTIME_H +#define LYXTIME_H + +#include + +namespace lyx { + +typedef time_t time_type; + +time_type current_time(); + +}; // namespace lyx + +#endif // LYXTIME_H diff --git a/src/support/userinfo.C b/src/support/userinfo.C new file mode 100644 index 0000000000..17b8fc5c26 --- /dev/null +++ b/src/support/userinfo.C @@ -0,0 +1,44 @@ +/** + * \file userinfo.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#include + +#include "userinfo.h" +#include "LAssert.h" +#include "filetools.h" + +#include +#include +#include + +namespace lyx { + +string const user_name() +{ + struct passwd * pw(getpwuid(geteuid())); + lyx::Assert(pw); + + string name = pw->pw_gecos; + if (name.empty()) + name = pw->pw_name; + return name; +} + + +string const user_email() +{ + string email = GetEnv("EMAIL_ADDRESS"); + if (email.empty()) + email = GetEnv("EMAIL"); + return email; +} + + +} // namespace lyx diff --git a/src/support/userinfo.h b/src/support/userinfo.h new file mode 100644 index 0000000000..4e2042a145 --- /dev/null +++ b/src/support/userinfo.h @@ -0,0 +1,27 @@ +// -*- C++ -*- +/** + * \file userinfo.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author John Levon + * + * Full author contact details are available in file CREDITS + */ + +#ifndef USERINFO_H +#define USERINFO_H + +#include "LString.h" + +namespace lyx { + +/// return the current user's real name +string const user_name(); + +/// return the current user's e-mail address +string const user_email(); + +}; // namespace lyx + +#endif // USERINFO_H diff --git a/src/tabular.C b/src/tabular.C index c3255f0cea..a2cac72c87 100644 --- a/src/tabular.C +++ b/src/tabular.C @@ -270,7 +270,9 @@ void LyXTabular::AppendRow(BufferParams const & bp, int cell) cell_info = c_info; ++row; for (int j = 0; j < columns_; ++j) { - cell_info[row][j].inset.clear(); + cell_info[row][j].inset.clear(false); + if (bp.tracking_changes) + cell_info[row][j].inset.markNew(true); } #endif Reinit(); @@ -321,8 +323,9 @@ void LyXTabular::AppendColumn(BufferParams const & bp, int cell) cell_info = c_info; //++column; for (int i = 0; i < rows_; ++i) { - //cell_info[i][column].inset.clear(); - cell_info[i][column + 1].inset.clear(); + cell_info[i][column + 1].inset.clear(false); + if (bp.tracking_changes) + cell_info[i][column + 1].inset.markNew(true); } Reinit(); } @@ -1555,7 +1558,7 @@ void LyXTabular::SetMultiColumn(Buffer const * buffer, int cell, int number) cellinfo_of_cell(cell+i)->multicolumn = CELL_PART_OF_MULTICOLUMN; cellinfo_of_cell(cell)->inset.appendParagraphs(buffer->params, cellinfo_of_cell(cell+i)->inset.paragraph()); - cellinfo_of_cell(cell+i)->inset.clear(); + cellinfo_of_cell(cell+i)->inset.clear(false); } #else for (number--; number > 0; --number) { diff --git a/src/text.C b/src/text.C index 4b653f5d14..f76d936bc7 100644 --- a/src/text.C +++ b/src/text.C @@ -50,7 +50,12 @@ using lyx::pos_type; namespace { +/// top, right, bottom pixel margin int const PAPER_MARGIN = 20; +/// margin for changebar +int const CHANGEBAR_MARGIN = 10; +/// left margin +int const LEFT_MARGIN = PAPER_MARGIN + CHANGEBAR_MARGIN; } // namespace anon @@ -245,6 +250,9 @@ int LyXText::singleWidth(BufferView * bview, Paragraph * par, // Returns the paragraph position of the last character in the specified row pos_type LyXText::rowLast(Row const * row) const { + if (!row->par()->size()) + return 0; + if (!row->next() || row->next()->par() != row->par()) { return row->par()->size() - 1; } else { @@ -260,8 +268,8 @@ pos_type LyXText::rowLastPrintable(Row const * row) const Inset * ins; // we have to consider a space on the last position in this case! if (row->next() && row->par() == row->next()->par() && - row->next()->par()->getChar(last+1) == Paragraph::META_INSET && - (ins=row->next()->par()->getInset(last+1)) && + row->next()->par()->getChar(last + 1) == Paragraph::META_INSET && + (ins=row->next()->par()->getInset(last + 1)) && (ins->needFullRow() || ins->display())) { ignore_the_space_on_the_last_position = false; @@ -533,7 +541,7 @@ void LyXText::drawForeignMark(DrawRowParams & p, float const orig_x, LyXFont con if (orig_font.language() == p.bv->buffer()->params.language) return; - int const y = p.yo + p.row->height() - 1; + int const y = p.yo + p.row->baseline() + 1; p.pain->line(int(orig_x), y, int(p.x), y, LColor::language); } @@ -609,7 +617,7 @@ void LyXText::drawChars(DrawRowParams & p, pos_type & vpos, { pos_type pos = vis2log(vpos); pos_type const last = rowLastPrintable(p.row); - LyXFont const & orig_font = getFont(p.bv->buffer(), p.row->par(), pos); + LyXFont orig_font(getFont(p.bv->buffer(), p.row->par(), pos)); // first character string str; @@ -618,6 +626,10 @@ void LyXText::drawChars(DrawRowParams & p, pos_type & vpos, unsigned char c = str[0]; str[0] = transformChar(c, p.row->par(), pos); } + + bool prev_struckout(isDeletedText(p.row->par(), pos)); + bool prev_newtext(isInsertedText(p.row->par(), pos)); + ++vpos; // collect as much similar chars as we can @@ -627,6 +639,12 @@ void LyXText::drawChars(DrawRowParams & p, pos_type & vpos, if (!IsPrintableNonspace(c)) break; + if (prev_struckout != isDeletedText(p.row->par(), pos)) + break; + + if (prev_newtext != isInsertedText(p.row->par(), pos)) + break; + if (arabic && Encodings::IsComposeChar_arabic(c)) break; if (hebrew && Encodings::IsComposeChar_hebrew(c)) @@ -641,6 +659,12 @@ void LyXText::drawChars(DrawRowParams & p, pos_type & vpos, ++vpos; } + if (prev_struckout) { + orig_font.setColor(LColor::strikeout); + } else if (prev_newtext) { + orig_font.setColor(LColor::newtext); + } + // Draw text and set the new x position p.pain->text(int(p.x), p.yo + p.row->baseline(), str, orig_font); p.x += font_metrics::width(str, orig_font); @@ -702,7 +726,7 @@ int LyXText::leftMargin(BufferView * bview, Row const * row) const if ((row->par()->getChar(row->pos()) == Paragraph::META_INSET) && (ins=row->par()->getInset(row->pos())) && (ins->needFullRow() || ins->display())) - return PAPER_MARGIN; + return LEFT_MARGIN; LyXTextClass const & tclass = bview->buffer()->params.getLyXTextClass(); @@ -710,7 +734,7 @@ int LyXText::leftMargin(BufferView * bview, Row const * row) const string parindent = layout->parindent; - int x = PAPER_MARGIN; + int x = LEFT_MARGIN; x += font_metrics::signedWidth(tclass.leftmargin(), tclass.defaultfont()); @@ -1312,8 +1336,8 @@ void LyXText::setHeightOfRow(BufferView * bview, Row * row_ptr) const LyXLayout_ptr const & layout = firstpar->layout(); - // as max get the first character of this row then it can increes but not - // decrees the height. Just some point to start with so we don't have to + // as max get the first character of this row then it can increase but not + // decrease the height. Just some point to start with so we don't have to // do the assignment below too often. LyXFont font = getFont(bview->buffer(), par, row_ptr->pos()); LyXFont::FONT_SIZE const tmpsize = font.size(); @@ -1578,6 +1602,9 @@ void LyXText::setHeightOfRow(BufferView * bview, Row * row_ptr) const row_ptr->baseline(maxasc + labeladdon); height += row_ptr->height(); + + row_ptr->top_of_text(row_ptr->baseline() - font_metrics::maxAscent(font)); + float x = 0; if (layout->margintype != MARGIN_RIGHT_ADDRESS_BOX) { float dummy; @@ -1722,6 +1749,11 @@ void LyXText::breakAgainOneRow(BufferView * bview, Row * row) void LyXText::breakParagraph(BufferView * bview, char keep_layout) { + // allow only if at start or end, or all previous is new text + if (cursor.pos() && cursor.pos() != cursor.par()->size() + && cursor.par()->isChangeEdited(0, cursor.pos())) + return; + LyXTextClass const & tclass = bview->buffer()->params.getLyXTextClass(); LyXLayout_ptr const & layout = cursor.par()->layout(); @@ -2386,6 +2418,48 @@ bool LyXText::selectWordWhenUnderCursor(BufferView * bview, } +void LyXText::acceptChange(BufferView * bv) +{ + if (!selection.set() && cursor.par()->size()) + return; + + bv->hideCursor(); + + if (selection.start.par() == selection.end.par()) { + LyXCursor & startc = selection.start; + LyXCursor & endc = selection.end; + setUndo(bv, Undo::INSERT, startc.par(), startc.par()->next()); + startc.par()->acceptChange(startc.pos(), endc.pos()); + finishUndo(); + clearSelection(); + redoParagraphs(bv, startc, startc.par()->next()); + setCursorIntern(bv, startc.par(), 0); + } +#warning handle multi par selection +} + + +void LyXText::rejectChange(BufferView * bv) +{ + if (!selection.set() && cursor.par()->size()) + return; + + bv->hideCursor(); + + if (selection.start.par() == selection.end.par()) { + LyXCursor & startc = selection.start; + LyXCursor & endc = selection.end; + setUndo(bv, Undo::INSERT, startc.par(), startc.par()->next()); + startc.par()->rejectChange(startc.pos(), endc.pos()); + finishUndo(); + clearSelection(); + redoParagraphs(bv, startc, startc.par()->next()); + setCursorIntern(bv, startc.par(), 0); + } +#warning handle multi par selection +} + + // This function is only used by the spellchecker for NextWord(). // It doesn't handle LYX_ACCENTs and probably never will. WordLangTuple const @@ -2419,24 +2493,32 @@ LyXText::selectNextWordToSpellcheck(BufferView * bview, float & value) const } // Now, skip until we have real text (will jump paragraphs) - while ((cursor.par()->size() > cursor.pos() - && (!cursor.par()->isLetter(cursor.pos())) - && (!cursor.par()->isInset(cursor.pos()) || - !cursor.par()->getInset(cursor.pos())->allowSpellcheck())) - || (cursor.par()->size() == cursor.pos() - && cursor.par()->next())) - { - if (cursor.pos() == cursor.par()->size()) { - cursor.par(cursor.par()->next()); - cursor.pos(0); - } else - cursor.pos(cursor.pos() + 1); - } + while (1) { + Paragraph * cpar(cursor.par()); + pos_type const cpos(cursor.pos()); + + if (cpos == cpar->size()) { + if (cpar->next()) { + cursor.par(cpar->next()); + cursor.pos(0); + continue; + } + break; + } + bool const is_bad_inset(cpar->isInset(cpos) + && !cpar->getInset(cpos)->allowSpellcheck()); + + if (cpar->isLetter(cpos) && !isDeletedText(cpar, cpos) + && !is_bad_inset) + break; + + cursor.pos(cpos + 1); + } + // now check if we hit an inset so it has to be a inset containing text! if (cursor.pos() < cursor.par()->size() && - cursor.par()->isInset(cursor.pos())) - { + cursor.par()->isInset(cursor.pos())) { // lock the inset! cursor.par()->getInset(cursor.pos())->edit(bview); // now call us again to do the above trick @@ -2459,7 +2541,8 @@ LyXText::selectNextWordToSpellcheck(BufferView * bview, float & value) const // and find the end of the word (insets like optional hyphens // and ligature break are part of a word) while (cursor.pos() < cursor.par()->size() - && (cursor.par()->isLetter(cursor.pos()))) + && cursor.par()->isLetter(cursor.pos()) + && !isDeletedText(cursor.par(), cursor.pos())) cursor.pos(cursor.pos() + 1); // Finally, we copy the word to a string and return it @@ -2624,6 +2707,7 @@ void LyXText::changeRegionCase(BufferView * bview, break; } } +#warning changes par->setChar(pos, c); checkParagraph(bview, par, pos); @@ -2646,24 +2730,32 @@ void LyXText::transposeChars(BufferView & bview) pos_type tmppos = cursor.pos(); // First decide if it is possible to transpose at all + + if (tmppos == 0 || tmppos == tmppar->size()) + return; - // We are at the beginning of a paragraph. - if (tmppos == 0) return; - - // We are at the end of a paragraph. - if (tmppos == tmppar->size() - 1) return; + if (isDeletedText(tmppar, tmppos - 1) + || isDeletedText(tmppar, tmppos)) + return; unsigned char c1 = tmppar->getChar(tmppos); unsigned char c2 = tmppar->getChar(tmppos - 1); - if (c1 != Paragraph::META_INSET - && c2 != Paragraph::META_INSET) { - tmppar->setChar(tmppos, c2); - tmppar->setChar(tmppos - 1, c1); - } // We should have an implementation that handles insets // as well, but that will have to come later. (Lgb) - checkParagraph(const_cast(&bview), tmppar, tmppos); + if (c1 == Paragraph::META_INSET || c2 == Paragraph::META_INSET) + return; + + bool const erased = tmppar->erase(tmppos - 1, tmppos + 1); + pos_type const ipos(erased ? tmppos - 1 : tmppos + 1); + + tmppar->insertChar(ipos, c1); + tmppar->insertChar(ipos + 1, c2); + + /* fugly */ + BufferView * bv(const_cast(&bview)); + + checkParagraph(bv, tmppar, tmppos); } @@ -2717,6 +2809,10 @@ void LyXText::backspace(BufferView * bview) // 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 + if (cursor.par()->isChangeEdited(0, cursor.par()->size())) + return; + // we may paste some paragraphs // is it an empty paragraph? @@ -3188,6 +3284,23 @@ void LyXText::paintRowSelection(DrawRowParams & p) } +void LyXText::paintChangeBar(DrawRowParams & p) +{ + pos_type const start = p.row->pos(); + pos_type const end = rowLastPrintable(p.row); + + if (!p.row->par()->isChanged(start, end)) + return; + + int const height = (p.row->next() + ? p.row->height() + p.row->next()->top_of_text() + : p.row->baseline()); + + p.pain->fillRectangle(4, p.yo, 5, + height, LColor::changebar); +} + + void LyXText::paintRowAppendix(DrawRowParams & p) { // FIXME: can be just p.width ? @@ -3216,7 +3329,10 @@ void LyXText::paintRowDepthBar(DrawRowParams & p) next_depth = p.row->next()->par()->getDepth(); for (Paragraph::depth_type i = 1; i <= depth; ++i) { - int const x = (PAPER_MARGIN / 5) * i + p.xo; + int x = (PAPER_MARGIN / 5) * i + p.xo; + // only consider the changebar space if we're drawing outer left + if (!p.xo) + x += CHANGEBAR_MARGIN; int const h = p.yo + p.row->height() - 1 - (i - next_depth - 1) * 3; p.pain->line(x, p.yo, x, h, LColor::depthbar); @@ -3572,7 +3688,7 @@ void LyXText::paintLastRow(DrawRowParams & p) LyXFont const font = getLabelFont(buffer, par); int const size = int(0.75 * font_metrics::maxAscent(font)); int const y = (p.yo + p.row->baseline()) - size; - int x = is_rtl ? PAPER_MARGIN : ww - PAPER_MARGIN - size; + int x = is_rtl ? LEFT_MARGIN : ww - PAPER_MARGIN - size; if (p.row->fill() <= size) x += (size - p.row->fill() + 1) * (is_rtl ? -1 : 1); @@ -3622,16 +3738,42 @@ void LyXText::paintRowText(DrawRowParams & p) LyXLayout_ptr const & layout = par->layout(); + bool running_strikeout = false; + bool is_struckout = false; + float last_strikeout_x = 0.0; + pos_type vpos = p.row->pos(); while (vpos <= last) { if (p.x > p.bv->workWidth()) break; pos_type pos = vis2log(vpos); + if (p.x + singleWidth(p.bv, par, pos) < 0) { p.x += singleWidth(p.bv, par, pos); ++vpos; continue; } + + is_struckout = isDeletedText(par, pos); + + if (is_struckout && !running_strikeout) { + running_strikeout = true; + last_strikeout_x = p.x; + } + + bool const highly_editable_inset = par->isInset(pos) + && isHighlyEditableInset(par->getInset(pos)); + + // if we reach the end of a struck out range, paint it + // we also don't paint across things like tables + if (running_strikeout && (highly_editable_inset || !is_struckout)) { + int const middle = p.yo + p.row->top_of_text() + + ((p.row->baseline() - p.row->top_of_text()) / 2); + p.pain->line(int(last_strikeout_x), middle, int(p.x), middle, + LColor::strikeout, Painter::line_solid, Painter::line_thin); + running_strikeout = false; + } + if (main_body > 0 && pos == main_body - 1) { int const lwidth = font_metrics::width(layout->labelsep, getLabelFont(buffer, par)); @@ -3681,6 +3823,15 @@ void LyXText::paintRowText(DrawRowParams & p) break; } } + + // if we reach the end of a struck out range, paint it + if (running_strikeout) { + int const middle = p.yo + p.row->top_of_text() + + ((p.row->baseline() - p.row->top_of_text()) / 2); + p.pain->line(int(last_strikeout_x), middle, int(p.x), middle, + LColor::strikeout, Painter::line_solid, Painter::line_thin); + running_strikeout = false; + } } @@ -3725,6 +3876,9 @@ void LyXText::getVisibleRow(BufferView * bv, int y_offset, int x_offset, // environment depth brackets paintRowDepthBar(p); + // changebar + paintChangeBar(p); + // draw any stuff wanted for a first row of a paragraph if (!row->pos()) { paintFirstRow(p); @@ -3778,6 +3932,12 @@ LyXText::getColumnNearX(BufferView * bview, Row * row, int & x, !row->par()->isLineSeparator(main_body - 1))) main_body = 0; + // check for empty row + if (!row->par()->size()) { + x = int(tmpx); + return 0; + } + while (vc <= last && tmpx <= x) { c = vis2log(vc); last_tmpx = tmpx; @@ -3822,9 +3982,7 @@ LyXText::getColumnNearX(BufferView * bview, Row * row, int & x, : false; // If lastrow is false, we don't need to compute // the value of rtl. - if (row->pos() > last) // Row is empty? - c = row->pos(); - else if (lastrow && + if (lastrow && ((rtl && left_side && vc == row->pos() && x < tmpx - 5) || (!rtl && !left_side && vc == last + 1 && x > tmpx + 5))) c = last + 1; diff --git a/src/text2.C b/src/text2.C index a4592c79ce..16c056c7b6 100644 --- a/src/text2.C +++ b/src/text2.C @@ -1493,7 +1493,8 @@ void LyXText::cutSelection(BufferView * bview, bool doclear, bool realcut) // cutSelection can invalidate the cursor so we need to set // it anew. (Lgb) - cursor = selection.start; + // we prefer the end for when tracking changes + cursor = selection.end; // need a valid cursor. (Lgb) clearSelection(); @@ -1557,14 +1558,13 @@ void LyXText::pasteSelection(BufferView * bview) } -// sets the selection over the number of characters of string, no check!! -void LyXText::setSelectionOverString(BufferView * bview, string const & str) +void LyXText::setSelectionRange(BufferView * bview, lyx::pos_type length) { - if (str.empty()) + if (!length) return; selection.cursor = cursor; - for (string::size_type i = 0; i < str.length(); ++i) + while (length--) cursorRight(bview); setSelection(bview); } @@ -1801,7 +1801,11 @@ void LyXText::setCursor(BufferView * bview, LyXCursor & cur, Paragraph * par, pos_type last = rowLastPrintable(old_row); - if (pos > last + 1) { + // None of these should happen, but we're scaredy-cats + if (pos > par->size()) { + pos = 0; + cur.pos(0); + } else if (pos > last + 1) { // This shouldn't happen. pos = last + 1; cur.pos(pos);