X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=e9824097d429d6b6c746b5df24fb3cfdd659c56f;hb=f3370c3e6a0686b03769e48bcd3fb673925533a9;hp=4c57dfd479683af405ca5c8d00ae274a0289c758;hpb=07924ac300f68eaf4825951af6a6ac3c14d6edd6;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 4c57dfd479..e9824097d4 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -51,10 +51,10 @@ #include "TextMetrics.h" #include "TexRow.h" #include "TocBackend.h" -#include "VSpace.h" #include "WordLangTuple.h" #include "insets/InsetBibtex.h" +#include "insets/InsetCitation.h" #include "insets/InsetCommand.h" // ChangeRefs #include "insets/InsetExternal.h" #include "insets/InsetGraphics.h" @@ -62,6 +62,8 @@ #include "insets/InsetRef.h" #include "insets/InsetText.h" +#include "mathed/MathData.h" + #include "frontends/alert.h" #include "frontends/Application.h" #include "frontends/Delegates.h" @@ -69,13 +71,12 @@ #include "frontends/Painter.h" #include "frontends/Selection.h" -#include "graphics/Previews.h" - #include "support/convert.h" #include "support/debug.h" #include "support/ExceptionMessage.h" #include "support/filetools.h" #include "support/gettext.h" +#include "support/lassert.h" #include "support/lstrings.h" #include "support/Package.h" #include "support/types.h" @@ -108,9 +109,7 @@ T * getInsetByCode(Cursor const & cur, InsetCode code) } -bool findInset(DocIterator & dit, vector const & codes, - bool same_content); - +/// Note that comparing contents can only be used for InsetCommand bool findNextInset(DocIterator & dit, vector const & codes, docstring const & contents) { @@ -118,15 +117,17 @@ bool findNextInset(DocIterator & dit, vector const & codes, while (tmpdit) { Inset const * inset = tmpdit.nextInset(); - if (inset - && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end() - && (contents.empty() || - //FIXME: This static_cast seems very dangerous. Does this - // mean that if contents is not empty, we must only be - // looking for InsetCommand's ?? - static_cast(inset)->getFirstNonOptParam() == contents)) { - dit = tmpdit; - return true; + if (inset) { + bool const valid_code = std::find(codes.begin(), codes.end(), + inset->lyxCode()) != codes.end(); + InsetCommand const * ic = inset->asInsetCommand(); + bool const same_or_no_contents = contents.empty() + || (ic && (ic->getFirstNonOptParam() == contents)); + + if (valid_code && same_or_no_contents) { + dit = tmpdit; + return true; + } } tmpdit.forwardInset(); } @@ -136,6 +137,7 @@ bool findNextInset(DocIterator & dit, vector const & codes, /// Looks for next inset with one of the given codes. +/// Note that same_content can only be used for InsetCommand bool findInset(DocIterator & dit, vector const & codes, bool same_content) { @@ -145,14 +147,14 @@ bool findInset(DocIterator & dit, vector const & codes, if (!tmpdit) return false; - if (same_content) { - Inset const * inset = tmpdit.nextInset(); - if (inset - && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end()) { - //FIXME: This static_cast seems very dangerous. Does this - // mean that if contents is not empty, we must only be - // looking for InsetCommand's ?? - contents = static_cast(inset)->getFirstNonOptParam(); + Inset const * inset = tmpdit.nextInset(); + if (same_content && inset) { + InsetCommand const * ic = inset->asInsetCommand(); + if (ic) { + bool const valid_code = std::find(codes.begin(), codes.end(), + ic->lyxCode()) != codes.end(); + if (valid_code) + contents = ic->getFirstNonOptParam(); } } @@ -226,7 +228,7 @@ struct BufferView::Private Private(BufferView & bv): wh_(0), cursor_(bv), anchor_pit_(0), anchor_ypos_(0), inlineCompletionUniqueChars_(0), - last_inset_(0), clickable_inset_(false), + last_inset_(0), clickable_inset_(false), mouse_position_cache_(), bookmark_edit_position_(-1), gui_(0) {} @@ -269,7 +271,7 @@ struct BufferView::Private /** kept to send setMouseHover(false). * Not owned, so don't delete. */ - Inset * last_inset_; + Inset const * last_inset_; /// are we hovering something that we can click bool clickable_inset_; @@ -308,8 +310,7 @@ BufferView::BufferView(Buffer & buf) d->cursor_.resetAnchor(); d->cursor_.setCurrentFont(); - if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) - thePreviews().generateBufferPreviews(buffer_); + buffer_.updatePreviews(); } @@ -324,9 +325,9 @@ BufferView::~BufferView() fp.pit = d->cursor_.bottom().pit(); fp.pos = d->cursor_.bottom().pos(); theSession().lastFilePos().save(buffer_.fileName(), fp); - + if (d->last_inset_) - d->last_inset_->setMouseHover(this, false); + d->last_inset_->setMouseHover(this, false); delete d; } @@ -497,7 +498,7 @@ void BufferView::updateScrollbar() d->scrollbarParameters_.page_step = height_; Text & t = buffer_.text(); - TextMetrics & tm = d->text_metrics_[&t]; + TextMetrics & tm = d->text_metrics_[&t]; LYXERR(Debug::GUI, " Updating scrollbar: height: " << t.paragraphs().size() @@ -564,7 +565,7 @@ docstring BufferView::toolTip(int x, int y) const } -docstring BufferView::contextMenu(int x, int y) const +string BufferView::contextMenu(int x, int y) const { //If there is a selection, return the containing inset menu if (d->cursor_.selection()) @@ -657,7 +658,7 @@ void BufferView::setCursorFromScrollbar() case CUR_INSIDE: int const y = getPos(oldcur).y_; newy = min(last, max(y, first)); - if (y == newy) + if (y == newy) return; } // We reset the cursor because cursorStatus() does not @@ -670,7 +671,10 @@ void BufferView::setCursorFromScrollbar() // FIXME: Care about the d->cursor_ flags to redraw if needed Cursor old = d->cursor_; mouseSetCursor(cur); - bool badcursor = notifyCursorLeavesOrEnters(old, d->cursor_); + // the DEPM call in mouseSetCursor() might have destroyed the + // paragraph the cursor is in. + bool badcursor = old.fixIfBroken(); + badcursor |= notifyCursorLeavesOrEnters(old, d->cursor_); if (badcursor) d->cursor_.fixIfBroken(); } @@ -682,6 +686,10 @@ Change const BufferView::getCurrentChange() const return Change(Change::UNCHANGED); DocIterator dit = d->cursor_.selectionBegin(); + // The selected content might have been changed (see #7685) + while (dit.inMathed()) + // Find enclosing text cursor + dit.pop_back(); return dit.paragraph().lookupChange(dit.pos()); } @@ -770,7 +778,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // the bookmark. if (bottom_pit < int(buffer_.paragraphs().size())) { dit = doc_iterator_begin(&buffer_); - + dit.pit() = bottom_pit; dit.pos() = min(bottom_pos, dit.paragraph().size()); success = true; @@ -795,14 +803,12 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur) { - if (lyxrc.rtl_support) { - if (d->cursor_.real_current_font.isRightToLeft()) { - if (d->intl_.keymap == Intl::PRIMARY) - d->intl_.keyMapSec(); - } else { - if (d->intl_.keymap == Intl::SECONDARY) - d->intl_.keyMapPrim(); - } + if (d->cursor_.real_current_font.isRightToLeft()) { + if (d->intl_.keymap == Intl::PRIMARY) + d->intl_.keyMapSec(); + } else { + if (d->intl_.keymap == Intl::SECONDARY) + d->intl_.keyMapPrim(); } d->intl_.getTransManager().translateAndInsert(c, t, cur); @@ -874,7 +880,7 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) if (tm.contains(bot_pit)) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - LASSERT(!pm.rows().empty(), /**/); + LBUFERR(!pm.rows().empty()); // FIXME: smooth scrolling doesn't work in mathed. CursorSlice const & cs = dit.innerTextSlice(); int offset = coordOffset(dit).y_; @@ -885,20 +891,28 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) if (recenter) scrolled = scroll(ypos - height_/2); + // We try to visualize the whole row, if the row height is larger than + // the screen height, we scroll to a heuristic value of height_ / 4. + // FIXME: This heuristic value should be replaced by a recursive search + // for a row in the inset that can be visualized completely. + else if (row_dim.height() > height_) { + if (ypos < defaultRowHeight()) + scrolled = scroll(ypos - height_ / 4); + else if (ypos > height_ - defaultRowHeight()) + scrolled = scroll(ypos - 3 * height_ / 4); + } + // 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) - scrolled = scrollUp(-ypos + row_dim.ascent()); + else if (ypos - row_dim.ascent() < 0 && ypos < height_) { + int ynew = row_dim.ascent(); + scrolled = scrollUp(ynew - ypos); + } // If the bottom of the row falls of the screen, we scroll down. - // However, we have to be careful not to scroll that much that - // the top falls of the screen. - else if (ypos + row_dim.descent() > height_) { + else if (ypos + row_dim.descent() > height_ && ypos > 0) { int ynew = height_ - row_dim.descent(); - if (ynew < row_dim.ascent()) - ynew = row_dim.ascent(); - int const scroll = ypos - ynew; - scrolled = scrollDown(scroll); + scrolled = scrollDown(ypos - ynew); } // else, nothing to do, the cursor is already visible so we just return. @@ -933,10 +947,18 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) } -void BufferView::updateDocumentClass(DocumentClass const * const olddc) +void BufferView::makeDocumentClass() +{ + DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); + buffer_.params().makeDocumentClass(); + updateDocumentClass(olddc); +} + + +void BufferView::updateDocumentClass(DocumentClassConstPtr olddc) { message(_("Converting document to new document class...")); - + StableDocIterator backcur(d->cursor_); ErrorList & el = buffer_.errorList("Class Switch"); cap::switchBetweenClasses( @@ -948,6 +970,7 @@ void BufferView::updateDocumentClass(DocumentClass const * const olddc) buffer_.errors("Class Switch"); } + /** Return the change status at cursor position, taking in account the * status at each level of the document iterator (a table in a deleted * footnote is deleted). @@ -1026,9 +1049,17 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) // buffer clean/dirty status by itself. flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasRedoStack()); break; - case LFUN_FILE_INSERT: case LFUN_FILE_INSERT_PLAINTEXT_PARA: - case LFUN_FILE_INSERT_PLAINTEXT: + case LFUN_FILE_INSERT_PLAINTEXT: { + docstring const fname = cmd.argument(); + if (!FileName::isAbsolute(to_utf8(fname))) { + flag.message(_("Absolute filename expected.")); + return false; + } + flag.setEnabled(cur.inTexted()); + break; + } + case LFUN_FILE_INSERT: case LFUN_BOOKMARK_SAVE: // FIXME: Actually, these LFUNS should be moved to Text flag.setEnabled(cur.inTexted()); @@ -1043,7 +1074,6 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_WORD_FIND: case LFUN_WORD_FIND_FORWARD: case LFUN_WORD_FIND_BACKWARD: - case LFUN_WORD_FINDADV: case LFUN_WORD_REPLACE: case LFUN_MARK_OFF: case LFUN_MARK_ON: @@ -1053,14 +1083,22 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_BIBTEX_DATABASE_ADD: case LFUN_BIBTEX_DATABASE_DEL: case LFUN_STATISTICS: - case LFUN_BRANCH_ADD_INSERT: case LFUN_KEYMAP_OFF: case LFUN_KEYMAP_PRIMARY: case LFUN_KEYMAP_SECONDARY: case LFUN_KEYMAP_TOGGLE: + case LFUN_INSET_SELECT_ALL: flag.setEnabled(true); break; + case LFUN_WORD_FINDADV: { + FindAndReplaceOptions opt; + istringstream iss(to_utf8(cmd.argument())); + iss >> opt; + flag.setEnabled(opt.repl_buf_name.empty() + || !buffer_.isReadonly()); + } + case LFUN_LABEL_GOTO: { flag.setEnabled(!cmd.argument().empty() || getInsetByCode(cur, REF_CODE)); @@ -1069,12 +1107,12 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_CHANGES_TRACK: flag.setEnabled(true); - flag.setOnOff(buffer_.params().trackChanges); + flag.setOnOff(buffer_.params().track_changes); break; case LFUN_CHANGES_OUTPUT: flag.setEnabled(true); - flag.setOnOff(buffer_.params().outputChanges); + flag.setOnOff(buffer_.params().output_changes); break; case LFUN_CHANGES_MERGE: @@ -1120,13 +1158,17 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx())); break; + case LFUN_BRANCH_ADD_INSERT: + flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru())); + break; + case LFUN_DIALOG_SHOW_NEW_INSET: // FIXME: this is wrong, but I do not understand the // intent (JMarc) if (cur.inset().lyxCode() == CAPTION_CODE) return cur.inset().getStatus(cur, cmd, flag); // FIXME we should consider passthru paragraphs too. - flag.setEnabled(!cur.inset().getLayout().isPassThru()); + flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru())); break; case LFUN_CITATION_INSERT: { @@ -1175,20 +1217,13 @@ void BufferView::editInset(string const & name, Inset * inset) void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) { - //lyxerr << [ cmd = " << cmd << "]" << endl; - - // Make sure that the cached BufferView is correct. - LYXERR(Debug::ACTION, " action[" << cmd.action() << ']' - << " arg[" << to_utf8(cmd.argument()) << ']' - << " x[" << cmd.x() << ']' - << " y[" << cmd.y() << ']' - << " button[" << cmd.button() << ']'); + LYXERR(Debug::ACTION, "BufferView::dispatch: cmd: " << cmd); string const argument = to_utf8(cmd.argument()); Cursor & cur = d->cursor_; // Don't dispatch function that does not apply to internal buffers. - if (buffer_.isInternal() + if (buffer_.isInternal() && lyxaction.funcHasFlag(cmd.action(), LyXAction::NoInternal)) return; @@ -1200,7 +1235,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) switch (act) { case LFUN_BUFFER_PARAMS_APPLY: { - DocumentClass const * const oldClass = buffer_.params().documentClassPtr(); + DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); cur.recordUndoFullDocument(); istringstream ss(to_utf8(cmd.argument())); Lexer lex; @@ -1211,8 +1246,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) << unknown_tokens << " unknown token" << (unknown_tokens == 1 ? "" : "s")); } - updateDocumentClass(oldClass); - + updateDocumentClass(olddc); + // We are most certainly here because of a change in the document // It is then better to make sure that all dialogs are in sync with // current document settings. @@ -1220,14 +1255,11 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) dr.forceBufferUpdate(); break; } - + case LFUN_LAYOUT_MODULES_CLEAR: { - DocumentClass const * const oldClass = - buffer_.params().documentClassPtr(); cur.recordUndoFullDocument(); buffer_.params().clearLayoutModules(); - buffer_.params().makeDocumentClass(); - updateDocumentClass(oldClass); + makeDocumentClass(); dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); break; @@ -1235,26 +1267,24 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_LAYOUT_MODULE_ADD: { BufferParams const & params = buffer_.params(); - if (!params.moduleCanBeAdded(argument)) { - LYXERR0("Module `" << argument << + if (!params.layoutModuleCanBeAdded(argument)) { + LYXERR0("Module `" << argument << "' cannot be added due to failed requirements or " "conflicts with installed modules."); break; } - DocumentClass const * const oldClass = params.documentClassPtr(); cur.recordUndoFullDocument(); buffer_.params().addLayoutModule(argument); - buffer_.params().makeDocumentClass(); - updateDocumentClass(oldClass); + makeDocumentClass(); dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); break; } case LFUN_TEXTCLASS_APPLY: { - // since this shortcircuits, the second call is made only if + // since this shortcircuits, the second call is made only if // the first fails - bool const success = + bool const success = LayoutFileList::get().load(argument, buffer_.temppath()) || LayoutFileList::get().load(argument, buffer_.filePath()); if (!success) { @@ -1272,24 +1302,21 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; // Save the old, possibly modular, layout for use in conversion. - DocumentClass const * const oldDocClass = - buffer_.params().documentClassPtr(); cur.recordUndoFullDocument(); buffer_.params().setBaseClass(argument); - buffer_.params().makeDocumentClass(); - updateDocumentClass(oldDocClass); + makeDocumentClass(); dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); break; } case LFUN_TEXTCLASS_LOAD: { - // since this shortcircuits, the second call is made only if + // since this shortcircuits, the second call is made only if // the first fails - bool const success = + bool const success = LayoutFileList::get().load(argument, buffer_.temppath()) || LayoutFileList::get().load(argument, buffer_.filePath()); - if (!success) { + if (!success) { docstring s = bformat(_("The document class `%1$s' " "could not be loaded."), from_utf8(argument)); frontend::Alert::error(_("Could not load class"), s); @@ -1298,12 +1325,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_LAYOUT_RELOAD: { - DocumentClass const * const oldClass = buffer_.params().documentClassPtr(); LayoutFileIndex bc = buffer_.params().baseClassID(); LayoutFileList::get().reset(bc); buffer_.params().setBaseClass(bc); - buffer_.params().makeDocumentClass(); - updateDocumentClass(oldClass); + makeDocumentClass(); dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); break; @@ -1357,7 +1382,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } break; } - + case LFUN_PARAGRAPH_GOTO: { int const id = convert(cmd.getArg(0)); int const pos = convert(cmd.getArg(1)); @@ -1404,12 +1429,12 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_CHANGES_TRACK: - buffer_.params().trackChanges = !buffer_.params().trackChanges; + buffer_.params().track_changes = !buffer_.params().track_changes; break; case LFUN_CHANGES_OUTPUT: - buffer_.params().outputChanges = !buffer_.params().outputChanges; - if (buffer_.params().outputChanges) { + buffer_.params().output_changes = !buffer_.params().output_changes; + if (buffer_.params().output_changes) { bool dvipost = LaTeXFeatures::isAvailable("dvipost"); bool xcolorulem = LaTeXFeatures::isAvailable("ulem") && LaTeXFeatures::isAvailable("xcolor"); @@ -1435,7 +1460,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); break; - + case LFUN_CHANGE_PREVIOUS: findPreviousChange(this); // FIXME: Move this LFUN to Buffer so that we don't have to do this: @@ -1457,6 +1482,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) buffer_.text().cursorBottom(cur); // accept everything in a single step to support atomic undo buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT); + cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); @@ -1470,6 +1496,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) // reject everything in a single step to support atomic undo // Note: reject does not work recursively; the user may have to repeat the operation buffer_.text().acceptOrRejectChanges(cur, Text::REJECT); + cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); @@ -1477,6 +1504,8 @@ 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; @@ -1494,8 +1523,14 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) docstring const data = find2string(searched_string, true, false, fw); bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data)); - if (found) + if (found) { dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } break; } @@ -1507,10 +1542,14 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace")); break; } - if (lyxfind(this, req)) + if (lyxfind(this, req)) { dr.screenUpdate(Update::Force | Update::FitCursor); - else - message(_("String not found!")); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } d->search_request_cache_ = req; break; } @@ -1532,6 +1571,11 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (lyxreplace(this, cmd, has_deleted)) { dr.forceBufferUpdate(); dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; } break; } @@ -1540,8 +1584,14 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) FindAndReplaceOptions opt; istringstream iss(to_utf8(cmd.argument())); iss >> opt; - if (findAdv(this, opt)) + if (findAdv(this, opt)) { dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } break; } @@ -1571,7 +1621,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_SCREEN_SHOW_CURSOR: showCursor(); break; - + case LFUN_SCREEN_RECENTER: recenter(); break; @@ -1599,7 +1649,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (inset->delDatabase(cmd.argument())) { buffer_.invalidateBibfileCache(); dr.forceBufferUpdate(); - } + } } break; } @@ -1613,9 +1663,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) from = doc_iterator_begin(&buffer_); to = doc_iterator_end(&buffer_); } - int const words = countWords(from, to); - int const chars = countChars(from, to, false); - int const chars_blanks = countChars(from, to, true); + buffer_.updateStatistics(from, to); + int const words = buffer_.wordCount(); + int const chars = buffer_.charCount(false); + int const chars_blanks = buffer_.charCount(true); docstring message; if (cur.selection()) message = _("Statistics for the selection:"); @@ -1670,28 +1721,51 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) p = Point(width_, height_); Cursor old = cur; bool const in_texted = cur.inTexted(); - cur.reset(); + cur.setCursor(doc_iterator_begin(cur.buffer())); + cur.selHandle(false); buffer_.changed(true); updateHoveredInset(); d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_, - true, act == LFUN_SCREEN_UP); + true, act == LFUN_SCREEN_UP); //FIXME: what to do with cur.x_target()? bool update = in_texted && cur.bv().checkDepm(cur, old); cur.finishUndo(); - if (update) { + + if (update || cur.mark()) dr.screenUpdate(Update::Force | Update::FitCursor); + if (update) dr.forceBufferUpdate(); - } break; } - case LFUN_SCROLL: - lfunScroll(cmd); + case LFUN_SCROLL: { + string const scroll_type = cmd.getArg(0); + int scroll_step = 0; + if (scroll_type == "line") + scroll_step = d->scrollbarParameters_.single_step; + else if (scroll_type == "page") + scroll_step = d->scrollbarParameters_.page_step; + else + return; + string const scroll_quantity = cmd.getArg(1); + if (scroll_quantity == "up") + scrollUp(scroll_step); + else if (scroll_quantity == "down") + scrollDown(scroll_step); + else { + int const scroll_value = convert(scroll_quantity); + if (scroll_value) + scroll(scroll_step * scroll_value); + } + buffer_.changed(true); + updateHoveredInset(); dr.forceBufferUpdate(); break; + } case LFUN_SCREEN_UP_SELECT: { + // FIXME: why is the algorithm different from LFUN_SCREEN_UP? cur.selHandle(true); if (isTopScreen()) { lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT)); @@ -1709,6 +1783,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_SCREEN_DOWN_SELECT: { + // FIXME: why is the algorithm different from LFUN_SCREEN_DOWN? cur.selHandle(true); if (isBottomScreen()) { lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT)); @@ -1726,6 +1801,41 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } + case LFUN_INSET_SELECT_ALL: + if (cur.depth() > 1 + && cur.selBegin().at_begin() + && cur.selEnd().at_end()) { + // All the contents of the inset if selected. + // Select the inset from outside. + cur.pop(); + cur.resetAnchor(); + cur.setSelection(true); + cur.posForward(); + } else if (cur.selBegin().idx() != cur.selEnd().idx() + || (cur.depth() > 1 + && cur.selBegin().at_cell_begin() + && cur.selEnd().at_cell_end())) { + // At least one complete cell is selected. + // Select all cells + cur.idx() = 0; + cur.pos() = 0; + cur.resetAnchor(); + cur.setSelection(true); + cur.idx() = cur.lastidx(); + cur.pos() = cur.lastpos(); + } else { + // select current cell + cur.pit() = 0; + cur.pos() = 0; + cur.resetAnchor(); + cur.setSelection(true); + cur.pit() = cur.lastpit(); + cur.pos() = cur.lastpos(); + } + dr.screenUpdate(Update::Force); + break; + + // This would be in Buffer class if only Cursor did not // require a bufferview case LFUN_INSET_FORALL: { @@ -1734,7 +1844,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) FuncRequest const fr = lyxaction.lookupFunc(commandstr); // an arbitrary number to limit number of iterations - const int max_iter = 10000; + const int max_iter = 100000; int iterations = 0; Cursor & cur = d->cursor_; Cursor const savecur = cur; @@ -1743,10 +1853,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) cur.forwardInset(); cur.beginUndoGroup(); while(cur && iterations < max_iter) { - Inset * ins = cur.nextInset(); + Inset * const ins = cur.nextInset(); if (!ins) break; - docstring insname = ins->name(); + docstring insname = ins->layoutName(); while (!insname.empty()) { if (insname == name || name == from_utf8("*")) { cur.recordUndo(); @@ -1759,7 +1869,9 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; insname = insname.substr(0, i); } - cur.forwardInset(); + // if we did not delete the inset, skip it + if (!cur.nextInset() || cur.nextInset() == ins) + cur.forwardInset(); } cur.endUndoGroup(); cur = savecur; @@ -1822,7 +1934,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (decodeInsetParam(name, data, buffer_)) lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, name + " " + data)); else - lyxerr << "Inset type '" << name << + lyxerr << "Inset type '" << name << "' not recognized in LFUN_DIALOG_SHOW_NEW_INSET" << endl; break; } @@ -1842,11 +1954,28 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) arg = token(argument, '|', 0); opt1 = token(argument, '|', 1); } + + // if our cursor is directly in front of or behind a citation inset, + // we will instead add the new key to it. + Inset * inset = cur.nextInset(); + if (!inset || inset->lyxCode() != CITE_CODE) + inset = cur.prevInset(); + if (inset && inset->lyxCode() == CITE_CODE) { + InsetCitation * icite = static_cast(inset); + if (icite->addKey(arg)) { + dr.forceBufferUpdate(); + dr.screenUpdate(Update::FitCursor | Update::SinglePar); + if (!opt1.empty()) + LYXERR0("Discarding optional argument to citation-insert."); + } + dispatched = true; + break; + } InsetCommandParams icp(CITE_CODE); icp["key"] = from_utf8(arg); if (!opt1.empty()) icp["before"] = from_utf8(opt1); - string icstr = InsetCommand::params2string("citation", icp); + string icstr = InsetCommand::params2string(icp); FuncRequest fr(LFUN_INSET_INSERT, icstr); lyx::dispatch(fr); break; @@ -1873,14 +2002,45 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } + // FIXME: + // The change of language of buffer belongs to the Buffer class. + // We have to do it here because we need a cursor for Undo. + // When Undo::recordUndoBufferParams() is implemented someday + // LFUN_BUFFER_LANGUAGE should be handled by the Buffer class. + case LFUN_BUFFER_LANGUAGE: { + Language const * oldL = buffer_.params().language; + Language const * newL = languages.getLanguage(argument); + if (!newL || oldL == newL) + break; + if (oldL->rightToLeft() == newL->rightToLeft()) { + cur.recordUndoFullDocument(); + buffer_.changeLanguage(oldL, newL); + cur.setCurrentFont(); + dr.forceBufferUpdate(); + } + break; + } + + case LFUN_FILE_INSERT_PLAINTEXT_PARA: + case LFUN_FILE_INSERT_PLAINTEXT: { + bool const as_paragraph = (act == LFUN_FILE_INSERT_PLAINTEXT_PARA); + string const fname = to_utf8(cmd.argument()); + if (!FileName::isAbsolute(fname)) + dr.setMessage(_("Absolute filename expected.")); + else + insertPlaintextFile(FileName(fname), as_paragraph); + break; + } + default: - dispatched = false; + // OK, so try the Buffer itself... + buffer_.dispatch(cmd, dr); + dispatched = dr.dispatched(); break; } buffer_.undo().endUndoGroup(); dr.dispatched(dispatched); - return; } @@ -1984,22 +2144,20 @@ void BufferView::updateHoveredInset() const need_redraw |= d->last_inset_->setMouseHover(this, false); d->last_inset_ = 0; } - - // const_cast because of setMouseHover(). - Inset * inset = const_cast(covering_inset); - if (inset && inset->setMouseHover(this, true)) { + + if (covering_inset && covering_inset->setMouseHover(this, true)) { need_redraw = true; - // Only the insets that accept the hover state, do + // Only the insets that accept the hover state, do // clear the last_inset_, so only set the last_inset_ // member if the hovered setting is accepted. - d->last_inset_ = inset; + d->last_inset_ = covering_inset; } if (need_redraw) { LYXERR(Debug::PAINTING, "Mouse hover detected at: (" - << d->mouse_position_cache_.x_ << ", " + << d->mouse_position_cache_.x_ << ", " << d->mouse_position_cache_.y_ << ")"); - + d->update_strategy_ = DecorationUpdate; // This event (moving without mouse click) is not passed further. @@ -2013,7 +2171,7 @@ void BufferView::clearLastInset(Inset * inset) const { if (d->last_inset_ != inset) { LYXERR0("Wrong last_inset!"); - LASSERT(false, /**/); + LATTEST(false); } d->last_inset_ = 0; } @@ -2070,46 +2228,26 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Notify left insets if (cur != old) { - old.fixIfBroken(); - bool badcursor = notifyCursorLeavesOrEnters(old, cur); + bool badcursor = old.fixIfBroken() | cur.fixIfBroken(); + badcursor |= notifyCursorLeavesOrEnters(old, cur); if (badcursor) cursor().fixIfBroken(); } - + // Do we have a selection? theSelection().haveSelection(cursor().selection()); + if (cur.needBufferUpdate()) { + cur.clearBufferUpdate(); + buffer().updateBuffer(); + } + // If the command has been dispatched, if (cur.result().dispatched() || cur.result().screenUpdate()) processUpdateFlags(cur.result().screenUpdate()); } -void BufferView::lfunScroll(FuncRequest const & cmd) -{ - string const scroll_type = cmd.getArg(0); - int scroll_step = 0; - if (scroll_type == "line") - scroll_step = d->scrollbarParameters_.single_step; - else if (scroll_type == "page") - scroll_step = d->scrollbarParameters_.page_step; - else - return; - string const scroll_quantity = cmd.getArg(1); - if (scroll_quantity == "up") - scrollUp(scroll_step); - else if (scroll_quantity == "down") - scrollDown(scroll_step); - else { - int const scroll_value = convert(scroll_quantity); - if (scroll_value) - scroll(scroll_step * scroll_value); - } - buffer_.changed(true); - updateHoveredInset(); -} - - int BufferView::minVisiblePart() { return 2 * defaultRowHeight(); @@ -2176,16 +2314,49 @@ int BufferView::scrollUp(int offset) void BufferView::setCursorFromRow(int row) { - int tmpid = -1; - int tmppos = -1; + int tmpid; + int tmppos; + pit_type newpit = 0; + pos_type newpos = 0; buffer_.texrow().getIdFromRow(row, tmpid, tmppos); + bool posvalid = (tmpid != -1); + if (posvalid) { + // we need to make sure that the row and position + // we got back are valid, because the buffer may well + // have changed since we last generated the LaTeX. + DocIterator dit = buffer_.getParFromID(tmpid); + if (dit == doc_iterator_end(&buffer_)) + posvalid = false; + else if (dit.depth() > 1) { + // We are in an inset. + pos_type lastpos = dit.lastpos(); + dit.pos() = tmppos > lastpos ? lastpos : tmppos; + setCursor(dit); + recenter(); + return; + } else { + newpit = dit.pit(); + // now have to check pos. + newpos = tmppos; + Paragraph const & par = buffer_.text().getPar(newpit); + if (newpos > par.size()) { + LYXERR0("Requested position no longer valid."); + newpos = par.size() - 1; + } + } + } + if (!posvalid) { + frontend::Alert::error(_("Inverse Search Failed"), + _("Invalid position requested by inverse search.\n" + "You need to update the viewed document.")); + return; + } d->cursor_.reset(); - if (tmpid == -1) - buffer_.text().setCursor(d->cursor_, 0, 0); - else - buffer_.text().setCursor(d->cursor_, buffer_.getParFromID(tmpid).pit(), tmppos); + buffer_.text().setCursor(d->cursor_, newpit, newpos); + d->cursor_.setSelection(false); + d->cursor_.resetAnchor(); recenter(); } @@ -2239,6 +2410,7 @@ TextMetrics const & BufferView::textMetrics(Text const * t) const TextMetrics & BufferView::textMetrics(Text const * t) { + LBUFERR(t); TextMetricsCache::iterator tmc_it = d->text_metrics_.find(t); if (tmc_it == d->text_metrics_.end()) { tmc_it = d->text_metrics_.insert( @@ -2270,6 +2442,11 @@ void BufferView::setCursor(DocIterator const & dit) d->cursor_.setCursor(dit); d->cursor_.setSelection(false); + // FIXME + // It seems on general grounds as if this is probably needed, but + // it is not yet clear. + // See bug #7394 and r38388. + // d->cursor.resetAnchor(); } @@ -2291,7 +2468,10 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) d->cursor_ = cur; - cur.forceBufferUpdate(); + // we would rather not do this here, but it needs to be done before + // the changed() signal is sent. + buffer_.updateBuffer(); + buffer_.changed(true); return true; } @@ -2299,7 +2479,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) bool BufferView::mouseSetCursor(Cursor & cur, bool select) { - LASSERT(&cur.bv() == this, /**/); + LASSERT(&cur.bv() == this, return false); if (!select) // this event will clear selection so we save selection for @@ -2307,14 +2487,17 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) cap::saveSelection(cursor()); d->cursor_.macroModeClose(); + // If a macro has been finalized, the cursor might have been broken + cur.fixIfBroken(); // Has the cursor just left the inset? - bool leftinset = (&d->cursor_.inset() != &cur.inset()); + bool const leftinset = (&d->cursor_.inset() != &cur.inset()); if (leftinset) d->cursor_.fixIfBroken(); // FIXME: shift-mouse selection doesn't work well across insets. - bool do_selection = select && &d->cursor_.normalAnchor().inset() == &cur.inset(); + bool const do_selection = + select && &d->cursor_.normalAnchor().inset() == &cur.inset(); // do the dEPM magic if needed // FIXME: (1) move this to InsetText::notifyCursorLeaves? @@ -2358,6 +2541,43 @@ void BufferView::putSelectionAt(DocIterator const & cur, } +bool BufferView::selectIfEmpty(DocIterator & cur) +{ + if ((cur.inTexted() && !cur.paragraph().empty()) + || (cur.inMathed() && !cur.cell().empty())) + return false; + + pit_type const beg_pit = cur.pit(); + if (beg_pit > 0) { + // The paragraph associated to this item isn't + // the first one, so it can be selected + cur.backwardPos(); + } else { + // We have to resort to select the space between the + // end of this item and the begin of the next one + cur.forwardPos(); + } + if (cur.empty()) { + // If it is the only item in the document, + // nothing can be selected + return false; + } + pit_type const end_pit = cur.pit(); + pos_type const end_pos = cur.pos(); + d->cursor_.clearSelection(); + d->cursor_.reset(); + d->cursor_.setCursor(cur); + d->cursor_.pit() = beg_pit; + d->cursor_.pos() = 0; + d->cursor_.setSelection(false); + d->cursor_.resetAnchor(); + d->cursor_.pit() = end_pit; + d->cursor_.pos() = end_pos; + d->cursor_.setSelection(); + return true; +} + + Cursor & BufferView::cursor() { return d->cursor_; @@ -2392,7 +2612,7 @@ bool BufferView::singleParUpdate() // (if this paragraph contains insets etc., rebreaking will // recursively descend) tm.redoParagraph(bottom_pit); - ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); + ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); if (pm.height() != old_height) // Paragraph height has changed so we cannot proceed to // the singlePar optimisation. @@ -2429,7 +2649,7 @@ void BufferView::updateMetrics() // make sure inline completion pointer is ok if (d->inlineCompletionPos_.fixIfBroken()) d->inlineCompletionPos_ = DocIterator(); - + if (d->anchor_pit_ >= npit) // The anchor pit must have been deleted... d->anchor_pit_ = npit - 1; @@ -2437,19 +2657,19 @@ void BufferView::updateMetrics() // Rebreak anchor paragraph. tm.redoParagraph(d->anchor_pit_); ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_]; - + // position anchor if (d->anchor_pit_ == 0) { int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min; - + // Complete buffer visible? Then it's easy. if (scrollRange == 0) d->anchor_ypos_ = anchor_pm.ascent(); - + // FIXME: Some clever handling needed to show // the _first_ paragraph up to the top if the cursor is // in the first line. - } + } anchor_pm.setPosition(d->anchor_ypos_); LYXERR(Debug::PAINTING, "metrics: " @@ -2501,7 +2721,7 @@ void BufferView::updateMetrics() void BufferView::insertLyXFile(FileName const & fname) { - LASSERT(d->cursor_.inTexted(), /**/); + LASSERT(d->cursor_.inTexted(), return); // Get absolute path of file and add ".lyx" // to the filename if necessary @@ -2512,8 +2732,8 @@ void BufferView::insertLyXFile(FileName const & fname) message(bformat(_("Inserting document %1$s..."), disp_fn)); docstring res; - Buffer buf("", false); - if (buf.loadLyXFile(filename) == Buffer::ReadSuccess) { + Buffer buf(filename.absFileName(), false); + if (buf.loadLyXFile() == Buffer::ReadSuccess) { ErrorList & el = buffer_.errorList("Parse"); // Copy the inserted document error list into the current buffer one. el = buf.errorList("Parse"); @@ -2528,7 +2748,6 @@ void BufferView::insertLyXFile(FileName const & fname) buffer_.changed(true); // emit message signal. message(bformat(res, disp_fn)); - buffer_.errors("Parse"); } @@ -2544,14 +2763,14 @@ Point BufferView::coordOffset(DocIterator const & dit) const CursorSlice const & sl = dit[i]; int xx = 0; int yy = 0; - + // get relative position inside sl.inset() sl.inset().cursorPos(*this, sl, dit.boundary() && (i + 1 == dit.depth()), xx, yy); - + // Make relative position inside of the edited inset relative to sl.inset() x += xx; y += yy; - + // In case of an RTL inset, the edited inset will be positioned to the left // of xx:yy if (sl.text()) { @@ -2576,7 +2795,7 @@ Point BufferView::coordOffset(DocIterator const & dit) const Dimension const dim = sl.inset().dimension(*this); lastw = dim.wid; } - + //lyxerr << "Cursor::getPos, i: " // << i << " x: " << xx << " y: " << y << endl; } @@ -2585,7 +2804,8 @@ Point BufferView::coordOffset(DocIterator const & dit) const CursorSlice const & sl = dit[0]; TextMetrics const & tm = textMetrics(sl.text()); ParagraphMetrics const & pm = tm.parMetrics(sl.pit()); - LASSERT(!pm.rows().empty(), /**/); + + LBUFERR(!pm.rows().empty()); y -= pm.rows()[0].ascent(); #if 1 // FIXME: document this mess @@ -2604,20 +2824,20 @@ Point BufferView::coordOffset(DocIterator const & dit) const for (size_t rit = 0; rit != rend; ++rit) y += pm.rows()[rit].height(); y += pm.rows()[rend].ascent(); - + TextMetrics const & bottom_tm = textMetrics(dit.bottom().text()); - + // Make relative position from the nested inset now bufferview absolute. int xx = bottom_tm.cursorX(dit.bottom(), dit.boundary() && dit.depth() == 1); x += xx; - - // In the RTL case place the nested inset at the left of the cursor in + + // In the RTL case place the nested inset at the left of the cursor in // the outer paragraph bool boundary_1 = dit.boundary() && 1 == dit.depth(); bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1); if (rtl) x -= lastw; - + return Point(x, y); } @@ -2631,7 +2851,7 @@ Point BufferView::getPos(DocIterator const & dit) const TextMetrics const & tm = textMetrics(bot.text()); // offset from outer paragraph - Point p = coordOffset(dit); + Point p = coordOffset(dit); p.y_ += tm.parMetrics(bot.pit()).position(); return p; } @@ -2717,9 +2937,9 @@ void BufferView::draw(frontend::Painter & pain) // and possibly grey out below pair lastpm = tm.last(); int const y2 = lastpm.second->position() + lastpm.second->descent(); - + if (y2 < height_) { - Color color = buffer().isInternal() + Color color = buffer().isInternal() ? Color_background : Color_bottomarea; pain.fillRectangle(0, y2, width_, height_ - y2, color); } @@ -2855,6 +3075,12 @@ DocIterator const & BufferView::inlineCompletionPos() const } +void BufferView::resetInlineCompletionPos() +{ + d->inlineCompletionPos_ = DocIterator(); +} + + bool samePar(DocIterator const & a, DocIterator const & b) { if (a.empty() && b.empty()) @@ -2867,7 +3093,7 @@ bool samePar(DocIterator const & a, DocIterator const & b) } -void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, +void BufferView::setInlineCompletion(Cursor const & cur, DocIterator const & pos, docstring const & completion, size_t uniqueChars) { uniqueChars = min(completion.size(), uniqueChars); @@ -2876,9 +3102,9 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, bool singlePar = true; d->inlineCompletion_ = completion; d->inlineCompletionUniqueChars_ = min(completion.size(), uniqueChars); - + //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl; - + // at new position? DocIterator const & old = d->inlineCompletionPos_; if (old != pos) { @@ -2891,7 +3117,7 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, } d->inlineCompletionPos_ = pos; } - + // set update flags if (changed) { if (singlePar && !(cur.result().screenUpdate() & Update::Force)) @@ -2903,8 +3129,8 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, bool BufferView::clickableInset() const -{ - return d->clickable_inset_; +{ + return d->clickable_inset_; } } // namespace lyx