]> git.lyx.org Git - lyx.git/blobdiff - src/BufferView.cpp
Include docbook_copy.py in released tarball.
[lyx.git] / src / BufferView.cpp
index f7567bd4dd9d6c35807b09bc1761586f2d119653..bff44cdbf02ae98d4f8d15b789ea911cfa7bfc03 100644 (file)
 #include "insets/InsetRef.h"
 #include "insets/InsetText.h"
 
+#include "mathed/InsetMathNest.h"
+#include "mathed/InsetMathRef.h"
 #include "mathed/MathData.h"
+#include "mathed/MathRow.h"
 
 #include "frontends/alert.h"
 #include "frontends/CaretGeometry.h"
@@ -59,6 +62,7 @@
 #include "frontends/NullPainter.h"
 #include "frontends/Painter.h"
 #include "frontends/Selection.h"
+#include "frontends/Clipboard.h"
 
 #include "support/convert.h"
 #include "support/debug.h"
@@ -272,9 +276,6 @@ struct BufferView::Private
          */
        frontend::GuiBufferViewDelegate * gui_;
 
-       /// Cache for Find Next
-       FuncRequest search_request_cache_;
-
        ///
        map<string, Inset *> edited_insets_;
 
@@ -330,16 +331,20 @@ BufferView::~BufferView()
 }
 
 
-int BufferView::rightMargin() const
+int BufferView::defaultMargin() const
 {
        // The value used to be hardcoded to 10
-       int const default_margin = zoomedPixels(10);
+       return zoomedPixels(20);
+}
+
+
+int BufferView::rightMargin() const
+{
        // The additional test for the case the outliner is opened.
-       if (!full_screen_ || !lyxrc.full_screen_limit
-           || width_ < lyxrc.full_screen_width + 2 * default_margin)
-               return default_margin;
+       if (full_screen_ && lyxrc.full_screen_limit)
+               return max(defaultMargin(), (width_ - lyxrc.full_screen_width) / 2);
 
-       return (width_ - lyxrc.full_screen_width) / 2;
+       return defaultMargin();
 }
 
 
@@ -447,6 +452,26 @@ Buffer const & BufferView::buffer() const
 }
 
 
+docstring const & BufferView::searchRequestCache() const
+{
+       return theClipboard().getFindBuffer();
+}
+
+
+void BufferView::setSearchRequestCache(docstring const & text)
+{
+       bool casesensitive;
+       bool matchword;
+       bool forward;
+       bool wrap;
+       bool instant;
+       bool onlysel;
+       docstring const search = string2find(text, casesensitive, matchword,
+                                            forward, wrap, instant, onlysel);
+       theClipboard().setFindBuffer(search);
+}
+
+
 bool BufferView::needsFitCursor() const
 {
        if (cursorStatus(d->cursor_) == CUR_INSIDE) {
@@ -649,13 +674,53 @@ string BufferView::contextMenu(int x, int y) const
 
        // Get inset under mouse, if there is one.
        Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
-       if (covering_inset)
+       if (covering_inset) {
+               if (covering_inset->asInsetMath()) {
+                       CoordCache::Insets const & inset_cache =
+                               coordCache().getInsets();
+                       Inset const * inner_inset = mathContextMenu(
+                               covering_inset->asInsetMath()->asNestInset(),
+                               inset_cache, x, y);
+                       if (inner_inset)
+                               return inner_inset->contextMenu(*this, x, y);
+               }
                return covering_inset->contextMenu(*this, x, y);
+       }
 
        return buffer_.inset().contextMenu(*this, x, y);
 }
 
 
+Inset const * BufferView::mathContextMenu(InsetMathNest const * inset,
+               CoordCache::Insets const & inset_cache, int x, int y) const
+{
+       for (size_t i = 0; i < inset->nargs(); ++i) {
+               MathData const & ar = inset->cell(i);
+               for (size_t j = 0; j < ar.size(); ++j) {
+                       string const name = lyxerr.debugging(Debug::MATHED)
+                               ? insetName(ar[j].nucleus()->lyxCode())
+                               : string();
+                       LYXERR(Debug::MATHED, "Examining inset: " << name);
+                       if (!ar[j].nucleus()->contextMenuName().empty()) {
+                               if (inset_cache.covers(ar[j].nucleus(), x, y)) {
+                                       LYXERR(Debug::MATHED, "Hit inset: "
+                                              << name);
+                                       return ar[j].nucleus();
+                               }
+                       }
+                       InsetMathNest const * imn =
+                               ar[j].nucleus()->asNestInset();
+                       if (imn) {
+                               Inset const * inner =
+                                       mathContextMenu(imn, inset_cache, x, y);
+                               if (inner)
+                                       return inner;
+                       }
+               }
+       }
+       return nullptr;
+}
+
 
 void BufferView::scrollDocView(int const pixels, bool update)
 {
@@ -794,23 +859,24 @@ void BufferView::bookmarkEditPosition()
 
 void BufferView::saveBookmark(unsigned int idx)
 {
+       if (buffer().isInternal())
+               return;
+
        // tentatively save bookmark, id and pos will be used to
        // acturately locate a bookmark in a 'live' lyx session.
        // pit and pos will be updated with bottom level pit/pos
        // when lyx exits.
-       if (!buffer_.isInternal()) {
-               theSession().bookmarks().save(
-                       buffer_.fileName(),
-                       d->cursor_.bottom().pit(),
-                       d->cursor_.bottom().pos(),
-                       d->cursor_.paragraph().id(),
-                       d->cursor_.pos(),
-                       idx
-                       );
-               if (idx)
-                       // emit message signal.
-                       message(_("Save bookmark"));
-       }
+       theSession().bookmarks().save(
+               buffer_.fileName(),
+               d->cursor_.bottom().pit(),
+               d->cursor_.bottom().pos(),
+               d->cursor_.paragraph().id(),
+               d->cursor_.pos(),
+               idx
+       );
+       if (idx)
+               // emit message signal.
+               message(_("Save bookmark"));
 }
 
 
@@ -851,7 +917,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
        // restoration is inaccurate. If a bookmark was within an inset,
        // it will be restored to the left of the outmost inset that contains
        // the bookmark.
-       if (bottom_pit < int(buffer_.paragraphs().size())) {
+       if (!success && bottom_pit < int(buffer_.paragraphs().size())) {
                dit = doc_iterator_begin(&buffer_);
 
                dit.pit() = bottom_pit;
@@ -922,18 +988,17 @@ void BufferView::scrollToCursor()
 
 bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
 {
-       // We are not properly started yet, delay until resizing is
-       // done.
+       // We are not properly started yet, delay until resizing is done.
        if (height_ == 0)
                return false;
 
        if (recenter)
-         LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor");
+               LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor");
        else
-         LYXERR(Debug::SCROLLING, "scrolling to cursor");
+               LYXERR(Debug::SCROLLING, "scrolling to cursor");
 
        CursorSlice const & bot = dit.bottom();
-       TextMetrics & tm = d->text_metrics_[bot.text()];
+       TextMetrics & tm = textMetrics(bot.text());
 
        pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1);
        pos_type bot_pit = bot.pit();
@@ -954,10 +1019,11 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
                LBUFERR(!pm.rows().empty());
                // FIXME: smooth scrolling doesn't work in mathed.
                CursorSlice const & cs = dit.innerTextSlice();
-               int offset = coordOffset(dit).y_;
-               int ypos = pm.position() + offset;
+               int const ypos = pm.position() + coordOffset(dit).y_;
+               ParagraphMetrics const & inner_pm =
+                       textMetrics(cs.text()).parMetrics(cs.pit());
                Dimension const & row_dim =
-                       pm.getRow(cs.pos(), dit.boundary()).dim();
+                       inner_pm.getRow(cs.pos(), dit.boundary()).dim();
                int scrolled = 0;
                if (recenter)
                        scrolled = scroll(ypos - height_/2);
@@ -976,13 +1042,13 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
                // If the top part of the row falls of the screen, we scroll
                // up to align the top of the row with the top of the screen.
                else if (ypos - row_dim.ascent() < 0 && ypos < height_) {
-                       int ynew = row_dim.ascent();
+                       int const ynew = row_dim.ascent();
                        scrolled = scrollUp(ynew - ypos);
                }
 
                // If the bottom of the row falls of the screen, we scroll down.
                else if (ypos + row_dim.descent() > height_ && ypos > 0) {
-                       int ynew = height_ - row_dim.descent();
+                       int const ynew = height_ - row_dim.descent();
                        scrolled = scrollDown(ypos - ynew);
                }
 
@@ -996,12 +1062,14 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
 
        tm.redoParagraph(bot_pit);
        ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
-       int offset = coordOffset(dit).y_;
+       int const offset = coordOffset(dit).y_;
 
        d->anchor_pit_ = bot_pit;
        CursorSlice const & cs = dit.innerTextSlice();
+       ParagraphMetrics const & inner_pm =
+               textMetrics(cs.text()).parMetrics(cs.pit());
        Dimension const & row_dim =
-               pm.getRow(cs.pos(), dit.boundary()).dim();
+               inner_pm.getRow(cs.pos(), dit.boundary()).dim();
 
        if (recenter)
                d->anchor_ypos_ = height_/2;
@@ -1021,7 +1089,7 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
 void BufferView::makeDocumentClass()
 {
        DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
-       buffer_.params().makeDocumentClass();
+       buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal());
        updateDocumentClass(olddc);
 }
 
@@ -1152,6 +1220,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
        case LFUN_MARK_OFF:
        case LFUN_MARK_ON:
        case LFUN_MARK_TOGGLE:
+       case LFUN_SEARCH_STRING_SET:
        case LFUN_SCREEN_RECENTER:
        case LFUN_SCREEN_SHOW_CURSOR:
        case LFUN_BIBTEX_DATABASE_ADD:
@@ -1180,7 +1249,8 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 
        case LFUN_LABEL_GOTO:
                flag.setEnabled(!cmd.argument().empty()
-                   || getInsetByCode<InsetRef>(cur, REF_CODE));
+                   || getInsetByCode<InsetRef>(cur, REF_CODE)
+                   || getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE));
                break;
 
        case LFUN_CHANGES_MERGE:
@@ -1248,6 +1318,10 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
                break;
        }
 
+       case LFUN_COPY:
+               flag.setEnabled(cur.selection());
+               break;
+
        default:
                return false;
        }
@@ -1446,6 +1520,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
 
        case LFUN_BOOKMARK_SAVE:
+               dr.screenUpdate(Update::Force);
                saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
                break;
 
@@ -1466,6 +1541,12 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        // eventually call LFUN_PARAGRAPH_GOTO, but it seems best
                        // to have it here.
                        dr.screenUpdate(Update::Force | Update::FitCursor);
+               } else {
+                       InsetMathRef * minset =
+                               getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE);
+                       if (minset)
+                               lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO,
+                                                       minset->getTarget()));
                }
                break;
        }
@@ -1601,63 +1682,72 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
        case LFUN_WORD_FIND_FORWARD:
        case LFUN_WORD_FIND_BACKWARD: {
-               // FIXME THREAD
-               // Would it maybe be better if this variable were view specific anyway?
-               static docstring last_search;
                docstring searched_string;
 
                if (!cmd.argument().empty()) {
-                       last_search = cmd.argument();
+                       setSearchRequestCache(cmd.argument());
                        searched_string = cmd.argument();
                } else {
-                       searched_string = last_search;
+                       searched_string = searchRequestCache();
                }
 
                if (searched_string.empty())
                        break;
 
-               bool const fw = act == LFUN_WORD_FIND_FORWARD;
                docstring const data =
-                       find2string(searched_string, true, false, fw);
+                       find2string(searched_string, false, false,
+                                   act == LFUN_WORD_FIND_FORWARD, false, false, false);
                bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data));
                if (found)
                        dr.screenUpdate(Update::Force | Update::FitCursor);
+               else
+                       dr.setMessage(_("Search string not found!"));
                break;
        }
 
        case LFUN_WORD_FIND: {
-               FuncRequest req = cmd;
-               if (cmd.argument().empty() && !d->search_request_cache_.argument().empty())
-                       req = d->search_request_cache_;
-               if (req.argument().empty()) {
+               docstring arg = cmd.argument();
+               if (arg.empty())
+                       arg = searchRequestCache();
+               if (arg.empty()) {
                        lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace"));
                        break;
                }
-               if (lyxfind(this, req))
+               if (lyxfind(this, FuncRequest(act, arg)))
                        dr.screenUpdate(Update::Force | Update::FitCursor);
+               else
+                       dr.setMessage(_("Search string not found!"));
 
-               d->search_request_cache_ = req;
+               setSearchRequestCache(arg);
                break;
        }
 
-       case LFUN_WORD_REPLACE: {
-               bool has_deleted = false;
-               if (cur.selection()) {
-                       DocIterator beg = cur.selectionBegin();
-                       DocIterator end = cur.selectionEnd();
-                       if (beg.pit() == end.pit()) {
-                               for (pos_type p = beg.pos() ; p < end.pos() ; ++p) {
-                                       if (!cur.inMathed() && cur.paragraph().isDeleted(p)) {
-                                               has_deleted = true;
-                                               break;
-                                       }
-                               }
-                       }
+       case LFUN_SEARCH_STRING_SET: {
+               docstring pattern = cmd.argument();
+               if (!pattern.empty()) {
+                       setSearchRequestCache(pattern);
+                       break;
                }
-               if (lyxreplace(this, cmd, has_deleted)) {
+               if (cur.selection())
+                       pattern = cur.selectionAsString(false);
+               else {
+                       pos_type spos = cur.pos();
+                       cur.innerText()->selectWord(cur, WHOLE_WORD);
+                       pattern = cur.selectionAsString(false);
+                       cur.selection(false);
+                       cur.pos() = spos;
+               }
+               setSearchRequestCache(pattern);
+               break;
+       }
+
+       case LFUN_WORD_REPLACE: {
+               if (lyxreplace(this, cmd)) {
                        dr.forceBufferUpdate();
                        dr.screenUpdate(Update::Force | Update::FitCursor);
                }
+               else
+                       dr.setMessage(_("Search string not found!"));
                break;
        }
 
@@ -1830,7 +1920,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                cur.setCursor(doc_iterator_begin(cur.buffer()));
                cur.selHandle(false);
                // Force an immediate computation of metrics because we need it below
-               processUpdateFlags(Update::Force);
+               updateMetrics();
 
                d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_,
                        true, act == LFUN_SCREEN_UP);
@@ -2177,6 +2267,18 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
        }
 
+       case LFUN_COPY:
+               // With multi-cell table content, we pass down to the inset
+               if (cur.inTexted() && cur.selection()
+                   && cur.selectionBegin().idx() != cur.selectionEnd().idx()) {
+                       buffer_.dispatch(cmd, dr);
+                       dispatched = dr.dispatched();
+                       break;
+               }
+               cap::copySelection(cur);
+               cur.message(_("Copy"));
+               break;
+
        default:
                // OK, so try the Buffer itself...
                buffer_.dispatch(cmd, dr);
@@ -2285,12 +2387,50 @@ Inset const * BufferView::getCoveringInset(Text const & text,
 }
 
 
+Inset const * BufferView::clickableMathInset(InsetMathNest const * inset,
+               CoordCache::Insets const & inset_cache, int x, int y) const
+{
+       for (size_t i = 0; i < inset->nargs(); ++i) {
+               MathData const & ar = inset->cell(i);
+               for (size_t j = 0; j < ar.size(); ++j) {
+                       string const name = lyxerr.debugging(Debug::MATHED)
+                               ? insetName(ar[j].nucleus()->lyxCode())
+                               : string();
+                       LYXERR(Debug::MATHED, "Checking inset: " << name);
+                       if (ar[j].nucleus()->clickable(*this, x, y)) {
+                               if (inset_cache.covers(ar[j].nucleus(), x, y)) {
+                                       LYXERR(Debug::MATHED, "Clickable inset: "
+                                              << name);
+                                       return ar[j].nucleus();
+                               }
+                       }
+                       InsetMathNest const * imn =
+                               ar[j].nucleus()->asNestInset();
+                       if (imn) {
+                               Inset const * inner =
+                                       clickableMathInset(imn, inset_cache, x, y);
+                               if (inner)
+                                       return inner;
+                       }
+               }
+       }
+       return nullptr;
+}
+
+
 void BufferView::updateHoveredInset() const
 {
        // Get inset under mouse, if there is one.
        int const x = d->mouse_position_cache_.x_;
        int const y = d->mouse_position_cache_.y_;
        Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
+       if (covering_inset && covering_inset->asInsetMath()) {
+               Inset const * inner_inset = clickableMathInset(
+                               covering_inset->asInsetMath()->asNestInset(),
+                               coordCache().getInsets(), x, y);
+               if (inner_inset)
+                       covering_inset = inner_inset;
+       }
 
        d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y);
 
@@ -2411,7 +2551,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
        // Do we have a selection?
        theSelection().haveSelection(cursor().selection());
 
-       if (cur.needBufferUpdate()) {
+       if (cur.needBufferUpdate() || buffer().needUpdate()) {
                cur.clearBufferUpdate();
                buffer().updateBuffer();
        }
@@ -2937,7 +3077,8 @@ void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang)
                        buf.changeLanguage(buf.language(), d->cursor_.getFont().language());
                buffer_.undo().recordUndo(d->cursor_);
                cap::pasteParagraphList(d->cursor_, pars,
-                                            buf.params().documentClassPtr(), el);
+                                       buf.params().documentClassPtr(),
+                                       buf.params().authors(), el);
                res = _("Document %1$s inserted.");
        } else {
                res = _("Could not insert document %1$s");
@@ -3060,7 +3201,9 @@ void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
        } else {
                Font const font = cur.real_current_font;
                frontend::FontMetrics const & fm = theFontMetrics(font);
-               dim.wid = fm.lineWidth();
+               // lineWidth() can be 0 to mean 'thin line' on HiDpi, but the
+               // caret drawing code is not prepared for that.
+               dim.wid = max(fm.lineWidth(), 1);
                dim.asc = fm.maxAscent();
                dim.des = fm.maxDescent();
        }