X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.C;h=ff3b47924018f876f07d04a52d8774abce6ac1c8;hb=0da3d53269a49c66b24615d24e20e441dcf7c07e;hp=6234108beb2bb7f02ae7227cbfb3771781eaa7de;hpb=b171e5f1501c0ef1ab724e0631200488bd3658a3;p=lyx.git diff --git a/src/BufferView.C b/src/BufferView.C index 6234108beb..ff3b479240 100644 --- a/src/BufferView.C +++ b/src/BufferView.C @@ -20,6 +20,7 @@ #include "buffer_funcs.h" #include "bufferlist.h" #include "bufferparams.h" +#include "bufferview_funcs.h" #include "coordcache.h" #include "CutAndPaste.h" #include "debug.h" @@ -61,7 +62,8 @@ #include "frontends/Alert.h" #include "frontends/FileDialog.h" -#include "frontends/font_metrics.h" +#include "frontends/FontMetrics.h" +#include "frontends/Selection.h" #include "graphics/Previews.h" @@ -78,22 +80,21 @@ #include -using lyx::docstring; -using lyx::pos_type; +namespace lyx { -using lyx::support::addPath; -using lyx::support::bformat; -using lyx::support::FileFilterList; -using lyx::support::fileSearch; -using lyx::support::isDirWriteable; -using lyx::support::makeDisplayPath; -using lyx::support::makeAbsPath; -using lyx::support::package; +using support::addPath; +using support::bformat; +using support::FileFilterList; +using support::FileName; +using support::fileSearch; +using support::isDirWriteable; +using support::isFileReadable; +using support::makeDisplayPath; +using support::package; using std::distance; using std::endl; using std::istringstream; -using std::find; using std::make_pair; using std::min; using std::max; @@ -101,14 +102,10 @@ using std::mem_fun_ref; using std::string; using std::vector; -extern BufferList bufferlist; - +namespace Alert = frontend::Alert; namespace { -unsigned int const saved_positions_num = 20; - - /// Return an inset of this class if it exists at the current cursor position template T * getInsetByCode(LCursor & cur, InsetBase::Code code) @@ -126,23 +123,12 @@ T * getInsetByCode(LCursor & cur, InsetBase::Code code) BufferView::BufferView() - : buffer_(0), wh_(0), + : width_(0), height_(0), buffer_(0), wh_(0), cursor_(*this), multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0), - intl_(new Intl) + intl_(new Intl), last_inset_(0) { xsel_cache_.set = false; - - saved_positions.resize(saved_positions_num); - // load saved bookmarks - lyx::Session::BookmarkList & bmList = LyX::ref().session().loadBookmarks(); - for (lyx::Session::BookmarkList::iterator bm = bmList.begin(); - bm != bmList.end(); ++bm) - if (bm->get<0>() < saved_positions_num) - saved_positions[bm->get<0>()] = Position( bm->get<1>(), bm->get<2>(), bm->get<3>() ); - // and then clear them - bmList.clear(); - intl_->initKeyMapper(lyxrc.use_kbmap); } @@ -169,8 +155,21 @@ void BufferView::setBuffer(Buffer * b) // to this buffer later on. buffer_->saveCursor(cursor_.selectionBegin(), cursor_.selectionEnd()); + // update bookmark pit of the current buffer before switch + for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i) { + BookmarksSection::Bookmark const & bm = LyX::ref().session().bookmarks().bookmark(i); + if (buffer()->fileName() != bm.filename.absFilename()) + continue; + // if par_id or pit has been changed, reset par_pit and par_id + // see http://bugzilla.lyx.org/show_bug.cgi?id=3092 + pit_type new_pit; + int new_id; + boost::tie(new_pit, new_id) = moveToPosition(bm.par_pit, bm.par_id, bm.par_pos); + if (bm.par_pit != new_pit || bm.par_id != new_id) + const_cast(bm).setPos(new_pit, new_id); + } // current buffer is going to be switched-off, save cursor pos - LyX::ref().session().saveFilePosition(buffer_->fileName(), + LyX::ref().session().lastFilePos().save(FileName(buffer_->fileName()), boost::tie(cursor_.pit(), cursor_.pos()) ); } @@ -186,7 +185,7 @@ void BufferView::setBuffer(Buffer * b) lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION << " No Buffer!" << endl; // We are closing the buffer, use the first buffer as current - buffer_ = bufferlist.first(); + buffer_ = theBufferList().first(); } else { // Set current buffer buffer_ = b; @@ -202,7 +201,6 @@ void BufferView::setBuffer(Buffer * b) << "Buffer addr: " << buffer_ << endl; cursor_.push(buffer_->inset()); cursor_.resetAnchor(); - buffer_->text().init(this); buffer_->text().setCurrentFont(cursor_); if (buffer_->getCursor().size() > 0 && buffer_->getAnchor().size() > 0) @@ -211,30 +209,23 @@ void BufferView::setBuffer(Buffer * b) cursor_.resetAnchor(); cursor_.setCursor(buffer_->getCursor().asDocIterator(&(buffer_->inset()))); cursor_.setSelection(); + theSelection().haveSelection(cursor_.selection()); } } - update(); + if (buffer_) + updateMetrics(false); - if (buffer_ && lyx::graphics::Previews::status() != LyXRC::PREVIEW_OFF) - lyx::graphics::Previews::get().generateBufferPreviews(*buffer_); + if (buffer_ && graphics::Previews::status() != LyXRC::PREVIEW_OFF) + graphics::Previews::get().generateBufferPreviews(*buffer_); } -bool BufferView::loadLyXFile(string const & filename, bool tolastfiles) +bool BufferView::loadLyXFile(FileName const & filename, bool tolastfiles) { - // Get absolute path of file and add ".lyx" - // to the filename if necessary - string s = fileSearch(string(), filename, "lyx"); - - bool const found = !s.empty(); - - if (!found) - s = filename; - // File already open? - if (bufferlist.exists(s)) { - docstring const file = makeDisplayPath(s, 20); + if (theBufferList().exists(filename.absFilename())) { + docstring const file = makeDisplayPath(filename.absFilename(), 20); docstring text = bformat(_("The document %1$s is already " "loaded.\n\nDo you want to revert " "to the saved version?"), file); @@ -242,32 +233,33 @@ bool BufferView::loadLyXFile(string const & filename, bool tolastfiles) text, 0, 1, _("&Revert"), _("&Switch to document")); if (ret != 0) { - setBuffer(bufferlist.getBuffer(s)); + setBuffer(theBufferList().getBuffer(filename.absFilename())); return true; } // FIXME: should be LFUN_REVERT - if (!bufferlist.close(bufferlist.getBuffer(s), false)) + if (!theBufferList().close(theBufferList().getBuffer(filename.absFilename()), false)) return false; // Fall through to new load. (Asger) + buffer_ = 0; } Buffer * b = 0; - if (found) { - b = bufferlist.newBuffer(s); - if (!::loadLyXFile(b, s)) { - bufferlist.release(b); + if (isFileReadable(filename)) { + b = theBufferList().newBuffer(filename.absFilename()); + if (!lyx::loadLyXFile(b, filename)) { + theBufferList().release(b); return false; } } else { docstring text = bformat(_("The document %1$s does not yet " "exist.\n\nDo you want to create " - "a new document?"), lyx::from_utf8(s)); + "a new document?"), from_utf8(filename.absFilename())); int const ret = Alert::prompt(_("Create new document?"), text, 0, 1, _("&Create"), _("Cancel")); if (ret == 0) { - b = newFile(s, string(), true); + b = newFile(filename.absFilename(), string(), true); if (!b) return false; } else @@ -278,43 +270,27 @@ bool BufferView::loadLyXFile(string const & filename, bool tolastfiles) // Send the "errors" signal in case of parsing errors b->errors("Parse"); + // Update the labels and section numbering. + updateLabels(*buffer_); // scroll to the position when the file was last closed if (lyxrc.use_lastfilepos) { - lyx::pit_type pit; - lyx::pos_type pos; - boost::tie(pit, pos) = LyX::ref().session().loadFilePosition(s); - // I am not sure how to separate the following part to a function - // so I will leave this to Lars. - // - // check pit since the document may be externally changed. - if ( static_cast(pit) < b->paragraphs().size() ) { - ParIterator it = b->par_iterator_begin(); - ParIterator const end = b->par_iterator_end(); - for (; it != end; ++it) - if (it.pit() == pit) { - // restored pos may be bigger than it->size - setCursor(makeDocIterator(it, min(pos, it->size()))); - update(Update::FitCursor); - break; - } + pit_type pit; + pos_type pos; + boost::tie(pit, pos) = LyX::ref().session().lastFilePos().load(filename); + // if successfully move to pit (returned par_id is not zero), update metrics + if (moveToPosition(pit, 0, pos).get<1>()) { + if (fitCursor()) + updateMetrics(false); } } if (tolastfiles) - LyX::ref().session().addLastFile(b->fileName()); + LyX::ref().session().lastFiles().add(FileName(b->fileName())); return true; } -void BufferView::reload() -{ - string const fn = buffer_->fileName(); - if (bufferlist.close(buffer_, false)) - loadLyXFile(fn); -} - - void BufferView::resize() { if (!buffer_) @@ -322,8 +298,7 @@ void BufferView::resize() lyxerr[Debug::DEBUG] << BOOST_CURRENT_FUNCTION << endl; - buffer_->text().init(this); - update(); + updateMetrics(false); switchKeyMap(); } @@ -331,10 +306,11 @@ void BufferView::resize() bool BufferView::fitCursor() { if (bv_funcs::status(this, cursor_) == bv_funcs::CUR_INSIDE) { - LyXFont const font = cursor_.getFont(); - int const asc = font_metrics::maxAscent(font); - int const des = font_metrics::maxDescent(font); - Point const p = bv_funcs::getPos(cursor_, cursor_.boundary()); + frontend::FontMetrics const & fm = + theFontMetrics(cursor_.getFont()); + int const asc = fm.maxAscent(); + int const des = fm.maxDescent(); + Point const p = bv_funcs::getPos(*this, cursor_, cursor_.boundary()); if (p.y_ - asc >= 0 && p.y_ + des < height_) return false; } @@ -356,6 +332,10 @@ bool BufferView::multiParSel() bool BufferView::update(Update::flags flags) { + // last_inset_ points to the last visited inset. This pointer may become + // invalid because of keyboard editing. Since all such operations + // causes screen update(), I reset last_inset_ to avoid such a problem. + last_inset_ = 0; // This is close to a hot-path. if (lyxerr.debugging(Debug::DEBUG)) { lyxerr[Debug::DEBUG] @@ -370,18 +350,58 @@ bool BufferView::update(Update::flags flags) if (!buffer_) return false; + if (lyxerr.debugging(Debug::WORKAREA)) { + lyxerr[Debug::WORKAREA] << "BufferView::update" << std::endl; + } + // Update macro store buffer_->buildMacros(); - // First drawing step - updateMetrics(flags & Update::SinglePar); - + // Now do the first drawing step if needed. This consists on updating + // the CoordCache in updateMetrics(). // The second drawing step is done in WorkArea::redraw() if needed. - bool const need_second_step = - (flags & (Update::Force | Update::FitCursor | Update::MultiParSel)) - && (fitCursor() || multiParSel()); - return need_second_step; + // Case when no explicit update is requested. + if (!flags) { + // no need to redraw anything. + metrics_info_.update_strategy = NoScreenUpdate; + return false; + } + + if (flags == Update::Decoration) { + metrics_info_.update_strategy = DecorationUpdate; + return true; + } + + if (flags == Update::FitCursor + || flags == (Update::Decoration | Update::FitCursor)) { + bool const fit_cursor = fitCursor(); + // tell the frontend to update the screen if needed. + if (fit_cursor) { + updateMetrics(false); + return true; + } + if (flags & Update::Decoration) { + metrics_info_.update_strategy = DecorationUpdate; + return true; + } + // no screen update is needed. + metrics_info_.update_strategy = NoScreenUpdate; + return false; + } + + bool full_metrics = flags & Update::Force; + if (flags & Update::MultiParSel) + full_metrics |= multiParSel(); + + bool const single_par = !full_metrics; + updateMetrics(single_par); + + if (flags & Update::FitCursor && fitCursor()) + updateMetrics(false); + + // tell the frontend to update the screen. + return true; } @@ -395,17 +415,21 @@ void BufferView::updateScrollbar() } LyXText & t = buffer_->text(); + TextMetrics & tm = text_metrics_[&t]; + int const parsize = int(t.paragraphs().size() - 1); if (anchor_ref_ > parsize) { anchor_ref_ = parsize; offset_ref_ = 0; } - lyxerr[Debug::GUI] - << BOOST_CURRENT_FUNCTION - << " Updating scrollbar: height: " << t.paragraphs().size() - << " curr par: " << cursor_.bottom().pit() - << " default height " << defaultRowHeight() << endl; + if (lyxerr.debugging(Debug::GUI)) { + lyxerr[Debug::GUI] + << BOOST_CURRENT_FUNCTION + << " Updating scrollbar: height: " << t.paragraphs().size() + << " curr par: " << cursor_.bottom().pit() + << " default height " << defaultRowHeight() << endl; + } // It would be better to fix the scrollbar to understand // values in [0..1] and divide everything by wh @@ -413,29 +437,33 @@ void BufferView::updateScrollbar() // estimated average paragraph height: if (wh_ == 0) wh_ = height_ / 4; - int h = t.getPar(anchor_ref_).height(); + + int h = tm.parMetrics(anchor_ref_).height(); // Normalize anchor/offset (MV): while (offset_ref_ > h && anchor_ref_ < parsize) { anchor_ref_++; offset_ref_ -= h; - h = t.getPar(anchor_ref_).height(); + h = tm.parMetrics(anchor_ref_).height(); } // Look at paragraph heights on-screen int sumh = 0; int nh = 0; - for (lyx::pit_type pit = anchor_ref_; pit <= parsize; ++pit) { + for (pit_type pit = anchor_ref_; pit <= parsize; ++pit) { if (sumh > height_) break; - int const h2 = t.getPar(pit).height(); + int const h2 = tm.parMetrics(pit).height(); sumh += h2; nh++; } + + BOOST_ASSERT(nh); int const hav = sumh / nh; // More realistic average paragraph height if (hav > wh_) wh_ = hav; + BOOST_ASSERT(h); scrollbarParameters_.height = (parsize + 1) * wh_; scrollbarParameters_.position = anchor_ref_ * wh_ + int(offset_ref_ * wh_ / float(h)); scrollbarParameters_.lineScrollHeight = int(wh_ * defaultRowHeight() / float(h)); @@ -457,20 +485,26 @@ void BufferView::scrollDocView(int value) return; LyXText & t = buffer_->text(); + TextMetrics & tm = text_metrics_[&t]; float const bar = value / float(wh_ * t.paragraphs().size()); anchor_ref_ = int(bar * t.paragraphs().size()); if (anchor_ref_ > int(t.paragraphs().size()) - 1) anchor_ref_ = int(t.paragraphs().size()) - 1; - t.redoParagraph(anchor_ref_); - int const h = t.getPar(anchor_ref_).height(); + + tm.redoParagraph(anchor_ref_); + int const h = tm.parMetrics(anchor_ref_).height(); offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h); + updateMetrics(false); } void BufferView::setCursorFromScrollbar() { + if (!buffer_) + return; + LyXText & t = buffer_->text(); int const height = 2 * defaultRowHeight(); @@ -482,15 +516,21 @@ void BufferView::setCursorFromScrollbar() switch (st) { case bv_funcs::CUR_ABOVE: + // We reset the cursor because bv_funcs::status() does not + // work when the cursor is within mathed. + cur.reset(buffer_->inset()); t.setCursorFromCoordinates(cur, 0, first); cur.clearSelection(); break; case bv_funcs::CUR_BELOW: + // We reset the cursor because bv_funcs::status() does not + // work when the cursor is within mathed. + cur.reset(buffer_->inset()); t.setCursorFromCoordinates(cur, 0, last); cur.clearSelection(); break; case bv_funcs::CUR_INSIDE: - int const y = bv_funcs::getPos(cur, cur.boundary()).y_; + int const y = bv_funcs::getPos(*this, cur, cur.boundary()).y_; int const newy = min(last, max(y, first)); if (y != newy) { cur.reset(buffer_->inset()); @@ -500,9 +540,9 @@ void BufferView::setCursorFromScrollbar() } -Change const BufferView::getCurrentChange() +Change const BufferView::getCurrentChange() const { - if (!buffer_->params().tracking_changes || !cursor_.selection()) + if (!cursor_.selection()) return Change(Change::UNCHANGED); DocIterator dit = cursor_.selectionBegin(); @@ -510,74 +550,49 @@ Change const BufferView::getCurrentChange() } -void BufferView::savePosition(unsigned int i) +void BufferView::saveBookmark(bool persistent) { - if (i >= saved_positions_num) - return; - BOOST_ASSERT(cursor_.inTexted()); - saved_positions[i] = Position(buffer_->fileName(), - cursor_.paragraph().id(), - cursor_.pos()); - if (i > 0) + LyX::ref().session().bookmarks().save( + FileName(buffer_->fileName()), + cursor_.pit(), + cursor_.paragraph().id(), + cursor_.pos(), + persistent + ); + if (persistent) // emit message signal. - message(bformat(_("Saved bookmark %1$d"), i)); + message(_("Save bookmark")); } -void BufferView::restorePosition(unsigned int i) +boost::tuple BufferView::moveToPosition(pit_type par_pit, int par_id, pos_type par_pos) { - if (i >= saved_positions_num) - return; - - string const fname = saved_positions[i].filename; - cursor_.clearSelection(); - if (fname != buffer_->fileName()) { - Buffer * b = 0; - if (bufferlist.exists(fname)) - b = bufferlist.getBuffer(fname); - else { - b = bufferlist.newBuffer(fname); - // Don't ask, just load it - ::loadLyXFile(b, fname); + // if a valid par_id is given, try it first + if (par_id > 0) { + ParIterator par = buffer_->getParFromID(par_id); + if (par != buffer_->par_iterator_end()) { + setCursor(makeDocIterator(par, min(par->size(), par_pos))); + return boost::make_tuple(cursor_.pit(), par_id); } - if (b) - setBuffer(b); } - - ParIterator par = buffer_->getParFromID(saved_positions[i].par_id); - if (par == buffer_->par_iterator_end()) - return; - - setCursor(makeDocIterator(par, min(par->size(), saved_positions[i].par_pos))); - - if (i > 0) - // emit message signal. - message(bformat(_("Moved to bookmark %1$d"), i)); -} - - -bool BufferView::isSavedPosition(unsigned int i) -{ - return i < saved_positions_num && !saved_positions[i].filename.empty(); -} - -void BufferView::saveSavedPositions() -{ - // save bookmarks. It is better to use the pit interface - // but I do not know how to effectively convert between - // par_id and pit. - for (unsigned int i=1; i < saved_positions_num; ++i) { - if ( isSavedPosition(i) ) - LyX::ref().session().saveBookmark( boost::tie( - i, - saved_positions[i].filename, - saved_positions[i].par_id, - saved_positions[i].par_pos) ); + // if par_id == 0, or searching through par_id failed + if (static_cast(par_pit) < buffer_->paragraphs().size()) { + ParIterator it = buffer_->par_iterator_begin(); + ParIterator const end = buffer_->par_iterator_end(); + for (; it != end; ++it) + if (it.pit() == par_pit) { + // restored pos may be bigger than it->size + setCursor(makeDocIterator(it, min(par_pos, it->size()))); + return boost::make_tuple(par_pit, it->id()); + } } + // both methods fail + return boost::make_tuple(pit_type(0), 0); } + void BufferView::switchKeyMap() { if (!lyxrc.rtl_support) @@ -602,12 +617,13 @@ int BufferView::workWidth() const void BufferView::center() { CursorSlice & bot = cursor_.bottom(); - lyx::pit_type const pit = bot.pit(); - bot.text()->redoParagraph(pit); - Paragraph const & par = bot.text()->paragraphs()[pit]; + TextMetrics & tm = text_metrics_[bot.text()]; + pit_type const pit = bot.pit(); + tm.redoParagraph(pit); + ParagraphMetrics const & pm = tm.parMetrics(pit); anchor_ref_ = pit; - offset_ref_ = bv_funcs::coordOffset(cursor_, cursor_.boundary()).y_ - + par.ascent() - height_ / 2; + offset_ref_ = bv_funcs::coordOffset(*this, cursor_, cursor_.boundary()).y_ + + pm.ascent() - height_ / 2; } @@ -624,8 +640,8 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) flag.enabled(!buffer_->redostack().empty()); break; case LFUN_FILE_INSERT: - case LFUN_FILE_INSERT_ASCII_PARA: - case LFUN_FILE_INSERT_ASCII: + case LFUN_FILE_INSERT_PLAINTEXT_PARA: + case LFUN_FILE_INSERT_PLAINTEXT: case LFUN_BOOKMARK_SAVE: // FIXME: Actually, these LFUNS should be moved to LyXText flag.enabled(cursor_.inTexted()); @@ -659,29 +675,28 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) break; } - case LFUN_BOOKMARK_GOTO: - flag.enabled(isSavedPosition(convert(lyx::to_utf8(cmd.argument())))); - break; case LFUN_CHANGES_TRACK: flag.enabled(true); - flag.setOnOff(buffer_->params().tracking_changes); + flag.setOnOff(buffer_->params().trackChanges); break; case LFUN_CHANGES_OUTPUT: { OutputParams runparams; LaTeXFeatures features(*buffer_, buffer_->params(), runparams); - flag.enabled(buffer_ && buffer_->params().tracking_changes - && features.isAvailable("dvipost")); - flag.setOnOff(buffer_->params().output_changes); + flag.enabled(buffer_ && features.isAvailable("dvipost")); + flag.setOnOff(buffer_->params().outputChanges); break; } case LFUN_CHANGES_MERGE: - case LFUN_CHANGE_ACCEPT: // what about these two - case LFUN_CHANGE_REJECT: // what about these two + case LFUN_CHANGE_NEXT: case LFUN_ALL_CHANGES_ACCEPT: case LFUN_ALL_CHANGES_REJECT: - flag.enabled(buffer_ && buffer_->params().tracking_changes); + // TODO: context-sensitive enabling of LFUNs + // In principle, these command should only be enabled if there + // is a change in the document. However, without proper + // optimizations, this will inevitably result in poor performance. + flag.enabled(buffer_); break; case LFUN_BUFFER_TOGGLE_COMPRESSION: { @@ -697,7 +712,7 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) } -bool BufferView::dispatch(FuncRequest const & cmd) +Update::flags BufferView::dispatch(FuncRequest const & cmd) { //lyxerr << BOOST_CURRENT_FUNCTION // << [ cmd = " << cmd << "]" << endl; @@ -705,74 +720,75 @@ bool BufferView::dispatch(FuncRequest const & cmd) // Make sure that the cached BufferView is correct. lyxerr[Debug::ACTION] << BOOST_CURRENT_FUNCTION << " action[" << cmd.action << ']' - << " arg[" << lyx::to_utf8(cmd.argument()) << ']' + << " arg[" << to_utf8(cmd.argument()) << ']' << " x[" << cmd.x << ']' << " y[" << cmd.y << ']' << " button[" << cmd.button() << ']' << endl; + // FIXME: this should not be possible. + if (!buffer_) + return Update::None; + LCursor & cur = cursor_; + // Default Update flags. + Update::flags updateFlags = Update::Force | Update::FitCursor; switch (cmd.action) { case LFUN_UNDO: - if (buffer_) { - cur.message(_("Undo")); - cur.clearSelection(); - if (!textUndo(*this)) - cur.message(_("No further undo information")); - update(); - switchKeyMap(); + cur.message(_("Undo")); + cur.clearSelection(); + if (!textUndo(*this)) { + cur.message(_("No further undo information")); + updateFlags = Update::None; } + switchKeyMap(); break; case LFUN_REDO: - if (buffer_) { - cur.message(_("Redo")); - cur.clearSelection(); - if (!textRedo(*this)) - cur.message(_("No further redo information")); - update(); - switchKeyMap(); + cur.message(_("Redo")); + cur.clearSelection(); + if (!textRedo(*this)) { + cur.message(_("No further redo information")); + updateFlags = Update::None; } + switchKeyMap(); break; case LFUN_FILE_INSERT: - // FIXME: We don't know the encoding of filenames - menuInsertLyXFile(lyx::to_utf8(cmd.argument())); + // FIXME UNICODE + menuInsertLyXFile(to_utf8(cmd.argument())); break; - case LFUN_FILE_INSERT_ASCII_PARA: - // FIXME: We don't know the encoding of filenames - insertAsciiFile(this, lyx::to_utf8(cmd.argument()), true); + case LFUN_FILE_INSERT_PLAINTEXT_PARA: + // FIXME UNICODE + insertPlaintextFile(this, to_utf8(cmd.argument()), true); break; - case LFUN_FILE_INSERT_ASCII: - // FIXME: We don't know the encoding of filenames - insertAsciiFile(this, lyx::to_utf8(cmd.argument()), false); + case LFUN_FILE_INSERT_PLAINTEXT: + // FIXME UNICODE + insertPlaintextFile(this, to_utf8(cmd.argument()), false); break; case LFUN_FONT_STATE: - cur.message(lyx::from_utf8(cur.currentState())); + cur.message(cur.currentState()); break; case LFUN_BOOKMARK_SAVE: - savePosition(convert(lyx::to_utf8(cmd.argument()))); - break; - - case LFUN_BOOKMARK_GOTO: - restorePosition(convert(lyx::to_utf8(cmd.argument()))); + saveBookmark(convert(to_utf8(cmd.argument()))); break; case LFUN_LABEL_GOTO: { - string label = lyx::to_utf8(cmd.argument()); + docstring label = cmd.argument(); if (label.empty()) { InsetRef * inset = getInsetByCode(cursor_, InsetBase::REF_CODE); if (inset) { - label = inset->getContents(); - savePosition(0); + label = inset->getParam("reference"); + // persistent=false: use temp_bookmark + saveBookmark(false); } } @@ -782,41 +798,54 @@ bool BufferView::dispatch(FuncRequest const & cmd) } case LFUN_PARAGRAPH_GOTO: { - int const id = convert(lyx::to_utf8(cmd.argument())); - ParIterator par = buffer_->getParFromID(id); - if (par == buffer_->par_iterator_end()) { - lyxerr[Debug::INFO] << "No matching paragraph found! [" - << id << ']' << endl; - break; - } else { - lyxerr[Debug::INFO] << "Paragraph " << par->id() - << " found." << endl; + int const id = convert(to_utf8(cmd.argument())); + int i = 0; + for (Buffer * b = buffer_; i == 0 || b != buffer_; b = theBufferList().next(b)) { + ParIterator par = b->getParFromID(id); + if (par == b->par_iterator_end()) { + lyxerr[Debug::INFO] + << "No matching paragraph found! [" + << id << "]." << endl; + } else { + lyxerr[Debug::INFO] + << "Paragraph " << par->id() + << " found in buffer `" + << b->fileName() << "'." << endl; + + if (b == buffer_) { + // Set the cursor + setCursor(makeDocIterator(par, 0)); + switchKeyMap(); + } else { + // Switch to other buffer view and resend cmd + theLyXFunc().dispatch(FuncRequest( + LFUN_BUFFER_SWITCH, b->fileName())); + theLyXFunc().dispatch(cmd); + updateFlags = Update::None; + } + break; + } + ++i; } - - // Set the cursor - setCursor(makeDocIterator(par, 0)); - - update(); - switchKeyMap(); break; } case LFUN_OUTLINE_UP: - lyx::toc::outline(lyx::toc::Up, cursor_); + toc::outline(toc::Up, cursor_); cursor_.text()->setCursor(cursor_, cursor_.pit(), 0); updateLabels(*buffer_); break; case LFUN_OUTLINE_DOWN: - lyx::toc::outline(lyx::toc::Down, cursor_); + toc::outline(toc::Down, cursor_); cursor_.text()->setCursor(cursor_, cursor_.pit(), 0); updateLabels(*buffer_); break; case LFUN_OUTLINE_IN: - lyx::toc::outline(lyx::toc::In, cursor_); + toc::outline(toc::In, cursor_); updateLabels(*buffer_); break; case LFUN_OUTLINE_OUT: - lyx::toc::outline(lyx::toc::Out, cursor_); + toc::outline(toc::Out, cursor_); updateLabels(*buffer_); break; @@ -833,17 +862,19 @@ bool BufferView::dispatch(FuncRequest const & cmd) } case LFUN_CHANGES_TRACK: - trackChanges(); + buffer_->params().trackChanges = !buffer_->params().trackChanges; break; - case LFUN_CHANGES_OUTPUT: { - bool const state = buffer_->params().output_changes; - buffer_->params().output_changes = !state; + case LFUN_CHANGES_OUTPUT: + buffer_->params().outputChanges = !buffer_->params().outputChanges; + break; + + case LFUN_CHANGE_NEXT: + findNextChange(this); break; - } case LFUN_CHANGES_MERGE: - if (lyx::find::findNextChange(this)) + if (findNextChange(this)) showDialog("changes"); break; @@ -852,9 +883,8 @@ bool BufferView::dispatch(FuncRequest const & cmd) #ifdef WITH_WARNINGS #warning FIXME changes #endif - while (lyx::find::findNextChange(this)) - getLyXText()->acceptChange(cursor_); - update(); + while (findNextChange(this)) + getLyXText()->acceptOrRejectChanges(cursor_, LyXText::ACCEPT); break; } @@ -863,40 +893,40 @@ bool BufferView::dispatch(FuncRequest const & cmd) #ifdef WITH_WARNINGS #warning FIXME changes #endif - while (lyx::find::findNextChange(this)) - getLyXText()->rejectChange(cursor_); + while (findNextChange(this)) + getLyXText()->acceptOrRejectChanges(cursor_, LyXText::REJECT); break; } case LFUN_WORD_FIND: - lyx::find::find(this, cmd); + find(this, cmd); break; case LFUN_WORD_REPLACE: - lyx::find::replace(this, cmd); + replace(this, cmd); break; case LFUN_MARK_OFF: cur.clearSelection(); cur.resetAnchor(); - cur.message(lyx::from_utf8(N_("Mark off"))); + cur.message(from_utf8(N_("Mark off"))); break; case LFUN_MARK_ON: cur.clearSelection(); cur.mark() = true; cur.resetAnchor(); - cur.message(lyx::from_utf8(N_("Mark on"))); + cur.message(from_utf8(N_("Mark on"))); break; case LFUN_MARK_TOGGLE: cur.clearSelection(); if (cur.mark()) { cur.mark() = false; - cur.message(lyx::from_utf8(N_("Mark removed"))); + cur.message(from_utf8(N_("Mark removed"))); } else { cur.mark() = true; - cur.message(lyx::from_utf8(N_("Mark set"))); + cur.message(from_utf8(N_("Mark set"))); } cur.resetAnchor(); break; @@ -911,7 +941,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, InsetBase::BIBTEX_CODE); if (inset) { - if (inset->addDatabase(lyx::to_utf8(cmd.argument()))) + if (inset->addDatabase(to_utf8(cmd.argument()))) buffer_->updateBibfilesCache(); } break; @@ -923,7 +953,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, InsetBase::BIBTEX_CODE); if (inset) { - if (inset->delDatabase(lyx::to_utf8(cmd.argument()))) + if (inset->delDatabase(to_utf8(cmd.argument()))) buffer_->updateBibfilesCache(); } break; @@ -989,10 +1019,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) } default: - return false; + updateFlags = Update::None; } - return true; + return updateFlags; } @@ -1026,26 +1056,24 @@ void BufferView::clearSelection() if (buffer_) { cursor_.clearSelection(); xsel_cache_.set = false; + // The buffer did not really change, but this causes the + // redraw we need because we cleared the selection above. + buffer_->changed(); } } void BufferView::workAreaResize(int width, int height) { - bool const widthChange = width != width_; - bool const heightChange = height != height_; - // Update from work area width_ = width; height_ = height; - if (buffer_ && widthChange) { - // The WorkArea content needs a resize - resize(); - } + // The complete text metrics will be redone. + text_metrics_.clear(); - if (widthChange || heightChange) - update(); + if (buffer_) + resize(); } @@ -1057,6 +1085,7 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) // LFUN_FILE_OPEN generated by drag-and-drop. FuncRequest cmd = cmd0; + // E.g. Qt mouse press when no buffer if (!buffer_) return false; @@ -1064,24 +1093,65 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) cur.push(buffer_->inset()); cur.selection() = cursor_.selection(); - // Doesn't go through lyxfunc, so we need to update - // the layout choice etc. ourselves - - // E.g. Qt mouse press when no buffer - if (!buffer_) - return false; - // Either the inset under the cursor or the // surrounding LyXText will handle this event. // Build temporary cursor. cmd.y = min(max(cmd.y, -1), height_); InsetBase * inset = buffer_->text().editXY(cur, cmd.x, cmd.y); + //lyxerr << BOOST_CURRENT_FUNCTION // << " * hit inset at tip: " << inset << endl; //lyxerr << BOOST_CURRENT_FUNCTION // << " * created temp cursor:" << cur << endl; + // NOTE: editXY returns the top level inset of nested insets. If you happen + // to move from a text (inset=0) to a text inside an inset (e.g. an opened + // footnote inset, again inset=0), that inset will not be redrawn. + if (cmd.action == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) { + bool need_redraw = false; + + if (inset != last_inset_) { + if (last_inset_) + need_redraw |= last_inset_->setMouseHover(false); + if (inset) + need_redraw |= inset->setMouseHover(true); + last_inset_ = inset; + } + + // if last metrics update was in singlepar mode, WorkArea::redraw() will + // not expose the button for redraw. We adjust here the metrics dimension + // to enable a full redraw. + // FIXME: It is possible to redraw only the area around the button! + if (need_redraw + && metrics_info_.update_strategy == SingleParUpdate) { + // FIXME: It should be possible to redraw only the area around + // the button by doing this: + // + //metrics_info_.singlepar = false; + //metrics_info_.y1 = ymin of button; + //metrics_info_.y2 = ymax of button; + // + // Unfortunately, rowpainter.C:paintText() does not distinguish + // between background updates and text updates. So we use the hammer + // solution for now. We could also avoid the updateMetrics() below + // by using the first and last pit of the CoordCache. Have a look + // at LyXText::getPitNearY() to see what I mean. + // + //metrics_info_.pit1 = first pit of CoordCache; + //metrics_info_.pit2 = last pit of CoordCache; + //metrics_info_.singlepar = false; + //metrics_info_.y1 = 0; + //metrics_info_.y2 = height_; + // + updateMetrics(false); + } + + // This event (moving without mouse click) is not passed further. + // This should be changed if it is further utilized. + return need_redraw; + } + // Put anchor at the same position. cur.resetAnchor(); @@ -1089,27 +1159,24 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) // via the temp cursor. If the inset wishes to change the real // cursor it has to do so explicitly by using // cur.bv().cursor() = cur; (or similar) - if (inset) + if (inset) { inset->dispatch(cur, cmd); + } // Now dispatch to the temporary cursor. If the real cursor should // be modified, the inset's dispatch has to do so explicitly. if (!cur.result().dispatched()) cur.dispatch(cmd); - if (cur.result().dispatched()) { - // Redraw if requested or necessary. - if (cur.result().update()) - update(Update::FitCursor | Update::Force); - else - update(Update::FitCursor | Update::MultiParSel); - } + // Redraw if requested and necessary. + if (cur.result().dispatched() && cur.result().update()) + return update(cur.result().update()); - return true; + return false; } -void BufferView::scroll(int lines) +void BufferView::scroll(int /*lines*/) { // if (!buffer_) // return; @@ -1126,8 +1193,6 @@ void BufferView::scroll(int lines) // // scrollDocView(new_top_y); // -// // Update the scrollbar. -// workArea_->setScrollbarParams(t->height(), top_y(), defaultRowHeight());} } @@ -1145,12 +1210,12 @@ void BufferView::setCursorFromRow(int row) } -void BufferView::gotoLabel(string const & label) +void BufferView::gotoLabel(docstring const & label) { for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it) { - vector labels; + vector labels; it->getLabelList(*buffer_, labels); - if (find(labels.begin(),labels.end(),label) != labels.end()) { + if (std::find(labels.begin(), labels.end(), label) != labels.end()) { setCursor(it); update(); return; @@ -1175,6 +1240,30 @@ LyXText const * BufferView::getLyXText() const } +TextMetrics const & BufferView::textMetrics(LyXText const * t) const +{ + return const_cast(this)->textMetrics(t); +} + + +TextMetrics & BufferView::textMetrics(LyXText const * t) +{ + TextMetricsCache::iterator tmc_it = text_metrics_.find(t); + if (tmc_it == text_metrics_.end()) { + tmc_it = text_metrics_.insert( + make_pair(t, TextMetrics(this, const_cast(t)))).first; + } + return tmc_it->second; +} + + +ParagraphMetrics const & BufferView::parMetrics(LyXText const * t, + pit_type pit) const +{ + return textMetrics(t).parMetrics(pit); +} + + int BufferView::workHeight() const { return height_; @@ -1192,7 +1281,31 @@ void BufferView::setCursor(DocIterator const & dit) } -void BufferView::mouseSetCursor(LCursor & cur) +bool BufferView::checkDepm(LCursor & cur, LCursor & old) +{ + // Would be wrong to delete anything if we have a selection. + if (cur.selection()) + return false; + + bool need_anchor_change = false; + bool changed = cursor_.text()->deleteEmptyParagraphMechanism(cur, old, + need_anchor_change); + + if (need_anchor_change) + cur.resetAnchor(); + + if (!changed) + return false; + + updateLabels(*buffer_); + + updateMetrics(false); + buffer_->changed(); + return true; +} + + +bool BufferView::mouseSetCursor(LCursor & cur) { BOOST_ASSERT(&cur.bv() == this); @@ -1203,14 +1316,33 @@ void BufferView::mouseSetCursor(LCursor & cur) // do the dEPM magic if needed // FIXME: move this to InsetText::notifyCursorLeaves? + bool update = false; if (!badcursor && cursor_.inTexted()) - cursor_.text()->deleteEmptyParagraphMechanism(cur, cursor_); + checkDepm(cur, cursor_); + + // if the cursor was in an empty script inset and the new + // position is in the nucleus of the inset, notifyCursorLeaves + // will kill the script inset itself. So we check all the + // elements of the cursor to make sure that they are correct. + // For an example, see bug 2933: + // http://bugzilla.lyx.org/show_bug.cgi?id=2933 + // The code below could maybe be moved to a DocIterator method. + //lyxerr << "cur before " << cur <text(); - lyx::pit_type size = int(buftext.paragraphs().size()); + TextMetrics & tm = textMetrics(&buftext); + pit_type size = int(buftext.paragraphs().size()); if (anchor_ref_ > int(buftext.paragraphs().size() - 1)) { anchor_ref_ = int(buftext.paragraphs().size() - 1); offset_ref_ = 0; } - - lyx::pit_type const pit = anchor_ref_; + + // If the paragraph metrics has changed, we can not + // use the singlepar optimisation. + if (singlepar + // In Single Paragraph mode, rebreak only + // the (main text, not inset!) paragraph containing the cursor. + // (if this paragraph contains insets etc., rebreaking will + // recursively descend) + && tm.redoParagraph(cursor_.bottom().pit())) + singlepar = false; + + pit_type const pit = anchor_ref_; int pit1 = pit; int pit2 = pit; size_t const npit = buftext.paragraphs().size(); - // Rebreak anchor paragraph. In Single Paragraph mode, rebreak only - // the (main text, not inset!) paragraph containing the cursor. - // (if this paragraph contains insets etc., rebreaking will - // recursively descend) - if (!singlepar || pit == cursor_.bottom().pit()) - buftext.redoParagraph(pit); - int y0 = buftext.getPar(pit).ascent() - offset_ref_; + // Rebreak anchor paragraph. + if (!singlepar) + tm.redoParagraph(pit); + + // Clear out the position cache in case of full screen redraw. + if (!singlepar) + coord_cache_.clear(); - // Redo paragraphs above anchor if necessary; again, in Single Par - // mode, only if we encounter the (main text) one having the cursor. + int y0 = tm.parMetrics(pit).ascent() - offset_ref_; + + // Redo paragraphs above anchor if necessary. int y1 = y0; while (y1 > 0 && pit1 > 0) { - y1 -= buftext.getPar(pit1).ascent(); + y1 -= tm.parMetrics(pit1).ascent(); --pit1; - if (!singlepar || pit1 == cursor_.bottom().pit()) - buftext.redoParagraph(pit1); - y1 -= buftext.getPar(pit1).descent(); + if (!singlepar) + tm.redoParagraph(pit1); + y1 -= tm.parMetrics(pit1).descent(); } // Take care of ascent of first line - y1 -= buftext.getPar(pit1).ascent(); + y1 -= tm.parMetrics(pit1).ascent(); // Normalize anchor for next time anchor_ref_ = pit1; @@ -1306,34 +1450,33 @@ void BufferView::updateMetrics(bool singlepar) anchor_ref_ = 0; } - // Redo paragraphs below the anchor if necessary. Single par mode: - // only the one containing the cursor if encountered. + // Redo paragraphs below the anchor if necessary. int y2 = y0; while (y2 < height_ && pit2 < int(npit) - 1) { - y2 += buftext.getPar(pit2).descent(); + y2 += tm.parMetrics(pit2).descent(); ++pit2; - if (!singlepar || pit2 == cursor_.bottom().pit()) - buftext.redoParagraph(pit2); - y2 += buftext.getPar(pit2).ascent(); + if (!singlepar) + tm.redoParagraph(pit2); + y2 += tm.parMetrics(pit2).ascent(); } // Take care of descent of last line - y2 += buftext.getPar(pit2).descent(); + y2 += tm.parMetrics(pit2).descent(); // The coordinates of all these paragraphs are correct, cache them int y = y1; - CoordCache::InnerParPosCache & parPos = theCoords.parPos()[&buftext]; - for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) { - Paragraph const & par = buftext.getPar(pit); - y += par.ascent(); + CoordCache::InnerParPosCache & parPos = coord_cache_.parPos()[&buftext]; + for (pit_type pit = pit1; pit <= pit2; ++pit) { + ParagraphMetrics const & pm = tm.parMetrics(pit); + y += pm.ascent(); parPos[pit] = Point(0, y); if (singlepar && pit == cursor_.bottom().pit()) { // In Single Paragraph mode, collect here the // y1 and y2 of the (one) paragraph the cursor is in - y1 = y - par.ascent(); - y2 = y + par.descent(); + y1 = y - pm.ascent(); + y2 = y + pm.descent(); } - y += par.descent(); + y += pm.descent(); } if (singlepar) { @@ -1353,7 +1496,13 @@ void BufferView::updateMetrics(bool singlepar) << "size: " << size << endl; - metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, singlepar, size); + metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, + singlepar? SingleParUpdate: FullScreenUpdate, size); + + if (lyxerr.debugging(Debug::WORKAREA)) { + lyxerr[Debug::WORKAREA] << "BufferView::updateMetrics" << endl; + coord_cache_.dump(); + } } @@ -1364,31 +1513,32 @@ void BufferView::menuInsertLyXFile(string const & filenm) if (filename.empty()) { // Launch a file browser + // FIXME UNICODE string initpath = lyxrc.document_path; if (buffer_) { string const trypath = buffer_->filePath(); // If directory is writeable, use this as default. - if (isDirWriteable(trypath)) + if (isDirWriteable(FileName(trypath))) initpath = trypath; } - FileDialog fileDlg(lyx::to_utf8(_("Select LyX document to insert")), + // FIXME UNICODE + FileDialog fileDlg(_("Select LyX document to insert"), LFUN_FILE_INSERT, - make_pair(string(lyx::to_utf8(_("Documents|#o#O"))), - string(lyxrc.document_path)), - make_pair(string(lyx::to_utf8(_("Examples|#E#e"))), - string(addPath(package().system_support(), "examples")))); + make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)), + make_pair(_("Examples|#E#e"), from_utf8(addPath(package().system_support(), "examples")))); FileDialog::Result result = - fileDlg.open(initpath, - FileFilterList(lyx::to_utf8(_("LyX Documents (*.lyx)"))), - string()); + fileDlg.open(from_utf8(initpath), + FileFilterList(_("LyX Documents (*.lyx)")), + docstring()); if (result.first == FileDialog::Later) return; - filename = result.second; + // FIXME UNICODE + filename = to_utf8(result.second); // check selected filename if (filename.empty()) { @@ -1400,7 +1550,7 @@ void BufferView::menuInsertLyXFile(string const & filenm) // Get absolute path of file and add ".lyx" // to the filename if necessary - filename = fileSearch(string(), filename, "lyx"); + filename = fileSearch(string(), filename, "lyx").absFilename(); docstring const disp_fn = makeDisplayPath(filename); // emit message signal. @@ -1408,11 +1558,12 @@ void BufferView::menuInsertLyXFile(string const & filenm) docstring res; Buffer buf("", false); - if (::loadLyXFile(&buf, makeAbsPath(filename))) { + if (lyx::loadLyXFile(&buf, FileName(filename))) { ErrorList & el = buffer_->errorList("Parse"); // Copy the inserted document error list into the current buffer one. el = buf.errorList("Parse"); - lyx::cap::pasteParagraphList(cursor_, buf.paragraphs(), + recordUndo(cursor_); + cap::pasteParagraphList(cursor_, buf.paragraphs(), buf.params().textclass, el); res = _("Document %1$s inserted."); } else @@ -1424,32 +1575,4 @@ void BufferView::menuInsertLyXFile(string const & filenm) resize(); } - -void BufferView::trackChanges() -{ - bool const tracking = buffer_->params().tracking_changes; - - if (!tracking) { - for_each(buffer_->par_iterator_begin(), - buffer_->par_iterator_end(), - bind(&Paragraph::trackChanges, _1, Change::UNCHANGED)); - buffer_->params().tracking_changes = true; - - // We cannot allow undos beyond the freeze point - buffer_->undostack().clear(); - } else { - cursor_.setCursor(doc_iterator_begin(buffer_->inset())); - if (lyx::find::findNextChange(this)) { - showDialog("changes"); - return; - } - - for_each(buffer_->par_iterator_begin(), - buffer_->par_iterator_end(), - mem_fun_ref(&Paragraph::untrackChanges)); - - buffer_->params().tracking_changes = false; - } - - buffer_->redostack().clear(); -} +} // namespace lyx