commit 71623b88b2c613dd4ab826a9783a53e840bcd6e1
Author: Guillaume Munch <g...@lyx.org>
Date:   Sat Feb 18 19:12:55 2017 +0100

    Generalise the deletion protection mechanism from math to text (#9540)
    
    Now backspace and delete in text will select non-empty math and text insets
    before deleting them. This is consistent with what happens in math already.
    
    This is implemented for InsetText as well but can be disabled in case of
    negative feedback.
    
    This can be set for any sort of inset with the new virtual method
    Inset::confirmDeletion.
    
    New option "force" for the LFUN_*_DELETE_* commands, that bypasses the
    confirmDeletion check.
---
 src/Cursor.cpp               |   43 ++++++++++++++++++++---------------------
 src/Cursor.h                 |   16 ++++++++------
 src/DocIterator.cpp          |   18 +++++++++++++++++
 src/DocIterator.h            |    4 +++
 src/LyXAction.cpp            |   12 +++++++---
 src/Text.cpp                 |   16 +++++++++-----
 src/Text.h                   |    5 ++-
 src/Text3.cpp                |   18 +++++++++++++++-
 src/insets/Inset.h           |    4 +++
 src/insets/InsetText.h       |    4 +++
 src/mathed/InsetMathHull.h   |    2 +
 src/mathed/InsetMathNest.cpp |    8 +++---
 src/mathed/InsetMathNest.h   |    3 ++
 13 files changed, 106 insertions(+), 47 deletions(-)

diff --git a/src/Cursor.cpp b/src/Cursor.cpp
index 378a138..05389d9 100644
--- a/src/Cursor.cpp
+++ b/src/Cursor.cpp
@@ -551,24 +551,6 @@ void Cursor::checkNewWordPosition()
 }
 
 
-bool Cursor::posBackward()
-{
-       if (pos() == 0)
-               return false;
-       --pos();
-       return true;
-}
-
-
-bool Cursor::posForward()
-{
-       if (pos() == lastpos())
-               return false;
-       ++pos();
-       return true;
-}
-
-
 bool Cursor::posVisRight(bool skip_inset)
 {
        Cursor new_cur = *this; // where we will move to
@@ -1301,7 +1283,7 @@ void Cursor::insert(MathData const & ar)
 }
 
 
-bool Cursor::backspace()
+bool Cursor::backspace(bool const force)
 {
        if (selection()) {
                cap::eraseSelection(*this);
@@ -1337,7 +1319,7 @@ bool Cursor::backspace()
                }
        }
 
-       if (pos() != 0 && prevAtom()->nargs() > 0) {
+       if (pos() != 0 && !force && prevAtom()->confirmDeletion()) {
                // let's require two backspaces for 'big stuff' and
                // highlight on the first
                resetAnchor();
@@ -1351,7 +1333,7 @@ bool Cursor::backspace()
 }
 
 
-bool Cursor::erase()
+bool Cursor::erase(bool const force)
 {
        if (inMacroMode())
                return true;
@@ -1386,7 +1368,7 @@ bool Cursor::erase()
        }
 
        // 'clever' UI hack: only erase large items if previously slected
-       if (pos() != lastpos() && nextAtom()->nargs() > 0) {
+       if (pos() != lastpos() && !force && nextAtom()->confirmDeletion()) {
                resetAnchor();
                selection(true);
                ++pos();
@@ -2436,4 +2418,21 @@ void Cursor::checkBufferStructure()
 }
 
 
+bool Cursor::confirmDeletion(bool const before) const
+{
+       if (!selection()) {
+               if (Inset const * inset = before ? prevInset() : nextInset())
+                       return inset->confirmDeletion();
+       } else {
+               DocIterator dit = selectionBegin();
+               DocIterator const sel_end = selectionEnd();
+               for (; dit < sel_end; dit.posForward())
+                       if (Inset const * inset = dit.nextInset())
+                               if (inset->confirmDeletion())
+                                       return true;
+       }
+       return false;
+}
+
+
 } // namespace lyx
diff --git a/src/Cursor.h b/src/Cursor.h
index f5f1322..dbdd156 100644
--- a/src/Cursor.h
+++ b/src/Cursor.h
@@ -232,10 +232,6 @@ public:
        //
        // common part
        //
-       /// move one step backwards
-       bool posBackward();
-       /// move one step forward
-       bool posForward();
        /// move visually one step to the right
        /**
         * @note: This method may move into an inset unless skip_inset == true.
@@ -398,6 +394,11 @@ public:
        /// and after leaving the word the result is empty.
        DocIterator newWord() const { return new_word_; }
 
+       /// Return true if the next or previous inset has confirmDeletion 
depending
+       /// on the boolean before. If there is a selection, return true if at 
least
+       /// one inset in the selection has confirmDeletion.
+       bool confirmDeletion(bool before = false) const;
+
 public:
 //private:
        
@@ -454,9 +455,10 @@ public:
        ///
        void insert(MathData const &);
        /// return false for empty math insets
-       bool erase();
-       /// return false for empty math insets
-       bool backspace();
+       /// Use force to skip the confirmDeletion check.
+       bool erase(bool force = false);
+       bool backspace(bool force = false);
+
        /// move the cursor up by sending an internal LFUN_UP
        /// return true if fullscreen update is needed
        bool up();
diff --git a/src/DocIterator.cpp b/src/DocIterator.cpp
index fdb6187..51b44d8 100644
--- a/src/DocIterator.cpp
+++ b/src/DocIterator.cpp
@@ -318,6 +318,24 @@ Inset * DocIterator::innerInsetOfType(int code) const
 }
 
 
+bool DocIterator::posBackward()
+{
+       if (pos() == 0)
+               return false;
+       --pos();
+       return true;
+}
+
+
+bool DocIterator::posForward()
+{
+       if (pos() == lastpos())
+               return false;
+       ++pos();
+       return true;
+}
+
+
 // This duplicates code above, but is in the critical path.
 // So please think twice before adding stuff
 void DocIterator::forwardPos()
diff --git a/src/DocIterator.h b/src/DocIterator.h
index 425bfa4..cde876c 100644
--- a/src/DocIterator.h
+++ b/src/DocIterator.h
@@ -184,6 +184,10 @@ public:
        //
        // elementary moving
        //
+       /// move one step backwards
+       bool posBackward();
+       /// move one step forward
+       bool posForward();
        /**
         * move on one logical position, descend into nested insets
         * including collapsed insets
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index 9171e1c..77cf3ea 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -1080,7 +1080,8 @@ void LyXAction::init()
 /*!
  * \var lyx::FuncCode lyx::LFUN_CHAR_DELETE_BACKWARD
  * \li Action: Deletes one character in the backward direction (usually the 
"BackSpace" key).
- * \li Syntax: char-delete-backward
+ * \li Syntax: char-delete-backward [force]
+ * \li Params: force: Delete big insets, do no only select them.
  * \endvar
  */
                { LFUN_CHAR_DELETE_BACKWARD, "char-delete-backward", 
SingleParUpdate, Edit },
@@ -1088,7 +1089,8 @@ void LyXAction::init()
 /*!
  * \var lyx::FuncCode lyx::LFUN_CHAR_DELETE_FORWARD
  * \li Action: Deletes one character in the backward direction (usually the 
"Delete" key).
- * \li Syntax: char-delete-forward
+ * \li Syntax: char-delete-forward [force]
+ * \li Params: force: Delete big insets, do no only select them.
  * \endvar
  */
                { LFUN_CHAR_DELETE_FORWARD, "char-delete-forward", 
SingleParUpdate, Edit },
@@ -4007,7 +4009,8 @@ void LyXAction::init()
 /*!
  * \var lyx::FuncCode lyx::LFUN_WORD_DELETE_BACKWARD
  * \li Action: Deletes characters to the beginning of the word (usually the 
"C+BackSpace" key).
- * \li Syntax: word-delete-backward
+ * \li Syntax: word-delete-backward [force]
+ * \li Params: force: Delete big insets, do no only select them.
  * \endvar
  */
                { LFUN_WORD_DELETE_BACKWARD, "word-delete-backward", Noop, Edit 
},
@@ -4015,7 +4018,8 @@ void LyXAction::init()
 /*!
  * \var lyx::FuncCode lyx::LFUN_WORD_DELETE_FORWARD
  * \li Action: Deletes characters to the end of the word (usually the 
"C+Delete" key).
- * \li Syntax: word-delete-forward
+ * \li Syntax: word-delete-forward [force]
+ * \li Params: force: Delete big insets, do no only select them.
  * \endvar
  */
                { LFUN_WORD_DELETE_FORWARD, "word-delete-forward", Noop, Edit },
diff --git a/src/Text.cpp b/src/Text.cpp
index 8d08baa..c1af094 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -1466,7 +1466,7 @@ void Text::rejectChanges()
 }
 
 
-void Text::deleteWordForward(Cursor & cur)
+void Text::deleteWordForward(Cursor & cur, bool const force)
 {
        LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
@@ -1476,13 +1476,15 @@ void Text::deleteWordForward(Cursor & cur)
                cur.selection(true);
                cursorForwardOneWord(cur);
                cur.setSelection();
-               cutSelection(cur, true, false);
-               cur.checkBufferStructure();
+               if (force || !cur.confirmDeletion()) {
+                       cutSelection(cur, true, false);
+                       cur.checkBufferStructure();
+               }
        }
 }
 
 
-void Text::deleteWordBackward(Cursor & cur)
+void Text::deleteWordBackward(Cursor & cur, bool const force)
 {
        LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
@@ -1492,8 +1494,10 @@ void Text::deleteWordBackward(Cursor & cur)
                cur.selection(true);
                cursorBackwardOneWord(cur);
                cur.setSelection();
-               cutSelection(cur, true, false);
-               cur.checkBufferStructure();
+               if (force || !cur.confirmDeletion()) {
+                       cutSelection(cur, true, false);
+                       cur.checkBufferStructure();
+               }
        }
 }
 
diff --git a/src/Text.h b/src/Text.h
index f49c8e2..2174984 100644
--- a/src/Text.h
+++ b/src/Text.h
@@ -223,9 +223,10 @@ public:
        ///
        bool cursorVisRightOneWord(Cursor & cur);
        /// Delete from cursor up to the end of the current or next word.
-       void deleteWordForward(Cursor & cur);
+       /// Use force to skip the confirmDeletion check.
+       void deleteWordForward(Cursor & cur, bool force = false);
        /// Delete from cursor to start of current or prior word.
-       void deleteWordBackward(Cursor & cur);
+       void deleteWordBackward(Cursor & cur, bool force = false);
        ///
        bool cursorUpParagraph(Cursor & cur);
        ///
diff --git a/src/Text3.cpp b/src/Text3.cpp
index 3e6fdf0..cb5f0b5 100644
--- a/src/Text3.cpp
+++ b/src/Text3.cpp
@@ -586,7 +586,7 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
                if (cur.selection())
                        cutSelection(cur, true, false);
                else
-                       deleteWordForward(cur);
+                       deleteWordForward(cur, cmd.getArg(0) == "force");
                finishChange(cur, false);
                break;
 
@@ -594,7 +594,7 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
                if (cur.selection())
                        cutSelection(cur, true, false);
                else
-                       deleteWordBackward(cur);
+                       deleteWordBackward(cur, cmd.getArg(0) == "force");
                finishChange(cur, false);
                break;
 
@@ -1054,6 +1054,13 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
                        if (cur.pos() == cur.paragraph().size())
                                // Par boundary, force full-screen update
                                singleParUpdate = false;
+                       else if (cmd.getArg(0) != "force" && 
cur.confirmDeletion()) {
+                               cur.resetAnchor();
+                               cur.selection(true);
+                               cur.posForward();
+                               cur.setSelection();
+                               break;
+                       }
                        needsUpdate |= erase(cur);
                        cur.resetAnchor();
                } else {
@@ -1071,6 +1078,13 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
                                // Par boundary, full-screen update
                                if (par_boundary)
                                        singleParUpdate = false;
+                               else if (cmd.getArg(0) != "force" && 
cur.confirmDeletion(true)) {
+                                       cur.resetAnchor();
+                                       cur.selection(true);
+                                       cur.posBackward();
+                                       cur.setSelection();
+                                       break;
+                               }
                                needsUpdate |= backspace(cur);
                                cur.resetAnchor();
                                if (par_boundary && !first_par && cur.pos() > 0
diff --git a/src/insets/Inset.h b/src/insets/Inset.h
index 29c4395..3395e25 100644
--- a/src/insets/Inset.h
+++ b/src/insets/Inset.h
@@ -581,6 +581,10 @@ public:
        //
        enum { TEXT_TO_INSET_OFFSET = 4 };
 
+       /// Determine the action of backspace and delete: do we select instead 
of
+       /// deleting if not already selected?
+       virtual bool confirmDeletion() const { return false; }
+
 protected:
        /// Constructors
        Inset(Buffer * buf) : buffer_(buf) {}
diff --git a/src/insets/InsetText.h b/src/insets/InsetText.h
index d8dbc43..eb52bc8 100644
--- a/src/insets/InsetText.h
+++ b/src/insets/InsetText.h
@@ -219,6 +219,10 @@ public:
        std::string contextMenuName() const;
        ///
        void doDispatch(Cursor & cur, FuncRequest & cmd);
+
+       ///
+       bool confirmDeletion() const { return !text().empty(); }
+
 protected:
        ///
        void iterateForToc(DocIterator const & cdit, bool output_active,
diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h
index e04d68e..bc574b9 100644
--- a/src/mathed/InsetMathHull.h
+++ b/src/mathed/InsetMathHull.h
@@ -189,6 +189,8 @@ public:
        InsetCode lyxCode() const { return MATH_HULL_CODE; }
        ///
        bool canPaintChange(BufferView const &) const;
+       ///
+       bool confirmDeletion() const { return nargs() != 1 || !cell(0).empty(); 
}
 
 protected:
        InsetMathHull(InsetMathHull const &);
diff --git a/src/mathed/InsetMathNest.cpp b/src/mathed/InsetMathNest.cpp
index bdcd8a9..ceec393 100644
--- a/src/mathed/InsetMathNest.cpp
+++ b/src/mathed/InsetMathNest.cpp
@@ -862,8 +862,8 @@ void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & 
cmd)
                else if (!cur.inMacroMode())
                        cur.recordUndoSelection();
                // if the inset can not be removed from within, delete it
-               if (!cur.backspace()) {
-                       FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD);
+               if (!cur.backspace(cmd.getArg(0) == "force")) {
+                       FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD, 
"force");
                        cur.innerText()->dispatch(cur, cmd);
                }
                break;
@@ -876,8 +876,8 @@ void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & 
cmd)
                else
                        cur.recordUndoSelection();
                // if the inset can not be removed from within, delete it
-               if (!cur.erase()) {
-                       FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD);
+               if (!cur.erase(cmd.getArg(0) == "force")) {
+                       FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD, 
"force");
                        cur.innerText()->dispatch(cur, cmd);
                }
                break;
diff --git a/src/mathed/InsetMathNest.h b/src/mathed/InsetMathNest.h
index f142635..f1cfdd8 100644
--- a/src/mathed/InsetMathNest.h
+++ b/src/mathed/InsetMathNest.h
@@ -131,6 +131,9 @@ public:
        ///
        InsetCode lyxCode() const { return MATH_NEST_CODE; }
 
+       ///
+       bool confirmDeletion() const { return nargs() > 0; }
+
 protected:
        ///
        InsetMathNest(InsetMathNest const & inset);

Reply via email to