X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=3fdb769ed792b49a9beb6d1cf710b61a1ac33c5f;hb=fc2658eff011adb08192cf897416698c34aab268;hp=341ab318f4c5c6c50606ee30794fc96f6ee06a57;hpb=df6dca574e03b54f679dd1bec74bf1ddd4a58192;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 341ab318f4..3fdb769ed7 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -20,18 +20,16 @@ #include "buffer_funcs.h" #include "BufferList.h" #include "BufferParams.h" -#include "bufferview_funcs.h" -#include "callback.h" // added for Dispatch functions #include "CoordCache.h" +#include "Cursor.h" #include "CutAndPaste.h" -#include "debug.h" #include "DispatchResult.h" +#include "EmbeddedFiles.h" #include "ErrorList.h" #include "factory.h" #include "FloatList.h" #include "FuncRequest.h" #include "FuncStatus.h" -#include "gettext.h" #include "Intl.h" #include "InsetIterator.h" #include "Language.h" @@ -41,17 +39,17 @@ #include "LyXFunc.h" #include "Layout.h" #include "LyXRC.h" +#include "MenuBackend.h" #include "MetricsInfo.h" #include "Paragraph.h" #include "paragraph_funcs.h" #include "ParagraphParameters.h" #include "ParIterator.h" #include "Session.h" -#include "TexRow.h" #include "Text.h" #include "TextClass.h" -#include "toc.h" -#include "Undo.h" +#include "TextMetrics.h" +#include "TexRow.h" #include "VSpace.h" #include "WordLangTuple.h" @@ -61,7 +59,8 @@ #include "insets/InsetText.h" #include "frontends/alert.h" -#include "frontends/FileDialog.h" +#include "frontends/Application.h" +#include "frontends/Delegates.h" #include "frontends/FontMetrics.h" #include "frontends/Painter.h" #include "frontends/Selection.h" @@ -69,72 +68,204 @@ #include "graphics/Previews.h" #include "support/convert.h" +#include "support/debug.h" #include "support/FileFilterList.h" #include "support/filetools.h" +#include "support/gettext.h" +#include "support/lstrings.h" #include "support/Package.h" #include "support/types.h" -#include -#include - +#include +#include #include +#include #include -using std::distance; -using std::endl; -using std::istringstream; -using std::make_pair; -using std::min; -using std::max; -using std::mem_fun_ref; -using std::string; -using std::vector; - +using namespace std; +using namespace lyx::support; namespace lyx { -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; - namespace Alert = frontend::Alert; namespace { /// Return an inset of this class if it exists at the current cursor position template -T * getInsetByCode(Cursor & cur, Inset::Code code) +T * getInsetByCode(Cursor const & cur, InsetCode code) { - T * inset = 0; DocIterator it = cur; - if (it.nextInset() && - it.nextInset()->lyxCode() == code) { - inset = static_cast(it.nextInset()); + Inset * inset = it.nextInset(); + if (inset && inset->lyxCode() == code) + return static_cast(inset); + return 0; +} + + +bool findInset(DocIterator & dit, vector const & codes, + bool same_content); + +bool findNextInset(DocIterator & dit, vector const & codes, + docstring const & contents) +{ + DocIterator tmpdit = dit; + + while (tmpdit) { + Inset const * inset = tmpdit.nextInset(); + if (inset + && find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end() + && (contents.empty() || + static_cast(inset)->getFirstNonOptParam() == contents)) { + dit = tmpdit; + return true; + } + tmpdit.forwardInset(); } - return inset; + + return false; +} + + +/// Looks for next inset with one of the given codes. +bool findInset(DocIterator & dit, vector const & codes, + bool same_content) +{ + docstring contents; + DocIterator tmpdit = dit; + tmpdit.forwardInset(); + if (!tmpdit) + return false; + + if (same_content) { + Inset const * inset = tmpdit.nextInset(); + if (inset + && find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end()) { + contents = static_cast(inset)->getFirstNonOptParam(); + } + } + + if (!findNextInset(tmpdit, codes, contents)) { + if (dit.depth() != 1 || dit.pit() != 0 || dit.pos() != 0) { + tmpdit = doc_iterator_begin(tmpdit.bottom().inset()); + if (!findNextInset(tmpdit, codes, contents)) + return false; + } else + return false; + } + + dit = tmpdit; + return true; +} + + +/// Looks for next inset with the given code +void findInset(DocIterator & dit, InsetCode code, bool same_content) +{ + findInset(dit, vector(1, code), same_content); +} + + +/// Moves cursor to the next inset with one of the given codes. +void gotoInset(BufferView * bv, vector const & codes, + bool same_content) +{ + Cursor tmpcur = bv->cursor(); + if (!findInset(tmpcur, codes, same_content)) { + bv->cursor().message(_("No more insets")); + return; + } + + tmpcur.clearSelection(); + bv->setCursor(tmpcur); + bv->showCursor(); } + +/// Moves cursor to the next inset with given code. +void gotoInset(BufferView * bv, InsetCode code, bool same_content) +{ + gotoInset(bv, vector(1, code), same_content); +} + + +/// A map from a Text to the associated text metrics +typedef map TextMetricsCache; + +enum ScreenUpdateStrategy { + NoScreenUpdate, + SingleParUpdate, + FullScreenUpdate, + DecorationUpdate +}; + } // anon namespace +///////////////////////////////////////////////////////////////////// +// +// BufferView +// +///////////////////////////////////////////////////////////////////// + +struct BufferView::Private +{ + Private(BufferView & bv): wh_(0), cursor_(bv), + anchor_pit_(0), anchor_ypos_(0), + last_inset_(0), gui_(0) + {} + + /// + ScrollbarParameters scrollbarParameters_; + /// + ScreenUpdateStrategy update_strategy_; + /// + CoordCache coord_cache_; + + /// Estimated average par height for scrollbar. + int wh_; + /// this is used to handle XSelection events in the right manner. + struct { + CursorSlice cursor; + CursorSlice anchor; + bool set; + } xsel_cache_; + /// + Cursor cursor_; + /// + pit_type anchor_pit_; + /// + int anchor_ypos_; + /// + vector par_height_; + + /// keyboard mapping object. + Intl intl_; + + /// last visited inset. + /** kept to send setMouseHover(false). + * Not owned, so don't delete. + */ + Inset * last_inset_; + + mutable TextMetricsCache text_metrics_; + + /// Whom to notify. + /** Not owned, so don't delete. + */ + frontend::GuiBufferViewDelegate * gui_; +}; + + BufferView::BufferView(Buffer & buf) - : width_(0), height_(0), buffer_(buf), wh_(0), - cursor_(*this), - multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0), - need_centering_(false), intl_(new Intl), last_inset_(0) + : width_(0), height_(0), buffer_(buf), d(new Private(*this)) { - xsel_cache_.set = false; - intl_->initKeyMapper(lyxrc.use_kbmap); + d->xsel_cache_.set = false; + d->intl_.initKeyMapper(lyxrc.use_kbmap); - cursor_.push(buffer_.inset()); - cursor_.resetAnchor(); - cursor_.setCurrentFont(); + d->cursor_.push(buffer_.inset()); + d->cursor_.resetAnchor(); + d->cursor_.setCurrentFont(); if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) graphics::Previews::get().generateBufferPreviews(buffer_); @@ -148,9 +279,36 @@ BufferView::~BufferView() // currently can only handle bottom (whole document) level pit and pos. // That is to say, if a cursor is in a nested inset, it will be // restore to the left of the top level inset. - LyX::ref().session().lastFilePos().save( - support::FileName(buffer_.fileName()), - boost::tie(cursor_.bottom().pit(), cursor_.bottom().pos()) ); + LastFilePosSection::FilePos fp; + fp.pit = d->cursor_.bottom().pit(); + fp.pos = d->cursor_.bottom().pos(); + LyX::ref().session().lastFilePos().save(buffer_.fileName(), fp); + + delete d; +} + + +Intl & BufferView::getIntl() +{ + return d->intl_; +} + + +Intl const & BufferView::getIntl() const +{ + return d->intl_; +} + + +CoordCache & BufferView::coordCache() +{ + return d->coord_cache_; +} + + +CoordCache const & BufferView::coordCache() const +{ + return d->coord_cache_; } @@ -168,48 +326,33 @@ Buffer const & BufferView::buffer() const bool BufferView::fitCursor() { - if (bv_funcs::status(this, cursor_) == bv_funcs::CUR_INSIDE) { + if (cursorStatus(d->cursor_) == CUR_INSIDE) { frontend::FontMetrics const & fm = - theFontMetrics(cursor_.getFont()); + theFontMetrics(d->cursor_.getFont().fontInfo()); int const asc = fm.maxAscent(); int const des = fm.maxDescent(); - Point const p = bv_funcs::getPos(*this, cursor_, cursor_.boundary()); + Point const p = getPos(d->cursor_, d->cursor_.boundary()); if (p.y_ - asc >= 0 && p.y_ + des < height_) return false; } - center(); return true; } -bool BufferView::multiParSel() -{ - if (!cursor_.selection()) - return false; - bool ret = multiparsel_cache_; - multiparsel_cache_ = cursor_.selBegin().pit() != cursor_.selEnd().pit(); - // Either this, or previous selection spans paragraphs - return ret || multiparsel_cache_; -} - - -bool BufferView::update(Update::flags flags) +void BufferView::processUpdateFlags(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; + d->last_inset_ = 0; // This is close to a hot-path. - LYXERR(Debug::DEBUG) - << BOOST_CURRENT_FUNCTION + LYXERR(Debug::DEBUG, "BufferView::processUpdateFlags()" << "[fitcursor = " << (flags & Update::FitCursor) << ", forceupdate = " << (flags & Update::Force) << ", singlepar = " << (flags & Update::SinglePar) - << "] buffer: " << &buffer_ << endl; + << "] buffer: " << &buffer_); - // Update macro store - if (!(cursor().inMathed() && cursor().inMacroMode())) - buffer_.buildMacros(); + buffer_.updateMacros(); // Now do the first drawing step if needed. This consists on updating // the CoordCache in updateMetrics(). @@ -218,159 +361,204 @@ bool BufferView::update(Update::flags flags) // Case when no explicit update is requested. if (!flags) { // no need to redraw anything. - metrics_info_.update_strategy = NoScreenUpdate; - return false; + d->update_strategy_ = NoScreenUpdate; + return; } if (flags == Update::Decoration) { - metrics_info_.update_strategy = DecorationUpdate; - return true; + d->update_strategy_ = DecorationUpdate; + buffer_.changed(); + return; } 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 (fitCursor()) { + showCursor(); + return; } if (flags & Update::Decoration) { - metrics_info_.update_strategy = DecorationUpdate; - return true; + d->update_strategy_ = DecorationUpdate; + buffer_.changed(); + return; } // no screen update is needed. - metrics_info_.update_strategy = NoScreenUpdate; - return false; + d->update_strategy_ = NoScreenUpdate; + return; } - bool full_metrics = flags & Update::Force; - if (flags & Update::MultiParSel) - full_metrics |= multiParSel(); + bool const full_metrics = flags & Update::Force || !singleParUpdate(); + + if (full_metrics) + // We have to update the full screen metrics. + updateMetrics(); - bool const single_par = !full_metrics; - updateMetrics(single_par); + if (!(flags & Update::FitCursor)) { + // Nothing to do anymore. Trigger a redraw and return + buffer_.changed(); + return; + } - if (flags & Update::FitCursor && fitCursor()) - updateMetrics(false); + // updateMetrics() does not update paragraph position + // This is done at draw() time. So we need a redraw! + buffer_.changed(); - // tell the frontend to update the screen. - return true; + if (fitCursor()) { + // The cursor is off screen so ensure it is visible. + // refresh it: + showCursor(); + } } void BufferView::updateScrollbar() { - Text & t = buffer_.text(); - TextMetrics & tm = text_metrics_[&t]; + if (height_ == 0) + return; - int const parsize = int(t.paragraphs().size() - 1); - if (anchor_ref_ > parsize) { - anchor_ref_ = parsize; - offset_ref_ = 0; + Text & t = buffer_.text(); + TextMetrics & tm = d->text_metrics_[&t]; + + LYXERR(Debug::GUI, " Updating scrollbar: height: " + << t.paragraphs().size() + << " curr par: " << d->cursor_.bottom().pit() + << " default height " << defaultRowHeight()); + + int const parsize = int(t.paragraphs().size()); + if (d->par_height_.size() != parsize) { + d->par_height_.clear(); + // FIXME: We assume a default paragraph height of 2 rows. This + // should probably be pondered with the screen width. + d->par_height_.resize(parsize, defaultRowHeight() * 2); } - 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 - // estimated average paragraph height: - if (wh_ == 0) - wh_ = height_ / 4; - - int h = tm.parMetrics(anchor_ref_).height(); - - // Normalize anchor/offset (MV): - while (offset_ref_ > h && anchor_ref_ < parsize) { - anchor_ref_++; - offset_ref_ -= h; - h = tm.parMetrics(anchor_ref_).height(); - } // Look at paragraph heights on-screen - int sumh = 0; - int nh = 0; - for (pit_type pit = anchor_ref_; pit <= parsize; ++pit) { - if (sumh > height_) - break; - int const h2 = tm.parMetrics(pit).height(); - sumh += h2; - nh++; + pit_type first_visible_pit = -1; + pair first = tm.first(); + pair last = tm.last(); + for (pit_type pit = first.first; pit <= last.first; ++pit) { + ParagraphMetrics const & pm = tm.parMetrics(pit); + d->par_height_[pit] = pm.height(); + if (first_visible_pit >= 0 || pm.position() + pm.descent() <= 0) + continue; + first_visible_pit = pit; + LYXERR(Debug::SCROLLING, "first visible pit " << first_visible_pit); + // FIXME: we should look for the first visible row within + // the deepest inset! + int row_pos = pm.position(); + size_t const nrows = pm.rows().size(); + for (size_t i = 0; i != nrows; ++i) { + Row const & row = pm.rows()[i]; + if (row_pos >= 0) { + LYXERR(Debug::SCROLLING, "first visible row " << i + << "(row pos = " << row_pos << ");"); + break; + } + row_pos += row.height(); + } + d->scrollbarParameters_.position = row_pos; } - BOOST_ASSERT(nh); - int const hav = sumh / nh; - // More realistic average paragraph height - if (hav > wh_) - wh_ = hav; + d->scrollbarParameters_.height = 0; + for (size_t i = 0; i != d->par_height_.size(); ++i) { + if (i == first_visible_pit) + d->scrollbarParameters_.position += d->scrollbarParameters_.height; + d->scrollbarParameters_.height += d->par_height_[i]; + } - 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)); + // We prefer fixed size line scrolling. + d->scrollbarParameters_.lineScrollHeight = defaultRowHeight(); } ScrollbarParameters const & BufferView::scrollbarParameters() const { - return scrollbarParameters_; + return d->scrollbarParameters_; } -void BufferView::scrollDocView(int value) +docstring BufferView::toolTip(int x, int y) const { - LYXERR(Debug::GUI) << BOOST_CURRENT_FUNCTION - << "[ value = " << value << "]" << endl; + // Get inset under mouse, if there is one. + Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y); + if (!covering_inset) + // No inset, no tooltip... + return docstring(); + return covering_inset->toolTip(*this, x, y); +} - Text & t = buffer_.text(); - TextMetrics & tm = text_metrics_[&t]; - float const bar = value / float(wh_ * t.paragraphs().size()); +docstring 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) + return covering_inset->contextMenu(*this, x, y); + + // FIXME: Do something more elaborate here. + return from_ascii("edit"); +} + + +void BufferView::scrollDocView(int value) +{ + int const offset = value - d->scrollbarParameters_.position; + // If the offset is less than 2 screen height, prefer to scroll instead. + if (abs(offset) <= 2 * height_) { + scroll(offset); + return; + } + + int par_pos = 0; + for (size_t i = 0; i != d->par_height_.size(); ++i) { + par_pos += d->par_height_[i]; + if (par_pos >= value) { + d->anchor_pit_ = pit_type(i); + break; + } + } - anchor_ref_ = int(bar * t.paragraphs().size()); - if (anchor_ref_ > int(t.paragraphs().size()) - 1) - anchor_ref_ = int(t.paragraphs().size()) - 1; + LYXERR(Debug::SCROLLING, "value = " << value + << "\tanchor_ref_ = " << d->anchor_pit_ + << "\tpar_pos = " << par_pos); - tm.redoParagraph(anchor_ref_); - int const h = tm.parMetrics(anchor_ref_).height(); - offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h); - updateMetrics(false); + d->anchor_ypos_ = par_pos - value; + updateMetrics(); buffer_.changed(); } +// FIXME: this method is not working well. void BufferView::setCursorFromScrollbar() { - TextMetrics & tm = text_metrics_[&buffer_.text()]; + TextMetrics & tm = d->text_metrics_[&buffer_.text()]; int const height = 2 * defaultRowHeight(); int const first = height; int const last = height_ - height; - Cursor & cur = cursor_; - - bv_funcs::CurStatus st = bv_funcs::status(this, cur); + Cursor & cur = d->cursor_; - switch (st) { - case bv_funcs::CUR_ABOVE: - // We reset the cursor because bv_funcs::status() does not + switch (cursorStatus(cur)) { + case CUR_ABOVE: + // We reset the cursor because cursorStatus() does not // work when the cursor is within mathed. cur.reset(buffer_.inset()); tm.setCursorFromCoordinates(cur, 0, first); cur.clearSelection(); break; - case bv_funcs::CUR_BELOW: - // We reset the cursor because bv_funcs::status() does not + case CUR_BELOW: + // We reset the cursor because cursorStatus() does not // work when the cursor is within mathed. cur.reset(buffer_.inset()); tm.setCursorFromCoordinates(cur, 0, last); cur.clearSelection(); break; - case bv_funcs::CUR_INSIDE: - int const y = bv_funcs::getPos(*this, cur, cur.boundary()).y_; + case CUR_INSIDE: + int const y = getPos(cur, cur.boundary()).y_; int const newy = min(last, max(y, first)); if (y != newy) { cur.reset(buffer_.inset()); @@ -382,14 +570,27 @@ void BufferView::setCursorFromScrollbar() Change const BufferView::getCurrentChange() const { - if (!cursor_.selection()) + if (!d->cursor_.selection()) return Change(Change::UNCHANGED); - DocIterator dit = cursor_.selectionBegin(); + DocIterator dit = d->cursor_.selectionBegin(); return dit.paragraph().lookupChange(dit.pos()); } +// this could be used elsewhere as well? +// FIXME: This does not work within mathed! +CursorStatus BufferView::cursorStatus(DocIterator const & dit) const +{ + Point const p = getPos(dit, dit.boundary()); + if (p.y_ < 0) + return CUR_ABOVE; + if (p.y_ > workHeight()) + return CUR_BELOW; + return CUR_INSIDE; +} + + void BufferView::saveBookmark(unsigned int idx) { // tenatively save bookmark, id and pos will be used to @@ -397,11 +598,11 @@ void BufferView::saveBookmark(unsigned int idx) // pit and pos will be updated with bottom level pit/pos // when lyx exits. LyX::ref().session().bookmarks().save( - FileName(buffer_.fileName()), - cursor_.bottom().pit(), - cursor_.bottom().pos(), - cursor_.paragraph().id(), - cursor_.pos(), + buffer_.fileName(), + d->cursor_.bottom().pit(), + d->cursor_.bottom().pos(), + d->cursor_.paragraph().id(), + d->cursor_.pos(), idx ); if (idx) @@ -416,7 +617,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, bool success = false; DocIterator doc_it; - cursor_.clearSelection(); + d->cursor_.clearSelection(); // if a valid par_id is given, try it first // This is the case for a 'live' bookmark when unique paragraph ID @@ -458,9 +659,13 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // Note: only bottom (document) level pit is set. setCursor(doc_it); // set the current font. - cursor_.setCurrentFont(); - // center the screen on this new position. - center(); + d->cursor_.setCurrentFont(); + // To center the screen on this new position we need the + // paragraph position which is computed at draw() time. + // So we need a redraw! + buffer_.changed(); + if (fitCursor()) + showCursor(); } return success; @@ -470,16 +675,16 @@ 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 (cursor_.real_current_font.isRightToLeft()) { - if (intl_->keymap == Intl::PRIMARY) - intl_->keyMapSec(); + if (d->cursor_.real_current_font.isRightToLeft()) { + if (d->intl_.keymap == Intl::PRIMARY) + d->intl_.keyMapSec(); } else { - if (intl_->keymap == Intl::SECONDARY) - intl_->keyMapPrim(); + if (d->intl_.keymap == Intl::SECONDARY) + d->intl_.keyMapPrim(); } } - intl_->getTransManager().translateAndInsert(c, t, cur); + d->intl_.getTransManager().translateAndInsert(c, t, cur); } @@ -489,31 +694,61 @@ int BufferView::workWidth() const } -void BufferView::updateOffsetRef() +void BufferView::showCursor() { - // No need to update offset_ref_ in this case. - if (!need_centering_) - return; - // We are not properly started yet, delay until resizing is // done. if (height_ == 0) return; - CursorSlice & bot = cursor_.bottom(); - TextMetrics & tm = text_metrics_[bot.text()]; - ParagraphMetrics const & pm = tm.parMetrics(bot.pit()); - Point p = bv_funcs::coordOffset(*this, cursor_, cursor_.boundary()); - offset_ref_ = p.y_ + pm.ascent() - height_ / 2; + LYXERR(Debug::SCROLLING, "recentering!"); - need_centering_ = false; -} + CursorSlice & bot = d->cursor_.bottom(); + TextMetrics & tm = d->text_metrics_[bot.text()]; + pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1); + int bot_pit = d->cursor_.bottom().pit(); + if (bot_pit > max_pit) { + // FIXME: Why does this happen? + LYXERR0("bottom pit is greater that max pit: " + << bot_pit << " > " << max_pit); + bot_pit = max_pit; + } -void BufferView::center() -{ - anchor_ref_ = cursor_.bottom().pit(); - need_centering_ = true; + if (bot_pit == tm.first().first - 1) + tm.newParMetricsUp(); + else if (bot_pit == tm.last().first + 1) + tm.newParMetricsDown(); + + if (tm.has(bot_pit)) { + ParagraphMetrics const & pm = tm.parMetrics(bot_pit); + int offset = coordOffset(d->cursor_, d->cursor_.boundary()).y_; + int ypos = pm.position() + offset; + Dimension const & row_dim = d->cursor_.textRow().dimension(); + if (ypos - row_dim.ascent() < 0) + scrollUp(- ypos + row_dim.ascent()); + else if (ypos + row_dim.descent() > height_) + scrollDown(ypos - height_ + row_dim.descent()); + // else, nothing to do, the cursor is already visible so we just return. + return; + } + + tm.redoParagraph(bot_pit); + ParagraphMetrics const & pm = tm.parMetrics(bot_pit); + int offset = coordOffset(d->cursor_, d->cursor_.boundary()).y_; + + d->anchor_pit_ = bot_pit; + Dimension const & row_dim = d->cursor_.textRow().dimension(); + + if (d->anchor_pit_ == 0) + d->anchor_ypos_ = offset + pm.ascent(); + else if (d->anchor_pit_ == max_pit) + d->anchor_ypos_ = height_ - offset - row_dim.descent(); + else + d->anchor_ypos_ = offset + pm.ascent() - height_ / 2; + + updateMetrics(); + buffer_.changed(); } @@ -521,15 +756,15 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) { FuncStatus flag; - Cursor & cur = cursor_; + Cursor & cur = d->cursor_; switch (cmd.action) { case LFUN_UNDO: - flag.enabled(!buffer_.undostack().empty()); + flag.enabled(buffer_.undo().hasUndoStack()); break; case LFUN_REDO: - flag.enabled(!buffer_.redostack().empty()); + flag.enabled(buffer_.undo().hasRedoStack()); break; case LFUN_FILE_INSERT: case LFUN_FILE_INSERT_PLAINTEXT_PARA: @@ -540,12 +775,8 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) break; case LFUN_FONT_STATE: case LFUN_LABEL_INSERT: + case LFUN_INFO_INSERT: case LFUN_PARAGRAPH_GOTO: - // FIXME handle non-trivially - case LFUN_OUTLINE_UP: - case LFUN_OUTLINE_DOWN: - case LFUN_OUTLINE_IN: - case LFUN_OUTLINE_OUT: case LFUN_NOTE_NEXT: case LFUN_REFERENCE_NEXT: case LFUN_WORD_FIND: @@ -563,7 +794,7 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) case LFUN_LABEL_GOTO: { flag.enabled(!cmd.argument().empty() - || getInsetByCode(cur, Inset::REF_CODE)); + || getInsetByCode(cur, REF_CODE)); break; } @@ -592,6 +823,11 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) flag.setOnOff(buffer_.params().compressed); break; } + + case LFUN_BUFFER_TOGGLE_EMBEDDING: { + flag.setOnOff(buffer_.params().embedded); + break; + } case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: @@ -605,6 +841,60 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) flag.enabled(false); break; + case LFUN_LAYOUT_TABULAR: + flag.enabled(cur.innerInsetOfType(TABULAR_CODE)); + break; + + case LFUN_LAYOUT: + case LFUN_LAYOUT_PARAGRAPH: + flag.enabled(cur.inset().forceDefaultParagraphs(cur.idx())); + break; + + case LFUN_INSET_SETTINGS: { + InsetCode code = cur.inset().lyxCode(); + bool enable = false; + switch (code) { + case TABULAR_CODE: + enable = cmd.argument() == "tabular"; + break; + case ERT_CODE: + enable = cmd.argument() == "ert"; + break; + case FLOAT_CODE: + enable = cmd.argument() == "float"; + break; + case WRAP_CODE: + enable = cmd.argument() == "wrap"; + break; + case NOTE_CODE: + enable = cmd.argument() == "note"; + break; + case BRANCH_CODE: + enable = cmd.argument() == "branch"; + break; + case BOX_CODE: + enable = cmd.argument() == "box"; + break; + case LISTINGS_CODE: + enable = cmd.argument() == "listings"; + break; + default: + break; + } + flag.enabled(enable); + break; + } + + case LFUN_DIALOG_SHOW_NEW_INSET: + flag.enabled(cur.inset().lyxCode() != ERT_CODE && + cur.inset().lyxCode() != LISTINGS_CODE); + if (cur.inset().lyxCode() == CAPTION_CODE) { + FuncStatus flag; + if (cur.inset().getStatus(cur, cmd, flag)) + return flag; + } + break; + default: flag.enabled(false); } @@ -613,57 +903,33 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) } -Update::flags BufferView::dispatch(FuncRequest const & cmd) +bool BufferView::dispatch(FuncRequest const & cmd) { - //lyxerr << BOOST_CURRENT_FUNCTION - // << [ cmd = " << cmd << "]" << endl; + //lyxerr << [ cmd = " << cmd << "]" << endl; // Make sure that the cached BufferView is correct. - LYXERR(Debug::ACTION) << BOOST_CURRENT_FUNCTION - << " action[" << cmd.action << ']' + LYXERR(Debug::ACTION, " action[" << cmd.action << ']' << " arg[" << to_utf8(cmd.argument()) << ']' << " x[" << cmd.x << ']' << " y[" << cmd.y << ']' - << " button[" << cmd.button() << ']' - << endl; + << " button[" << cmd.button() << ']'); - Cursor & cur = cursor_; - // Default Update flags. - Update::flags updateFlags = Update::Force | Update::FitCursor; + Cursor & cur = d->cursor_; switch (cmd.action) { case LFUN_UNDO: cur.message(_("Undo")); cur.clearSelection(); - if (!textUndo(*this)) { + if (!cur.textUndo()) cur.message(_("No further undo information")); - updateFlags = Update::None; - } break; case LFUN_REDO: cur.message(_("Redo")); cur.clearSelection(); - if (!textRedo(*this)) { + if (!cur.textRedo()) cur.message(_("No further redo information")); - updateFlags = Update::None; - } - break; - - case LFUN_FILE_INSERT: - // FIXME UNICODE - menuInsertLyXFile(to_utf8(cmd.argument())); - break; - - case LFUN_FILE_INSERT_PLAINTEXT_PARA: - // FIXME UNICODE - insertPlaintextFile(this, to_utf8(cmd.argument()), true); - break; - - case LFUN_FILE_INSERT_PLAINTEXT: - // FIXME UNICODE - insertPlaintextFile(this, to_utf8(cmd.argument()), false); break; case LFUN_FONT_STATE: @@ -678,8 +944,8 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) docstring label = cmd.argument(); if (label.empty()) { InsetRef * inset = - getInsetByCode(cursor_, - Inset::REF_CODE); + getInsetByCode(d->cursor_, + REF_CODE); if (inset) { label = inset->getParam("reference"); // persistent=false: use temp_bookmark @@ -700,24 +966,21 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) ParIterator par = b->getParFromID(id); if (par == b->par_iterator_end()) { - LYXERR(Debug::INFO) - << "No matching paragraph found! [" - << id << "]." << endl; + LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "]."); } else { - LYXERR(Debug::INFO) - << "Paragraph " << par->id() + LYXERR(Debug::INFO, "Paragraph " << par->id() << " found in buffer `" - << b->fileName() << "'." << endl; + << b->absFileName() << "'."); if (b == &buffer_) { // Set the cursor setCursor(makeDocIterator(par, 0)); + showCursor(); } else { // Switch to other buffer view and resend cmd theLyXFunc().dispatch(FuncRequest( - LFUN_BUFFER_SWITCH, b->fileName())); + LFUN_BUFFER_SWITCH, b->absFileName())); theLyXFunc().dispatch(cmd); - updateFlags = Update::None; } break; } @@ -726,34 +989,15 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) break; } - case LFUN_OUTLINE_UP: - toc::outline(toc::Up, cursor_); - cursor_.text()->setCursor(cursor_, cursor_.pit(), 0); - updateLabels(buffer_); - break; - case LFUN_OUTLINE_DOWN: - toc::outline(toc::Down, cursor_); - cursor_.text()->setCursor(cursor_, cursor_.pit(), 0); - updateLabels(buffer_); - break; - case LFUN_OUTLINE_IN: - toc::outline(toc::In, cursor_); - updateLabels(buffer_); - break; - case LFUN_OUTLINE_OUT: - toc::outline(toc::Out, cursor_); - updateLabels(buffer_); - break; - case LFUN_NOTE_NEXT: - bv_funcs::gotoInset(this, Inset::NOTE_CODE, false); + gotoInset(this, NOTE_CODE, false); break; case LFUN_REFERENCE_NEXT: { - vector tmp; - tmp.push_back(Inset::LABEL_CODE); - tmp.push_back(Inset::REF_CODE); - bv_funcs::gotoInset(this, tmp, true); + vector tmp; + tmp.push_back(LABEL_CODE); + tmp.push_back(REF_CODE); + gotoInset(this, tmp, true); break; } @@ -795,21 +1039,21 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) case LFUN_ALL_CHANGES_ACCEPT: // select complete document - cursor_.reset(buffer_.inset()); - cursor_.selHandle(true); - buffer_.text().cursorBottom(cursor_); + d->cursor_.reset(buffer_.inset()); + d->cursor_.selHandle(true); + buffer_.text().cursorBottom(d->cursor_); // accept everything in a single step to support atomic undo - buffer_.text().acceptOrRejectChanges(cursor_, Text::ACCEPT); + buffer_.text().acceptOrRejectChanges(d->cursor_, Text::ACCEPT); break; case LFUN_ALL_CHANGES_REJECT: // select complete document - cursor_.reset(buffer_.inset()); - cursor_.selHandle(true); - buffer_.text().cursorBottom(cursor_); + d->cursor_.reset(buffer_.inset()); + d->cursor_.selHandle(true); + buffer_.text().cursorBottom(d->cursor_); // 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(cursor_, Text::REJECT); + buffer_.text().acceptOrRejectChanges(d->cursor_, Text::REJECT); break; case LFUN_WORD_FIND: @@ -858,14 +1102,14 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) break; case LFUN_SCREEN_RECENTER: - center(); + showCursor(); break; case LFUN_BIBTEX_DATABASE_ADD: { - Cursor tmpcur = cursor_; - bv_funcs::findInset(tmpcur, Inset::BIBTEX_CODE, false); + Cursor tmpcur = d->cursor_; + findInset(tmpcur, BIBTEX_CODE, false); InsetBibtex * inset = getInsetByCode(tmpcur, - Inset::BIBTEX_CODE); + BIBTEX_CODE); if (inset) { if (inset->addDatabase(to_utf8(cmd.argument()))) buffer_.updateBibfilesCache(); @@ -874,10 +1118,10 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) } case LFUN_BIBTEX_DATABASE_DEL: { - Cursor tmpcur = cursor_; - bv_funcs::findInset(tmpcur, Inset::BIBTEX_CODE, false); + Cursor tmpcur = d->cursor_; + findInset(tmpcur, BIBTEX_CODE, false); InsetBibtex * inset = getInsetByCode(tmpcur, - Inset::BIBTEX_CODE); + BIBTEX_CODE); if (inset) { if (inset->delDatabase(to_utf8(cmd.argument()))) buffer_.updateBibfilesCache(); @@ -919,6 +1163,11 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) // turn compression on/off buffer_.params().compressed = !buffer_.params().compressed; break; + + case LFUN_BUFFER_TOGGLE_EMBEDDING: + // turn embedding on/off + buffer_.embeddedFiles().enable(!buffer_.params().embedded); + break; case LFUN_NEXT_INSET_TOGGLE: { // this is the real function we want to invoke @@ -929,7 +1178,7 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) if (inset) { if (inset->isActive()) { Cursor tmpcur = cur; - tmpcur.pushLeft(*inset); + tmpcur.pushBackward(*inset); inset->dispatch(tmpcur, tmpcmd); if (tmpcur.result().dispatched()) { cur.dispatched(); @@ -950,69 +1199,67 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd) case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: { - Point p = bv_funcs::getPos(*this, cur, cur.boundary()); + Point p = getPos(cur, cur.boundary()); if (p.y_ < 0 || p.y_ > height_) { // The cursor is off-screen so recenter before proceeding. - center(); - updateMetrics(false); - //FIXME: updateMetrics() does not update paragraph position - // This is done at draw() time. So we need a redraw! - buffer_.changed(); - p = bv_funcs::getPos(*this, cur, cur.boundary()); + showCursor(); + p = getPos(cur, cur.boundary()); } scroll(cmd.action == LFUN_SCREEN_UP? - height_ : height_); cur.reset(buffer_.inset()); - text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); + d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); //FIXME: what to do with cur.x_target()? - finishUndo(); - // The metrics are already up to date. see scroll() - updateFlags = Update::None; + cur.finishUndo(); break; } case LFUN_SCREEN_UP_SELECT: case LFUN_SCREEN_DOWN_SELECT: { + // Those two are not ready yet for consumption. + return false; + cur.selHandle(true); size_t initial_depth = cur.depth(); - Point const p = bv_funcs::getPos(*this, cur, cur.boundary()); + Point const p = getPos(cur, cur.boundary()); scroll(cmd.action == LFUN_SCREEN_UP_SELECT? - height_ : height_); // FIXME: We need to verify if the cursor stayed within an inset... //cur.reset(buffer_.inset()); - text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); - finishUndo(); + d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); + cur.finishUndo(); while (cur.depth() > initial_depth) { cur.forwardInset(); } // FIXME: we need to do a redraw again because of the selection + // But no screen update is needed. + d->update_strategy_ = NoScreenUpdate; buffer_.changed(); - updateFlags = Update::Force | Update::FitCursor; break; } default: - updateFlags = Update::None; + return false; } - return updateFlags; + return true; } docstring const BufferView::requestSelection() { - Cursor & cur = cursor_; + Cursor & cur = d->cursor_; if (!cur.selection()) { - xsel_cache_.set = false; + d->xsel_cache_.set = false; return docstring(); } - if (!xsel_cache_.set || - cur.top() != xsel_cache_.cursor || - cur.anchor_.top() != xsel_cache_.anchor) + if (!d->xsel_cache_.set || + cur.top() != d->xsel_cache_.cursor || + cur.anchor_.top() != d->xsel_cache_.anchor) { - xsel_cache_.cursor = cur.top(); - xsel_cache_.anchor = cur.anchor_.top(); - xsel_cache_.set = cur.selection(); + d->xsel_cache_.cursor = cur.top(); + d->xsel_cache_.anchor = cur.anchor_.top(); + d->xsel_cache_.set = cur.selection(); return cur.selectionAsString(false); } return docstring(); @@ -1021,12 +1268,12 @@ docstring const BufferView::requestSelection() void BufferView::clearSelection() { - cursor_.clearSelection(); + d->cursor_.clearSelection(); // Clear the selection buffer. Otherwise a subsequent // middle-mouse-button paste would use the selection buffer, // not the more current external selection. cap::clearSelection(); - xsel_cache_.set = false; + d->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(); @@ -1039,61 +1286,46 @@ void BufferView::resize(int width, int height) width_ = width; height_ = height; - updateMetrics(false); -} - - -Inset const * BufferView::getCoveringInset(Text const & text, int x, int y) -{ - pit_type pit = text_metrics_[&text].getPitNearY(y); - BOOST_ASSERT(pit != -1); - Paragraph const & par = text.getPar(pit); - - LYXERR(Debug::DEBUG) - << BOOST_CURRENT_FUNCTION - << ": x: " << x - << " y: " << y - << " pit: " << pit - << endl; - InsetList::const_iterator iit = par.insetlist.begin(); - InsetList::const_iterator iend = par.insetlist.end(); - for (; iit != iend; ++iit) { - Inset * const inset = iit->inset; - if (inset->covers(*this, x, y)) { - if (!inset->descendable()) - // No need to go further down if the inset is not - // descendable. - return inset; - - size_t cell_number = inset->nargs(); - // Check all the inner cell. - for (size_t i = 0; i != cell_number; ++i) { - Text const * inner_text = inset->getText(i); - if (inner_text) { - // Try deeper. - Inset const * inset_deeper = - getCoveringInset(*inner_text, x, y); - if (inset_deeper) - return inset_deeper; - } - } + // Clear the paragraph height cache. + d->par_height_.clear(); + + updateMetrics(); +} + - LYXERR(Debug::DEBUG) - << BOOST_CURRENT_FUNCTION - << ": Hit inset: " << inset << endl; - return inset; +Inset const * BufferView::getCoveringInset(Text const & text, + int x, int y) const +{ + TextMetrics & tm = d->text_metrics_[&text]; + Inset * inset = tm.checkInsetHit(x, y); + if (!inset) + return 0; + + if (!inset->descendable()) + // No need to go further down if the inset is not + // descendable. + return inset; + + size_t cell_number = inset->nargs(); + // Check all the inner cell. + for (size_t i = 0; i != cell_number; ++i) { + Text const * inner_text = inset->getText(i); + if (inner_text) { + // Try deeper. + Inset const * inset_deeper = + getCoveringInset(*inner_text, x, y); + if (inset_deeper) + return inset_deeper; } } - LYXERR(Debug::DEBUG) - << BOOST_CURRENT_FUNCTION - << ": No inset hit. " << endl; - return 0; + + return inset; } -bool BufferView::workAreaDispatch(FuncRequest const & cmd0) +void BufferView::mouseEventDispatch(FuncRequest const & cmd0) { - //lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl; + //lyxerr << "[ cmd0 " << cmd0 << "]" << endl; // This is only called for mouse related events including // LFUN_FILE_OPEN generated by drag-and-drop. @@ -1101,7 +1333,7 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) Cursor cur(*this); cur.push(buffer_.inset()); - cur.selection() = cursor_.selection(); + cur.selection() = d->cursor_.selection(); // Either the inset under the cursor or the // surrounding Text will handle this event. @@ -1110,63 +1342,40 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) cmd.y = min(max(cmd.y, -1), height_); if (cmd.action == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) { - //FIXME: disabling mouse hover for now as it is causing funny things - // on screen. - return false; // Get inset under mouse, if there is one. Inset const * covering_inset = getCoveringInset(buffer_.text(), cmd.x, cmd.y); - if (covering_inset == last_inset_) + if (covering_inset == d->last_inset_) // Same inset, no need to do anything... - return false; + return; bool need_redraw = false; // const_cast because of setMouseHover(). Inset * inset = const_cast(covering_inset); - if (last_inset_) + if (d->last_inset_) // Remove the hint on the last hovered inset (if any). - need_redraw |= last_inset_->setMouseHover(false); + need_redraw |= d->last_inset_->setMouseHover(false); if (inset) // Highlighted the newly hovered inset (if any). 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, BufferView::draw() 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 Text::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); - } + d->last_inset_ = inset; + if (!need_redraw) + return; + + LYXERR(Debug::PAINTING, "Mouse hover detected at: (" + << cmd.x << ", " << cmd.y << ")"); + + d->update_strategy_ = DecorationUpdate; // This event (moving without mouse click) is not passed further. // This should be changed if it is further utilized. - return need_redraw; + buffer_.changed(); + return; } // Build temporary cursor. - Inset * inset = text_metrics_[&buffer_.text()].editXY(cur, cmd.x, cmd.y); + Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x, cmd.y); // Put anchor at the same position. cur.resetAnchor(); @@ -1175,9 +1384,8 @@ 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. @@ -1187,11 +1395,11 @@ bool BufferView::workAreaDispatch(FuncRequest const & cmd0) //Do we have a selection? theSelection().haveSelection(cursor().selection()); - // Redraw if requested and necessary. - if (cur.result().dispatched() && cur.result().update()) - return update(cur.result().update()); - - return false; + // If the command has been dispatched, + if (cur.result().dispatched() + // an update is asked, + && cur.result().update()) + processUpdateFlags(cur.result().update()); } @@ -1207,12 +1415,12 @@ void BufferView::scroll(int y) void BufferView::scrollDown(int offset) { Text * text = &buffer_.text(); - TextMetrics & tm = text_metrics_[text]; + TextMetrics & tm = d->text_metrics_[text]; int ymax = height_ + offset; while (true) { - std::pair const & last = tm.last(); - int bottom_pos = last.second.position() + last.second.descent(); - if (last.first == text->paragraphs().size() - 1) { + pair last = tm.last(); + int bottom_pos = last.second->position() + last.second->descent(); + if (last.first + 1 == int(text->paragraphs().size())) { if (bottom_pos <= height_) return; offset = min(offset, bottom_pos - height_); @@ -1222,8 +1430,8 @@ void BufferView::scrollDown(int offset) break; tm.newParMetricsDown(); } - offset_ref_ += offset; - updateMetrics(false); + d->anchor_ypos_ -= offset; + updateMetrics(); buffer_.changed(); } @@ -1231,11 +1439,11 @@ void BufferView::scrollDown(int offset) void BufferView::scrollUp(int offset) { Text * text = &buffer_.text(); - TextMetrics & tm = text_metrics_[text]; + TextMetrics & tm = d->text_metrics_[text]; int ymin = - offset; while (true) { - std::pair const & first = tm.first(); - int top_pos = first.second.position() - first.second.ascent(); + pair first = tm.first(); + int top_pos = first.second->position() - first.second->ascent(); if (first.first == 0) { if (top_pos >= 0) return; @@ -1246,8 +1454,8 @@ void BufferView::scrollUp(int offset) break; tm.newParMetricsUp(); } - offset_ref_ -= offset; - updateMetrics(false); + d->anchor_ypos_ += offset; + updateMetrics(); buffer_.changed(); } @@ -1259,11 +1467,11 @@ void BufferView::setCursorFromRow(int row) buffer_.texrow().getIdFromRow(row, tmpid, tmppos); - cursor_.reset(buffer_.inset()); + d->cursor_.reset(buffer_.inset()); if (tmpid == -1) - buffer_.text().setCursor(cursor_, 0, 0); + buffer_.text().setCursor(d->cursor_, 0, 0); else - buffer_.text().setCursor(cursor_, buffer_.getParFromID(tmpid).pit(), tmppos); + buffer_.text().setCursor(d->cursor_, buffer_.getParFromID(tmpid).pit(), tmppos); } @@ -1272,9 +1480,9 @@ void BufferView::gotoLabel(docstring const & label) for (InsetIterator it = inset_iterator_begin(buffer_.inset()); it; ++it) { vector labels; it->getLabelList(buffer_, labels); - if (std::find(labels.begin(), labels.end(), label) != labels.end()) { + if (find(labels.begin(), labels.end(), label) != labels.end()) { setCursor(it); - update(); + showCursor(); return; } } @@ -1289,9 +1497,9 @@ TextMetrics const & BufferView::textMetrics(Text const * t) const TextMetrics & BufferView::textMetrics(Text const * t) { - TextMetricsCache::iterator tmc_it = text_metrics_.find(t); - if (tmc_it == text_metrics_.end()) { - tmc_it = text_metrics_.insert( + TextMetricsCache::iterator tmc_it = d->text_metrics_.find(t); + if (tmc_it == d->text_metrics_.end()) { + tmc_it = d->text_metrics_.insert( make_pair(t, TextMetrics(this, const_cast(t)))).first; } return tmc_it->second; @@ -1315,10 +1523,10 @@ void BufferView::setCursor(DocIterator const & dit) { size_t const n = dit.depth(); for (size_t i = 0; i < n; ++i) - dit[i].inset().edit(cursor_, true); + dit[i].inset().edit(d->cursor_, true); - cursor_.setCursor(dit); - cursor_.selection() = false; + d->cursor_.setCursor(dit); + d->cursor_.selection() = false; } @@ -1329,7 +1537,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) return false; bool need_anchor_change = false; - bool changed = cursor_.text()->deleteEmptyParagraphMechanism(cur, old, + bool changed = d->cursor_.text()->deleteEmptyParagraphMechanism(cur, old, need_anchor_change); if (need_anchor_change) @@ -1340,33 +1548,37 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) updateLabels(buffer_); - updateMetrics(false); + updateMetrics(); buffer_.changed(); return true; } -bool BufferView::mouseSetCursor(Cursor & cur) +bool BufferView::mouseSetCursor(Cursor & cur, bool select) { BOOST_ASSERT(&cur.bv() == this); - // this event will clear selection so we save selection for - // persistent selection - cap::saveSelection(cursor()); + if (!select) + // this event will clear selection so we save selection for + // persistent selection + cap::saveSelection(cursor()); // Has the cursor just left the inset? bool badcursor = false; - bool leftinset = (&cursor_.inset() != &cur.inset()); + bool leftinset = (&d->cursor_.inset() != &cur.inset()); if (leftinset) - badcursor = notifyCursorLeaves(cursor_, cur); + badcursor = notifyCursorLeaves(d->cursor_, cur); + + // FIXME: shift-mouse selection doesn't work well across insets. + bool do_selection = select && &d->cursor_.anchor().inset() == &cur.inset(); // do the dEPM magic if needed // FIXME: (1) move this to InsetText::notifyCursorLeaves? // FIXME: (2) if we had a working InsetText::notifyCursorLeaves, // the leftinset bool would not be necessary (badcursor instead). bool update = leftinset; - if (!badcursor && cursor_.inTexted()) - update |= checkDepm(cur, cursor_); + if (!do_selection && !badcursor && d->cursor_.inTexted()) + update |= checkDepm(cur, d->cursor_); // if the cursor was in an empty script inset and the new // position is in the nucleus of the inset, notifyCursorLeaves @@ -1375,7 +1587,7 @@ bool BufferView::mouseSetCursor(Cursor & cur) // 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 <cursor_.setCursor(dit); + d->cursor_.boundary(cur.boundary()); + if (do_selection) + d->cursor_.setSelection(); + else + d->cursor_.clearSelection(); - cursor_.setCursor(dit); - cursor_.boundary(cur.boundary()); - cursor_.clearSelection(); - finishUndo(); + d->cursor_.finishUndo(); + d->cursor_.setCurrentFont(); return update; } @@ -1396,266 +1613,429 @@ bool BufferView::mouseSetCursor(Cursor & cur) void BufferView::putSelectionAt(DocIterator const & cur, int length, bool backwards) { - cursor_.clearSelection(); + d->cursor_.clearSelection(); setCursor(cur); if (length) { if (backwards) { - cursor_.pos() += length; - cursor_.setSelection(cursor_, -length); + d->cursor_.pos() += length; + d->cursor_.setSelection(d->cursor_, -length); } else - cursor_.setSelection(cursor_, length); + d->cursor_.setSelection(d->cursor_, length); } } Cursor & BufferView::cursor() { - return cursor_; + return d->cursor_; } Cursor const & BufferView::cursor() const { - return cursor_; + return d->cursor_; } pit_type BufferView::anchor_ref() const { - return anchor_ref_; + return d->anchor_pit_; } -ViewMetricsInfo const & BufferView::viewMetricsInfo() +bool BufferView::singleParUpdate() { - return metrics_info_; + Text & buftext = buffer_.text(); + pit_type const bottom_pit = d->cursor_.bottom().pit(); + TextMetrics & tm = textMetrics(&buftext); + int old_height = tm.parMetrics(bottom_pit).height(); + + // 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(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. + return false; + + d->update_strategy_ = SingleParUpdate; + + LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent() + << " y2: " << pm.position() + pm.descent() + << " pit: " << bottom_pit + << " singlepar: 1"); + return true; } -// FIXME: We should split-up updateMetrics() for the singlepar case. -void BufferView::updateMetrics(bool singlepar) +void BufferView::updateMetrics() { Text & buftext = buffer_.text(); pit_type const npit = int(buftext.paragraphs().size()); - if (anchor_ref_ > int(npit - 1)) { - anchor_ref_ = int(npit - 1); - offset_ref_ = 0; - } + // Clear out the position cache in case of full screen redraw, + d->coord_cache_.clear(); - if (!singlepar) { - // Clear out the position cache in case of full screen redraw, - coord_cache_.clear(); - - // Clear out paragraph metrics to avoid having invalid metrics - // in the cache from paragraphs not relayouted below - // The complete text metrics will be redone. - text_metrics_.clear(); - } + // Clear out paragraph metrics to avoid having invalid metrics + // in the cache from paragraphs not relayouted below + // The complete text metrics will be redone. + d->text_metrics_.clear(); TextMetrics & tm = textMetrics(&buftext); - pit_type const bottom_pit = cursor_.bottom().pit(); - // 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(bottom_pit)) { - - updateOffsetRef(); - // collect cursor paragraph iter bounds - ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); - int y1 = pm.position() - pm.ascent(); - int y2 = pm.position() + pm.descent(); - metrics_info_ = ViewMetricsInfo(bottom_pit, bottom_pit, y1, y2, - SingleParUpdate, npit); - LYXERR(Debug::PAINTING) - << BOOST_CURRENT_FUNCTION - << "\ny1: " << y1 - << " y2: " << y2 - << " pit: " << bottom_pit - << " singlepar: " << singlepar - << endl; - return; - } - - pit_type const pit = anchor_ref_; - int pit1 = pit; - int pit2 = pit; - // Rebreak anchor paragraph. - tm.redoParagraph(pit); - - updateOffsetRef(); + tm.redoParagraph(d->anchor_pit_); + ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_]; + anchor_pm.setPosition(d->anchor_ypos_); - int y0 = tm.parMetrics(pit).ascent() - offset_ref_; + LYXERR(Debug::PAINTING, "metrics: " + << " anchor pit = " << d->anchor_pit_ + << " anchor ypos = " << d->anchor_ypos_); // Redo paragraphs above anchor if necessary. - int y1 = y0; - while (y1 > 0 && pit1 > 0) { - y1 -= tm.parMetrics(pit1).ascent(); - --pit1; + int y1 = d->anchor_ypos_ - anchor_pm.ascent(); + // We are now just above the anchor paragraph. + pit_type pit1 = d->anchor_pit_ - 1; + for (; pit1 >= 0 && y1 >= 0; --pit1) { tm.redoParagraph(pit1); - y1 -= tm.parMetrics(pit1).descent(); - } - - // Take care of ascent of first line - y1 -= tm.parMetrics(pit1).ascent(); - - // Normalize anchor for next time - anchor_ref_ = pit1; - offset_ref_ = -y1; - - // Grey at the beginning is ugly - if (pit1 == 0 && y1 > 0) { - y0 -= y1; - y1 = 0; - anchor_ref_ = 0; + ParagraphMetrics & pm = tm.par_metrics_[pit1]; + y1 -= pm.descent(); + // Save the paragraph position in the cache. + pm.setPosition(y1); + y1 -= pm.ascent(); } // Redo paragraphs below the anchor if necessary. - int y2 = y0; - while (y2 < height_ && pit2 < int(npit) - 1) { - y2 += tm.parMetrics(pit2).descent(); - ++pit2; + int y2 = d->anchor_ypos_ + anchor_pm.descent(); + // We are now just below the anchor paragraph. + pit_type pit2 = d->anchor_pit_ + 1; + for (; pit2 < npit && y2 <= height_; ++pit2) { tm.redoParagraph(pit2); - y2 += tm.parMetrics(pit2).ascent(); + ParagraphMetrics & pm = tm.par_metrics_[pit2]; + y2 += pm.ascent(); + // Save the paragraph position in the cache. + pm.setPosition(y2); + y2 += pm.descent(); } - // Take care of descent of last line - y2 += tm.parMetrics(pit2).descent(); - - LYXERR(Debug::PAINTING) - << BOOST_CURRENT_FUNCTION - << "\n y1: " << y1 - << " y2: " << y2 - << " pit1: " << pit1 - << " pit2: " << pit2 - << " npit: " << npit - << " singlepar: " << singlepar - << endl; + LYXERR(Debug::PAINTING, "Metrics: " + << " anchor pit = " << d->anchor_pit_ + << " anchor ypos = " << d->anchor_ypos_ + << " y1 = " << y1 + << " y2 = " << y2 + << " pit1 = " << pit1 + << " pit2 = " << pit2); - metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, - FullScreenUpdate, npit); + d->update_strategy_ = FullScreenUpdate; if (lyxerr.debugging(Debug::WORKAREA)) { - LYXERR(Debug::WORKAREA) << "BufferView::updateMetrics" << endl; - coord_cache_.dump(); + LYXERR(Debug::WORKAREA, "BufferView::updateMetrics"); + d->coord_cache_.dump(); } } -void BufferView::menuInsertLyXFile(string const & filenm) +void BufferView::insertLyXFile(FileName const & fname) { - BOOST_ASSERT(cursor_.inTexted()); - string filename = filenm; - - if (filename.empty()) { - // Launch a file browser - // FIXME UNICODE - string initpath = lyxrc.document_path; - string const trypath = buffer_.filePath(); - // If directory is writeable, use this as default. - if (isDirWriteable(FileName(trypath))) - initpath = trypath; - - // FIXME UNICODE - FileDialog fileDlg(_("Select LyX document to insert"), - LFUN_FILE_INSERT, - make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)), - make_pair(_("Examples|#E#e"), - from_utf8(addPath(package().system_support().absFilename(), - "examples")))); - - FileDialog::Result result = - fileDlg.open(from_utf8(initpath), - FileFilterList(_("LyX Documents (*.lyx)")), - docstring()); - - if (result.first == FileDialog::Later) - return; - - // FIXME UNICODE - filename = to_utf8(result.second); - - // check selected filename - if (filename.empty()) { - // emit message signal. - message(_("Canceled.")); - return; - } - } + BOOST_ASSERT(d->cursor_.inTexted()); // Get absolute path of file and add ".lyx" // to the filename if necessary - filename = fileSearch(string(), filename, "lyx").absFilename(); + FileName filename = fileSearch(string(), fname.absFilename(), "lyx"); - docstring const disp_fn = makeDisplayPath(filename); + docstring const disp_fn = makeDisplayPath(filename.absFilename()); // emit message signal. message(bformat(_("Inserting document %1$s..."), disp_fn)); docstring res; Buffer buf("", false); - if (lyx::loadLyXFile(&buf, FileName(filename))) { + if (buf.loadLyXFile(filename)) { ErrorList & el = buffer_.errorList("Parse"); // Copy the inserted document error list into the current buffer one. el = buf.errorList("Parse"); - recordUndo(cursor_); - cap::pasteParagraphList(cursor_, buf.paragraphs(), - buf.params().getTextClass_ptr(), el); + buffer_.undo().recordUndo(d->cursor_); + cap::pasteParagraphList(d->cursor_, buf.paragraphs(), + buf.params().getTextClassPtr(), el); res = _("Document %1$s inserted."); - } else + } else { res = _("Could not insert document %1$s"); + } + updateMetrics(); + buffer_.changed(); // emit message signal. message(bformat(res, disp_fn)); buffer_.errors("Parse"); - updateMetrics(false); +} + + +Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const +{ + int x = 0; + int y = 0; + int lastw = 0; + + // Addup contribution of nested insets, from inside to outside, + // keeping the outer paragraph for a special handling below + for (size_t i = dit.depth() - 1; i >= 1; --i) { + CursorSlice const & sl = dit[i]; + int xx = 0; + int yy = 0; + + // get relative position inside sl.inset() + sl.inset().cursorPos(*this, sl, 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()) { + bool boundary_i = boundary && i + 1 == dit.depth(); + bool rtl = textMetrics(sl.text()).isRTL(sl, boundary_i); + if (rtl) + x -= lastw; + } + + // remember width for the case that sl.inset() is positioned in an RTL inset + if (i && dit[i - 1].text()) { + // If this Inset is inside a Text Inset, retrieve the Dimension + // from the containing text instead of using Inset::dimension() which + // might not be implemented. + // FIXME (Abdel 23/09/2007): this is a bit messy because of the + // elimination of Inset::dim_ cache. This coordOffset() method needs + // to be rewritten in light of the new design. + Dimension const & dim = parMetrics(dit[i - 1].text(), + dit[i - 1].pit()).insetDimension(&sl.inset()); + lastw = dim.wid; + } else { + Dimension const dim = sl.inset().dimension(*this); + lastw = dim.wid; + } + + //lyxerr << "Cursor::getPos, i: " + // << i << " x: " << xx << " y: " << y << endl; + } + + // Add contribution of initial rows of outermost paragraph + CursorSlice const & sl = dit[0]; + TextMetrics const & tm = textMetrics(sl.text()); + ParagraphMetrics const & pm = tm.parMetrics(sl.pit()); + BOOST_ASSERT(!pm.rows().empty()); + y -= pm.rows()[0].ascent(); +#if 1 + // FIXME: document this mess + size_t rend; + if (sl.pos() > 0 && dit.depth() == 1) { + int pos = sl.pos(); + if (pos && boundary) + --pos; +// lyxerr << "coordOffset: boundary:" << boundary << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl; + rend = pm.pos2row(pos); + } else + rend = pm.pos2row(sl.pos()); +#else + size_t rend = pm.pos2row(sl.pos()); +#endif + 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(), boundary && dit.depth() == 1); + x += xx; + + // In the RTL case place the nested inset at the left of the cursor in + // the outer paragraph + bool boundary_1 = boundary && 1 == dit.depth(); + bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1); + if (rtl) + x -= lastw; + + return Point(x, y); +} + + +Point BufferView::getPos(DocIterator const & dit, bool boundary) const +{ + CursorSlice const & bot = dit.bottom(); + TextMetrics const & tm = textMetrics(bot.text()); + if (!tm.has(bot.pit())) + return Point(-1, -1); + + Point p = coordOffset(dit, boundary); // offset from outer paragraph + p.y_ += tm.parMetrics(bot.pit()).position(); + return p; } void BufferView::draw(frontend::Painter & pain) { + LYXERR(Debug::PAINTING, "\t\t*** START DRAWING ***"); + Text & text = buffer_.text(); + TextMetrics const & tm = d->text_metrics_[&text]; + int const y = tm.first().second->position(); PainterInfo pi(this, pain); - // Should the whole screen, including insets, be refreshed? - // FIXME: We should also distinguish DecorationUpdate to avoid text - // drawing if possible. This is not possible to do easily right now - // because of the single backing pixmap. - pi.full_repaint = metrics_info_.update_strategy != SingleParUpdate; - - if (pi.full_repaint) - // Clear background (if not delegated to rows) - pain.fillRectangle(0, metrics_info_.y1, width_, - metrics_info_.y2 - metrics_info_.y1, + + switch (d->update_strategy_) { + + case NoScreenUpdate: + // If no screen painting is actually needed, only some the different + // coordinates of insets and paragraphs needs to be updated. + pi.full_repaint = true; + pi.pain.setDrawingEnabled(false); + tm.draw(pi, 0, y); + break; + + case SingleParUpdate: + pi.full_repaint = false; + // In general, only the current row of the outermost paragraph + // will be redrawn. Particular cases where selection spans + // multiple paragraph are correctly detected in TextMetrics. + tm.draw(pi, 0, y); + break; + + case DecorationUpdate: + // FIXME: We should also distinguish DecorationUpdate to avoid text + // drawing if possible. This is not possible to do easily right now + // because of the single backing pixmap. + + case FullScreenUpdate: + // The whole screen, including insets, will be refreshed. + pi.full_repaint = true; + + // Clear background. + pain.fillRectangle(0, 0, width_, height_, buffer_.inset().backgroundColor()); - LYXERR(Debug::PAINTING) << "\t\t*** START DRAWING ***" << endl; - Text & text = buffer_.text(); - TextMetrics const & tm = text_metrics_[&text]; - int y = metrics_info_.y1 + tm.parMetrics(metrics_info_.p1).ascent(); - if (!pi.full_repaint) - tm.drawParagraph(pi, metrics_info_.p1, 0, y); - else + // Draw everything. tm.draw(pi, 0, y); - LYXERR(Debug::PAINTING) << "\n\t\t*** END DRAWING ***" << endl; - - // and grey out above (should not happen later) -// lyxerr << "par ascent: " << text.getPar(metrics_info_.p1).ascent() << endl; - if (metrics_info_.y1 > 0 - && metrics_info_.update_strategy == FullScreenUpdate) - pain.fillRectangle(0, 0, width_, metrics_info_.y1, Color::bottomarea); - - // and possibly grey out below -// lyxerr << "par descent: " << text.getPar(metrics_info_.p1).ascent() << endl; - if (metrics_info_.y2 < height_ - && metrics_info_.update_strategy == FullScreenUpdate) - pain.fillRectangle(0, metrics_info_.y2, width_, - height_ - metrics_info_.y2, Color::bottomarea); + + // and possibly grey out below + pair lastpm = tm.last(); + int const y2 = lastpm.second->position() + lastpm.second->descent(); + if (y2 < height_) + pain.fillRectangle(0, y2, width_, height_ - y2, Color_bottomarea); + break; + } + LYXERR(Debug::PAINTING, "\n\t\t*** END DRAWING ***"); + + // The scrollbar needs an update. + updateScrollbar(); + + // Normalize anchor for next time + pair firstpm = tm.first(); + pair lastpm = tm.last(); + for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) { + ParagraphMetrics const & pm = tm.parMetrics(pit); + if (pm.position() + pm.descent() > 0) { + d->anchor_pit_ = pit; + d->anchor_ypos_ = pm.position(); + break; + } + } + LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_ + << " anchor ypos = " << d->anchor_ypos_); +} + + +void BufferView::message(docstring const & msg) +{ + if (d->gui_) + d->gui_->message(msg); +} + + +void BufferView::showDialog(string const & name) +{ + if (d->gui_) + d->gui_->showDialog(name, string()); +} + + +void BufferView::showDialog(string const & name, + string const & data, Inset * inset) +{ + if (d->gui_) + d->gui_->showDialog(name, data, inset); +} + + +void BufferView::updateDialog(string const & name, string const & data) +{ + if (d->gui_) + d->gui_->updateDialog(name, data); +} + + +void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate * gui) +{ + d->gui_ = gui; +} + + +// FIXME: Move this out of BufferView again +docstring BufferView::contentsOfPlaintextFile(FileName const & fname) +{ + if (!fname.isReadableFile()) { + docstring const error = from_ascii(strerror(errno)); + docstring const file = makeDisplayPath(fname.absFilename(), 50); + docstring const text = + bformat(_("Could not read the specified document\n" + "%1$s\ndue to the error: %2$s"), file, error); + Alert::error(_("Could not read file"), text); + return docstring(); + } + + if (!fname.isReadableFile()) { + docstring const file = makeDisplayPath(fname.absFilename(), 50); + docstring const text = + bformat(_("%1$s\n is not readable."), file); + Alert::error(_("Could not open file"), text); + return docstring(); + } + + // FIXME UNICODE: We don't know the encoding of the file + docstring file_content = fname.fileContents("UTF-8"); + if (file_content.empty()) { + Alert::error(_("Reading not UTF-8 encoded file"), + _("The file is not UTF-8 encoded.\n" + "It will be read as local 8Bit-encoded.\n" + "If this does not give the correct result\n" + "then please change the encoding of the file\n" + "to UTF-8 with a program other than LyX.\n")); + file_content = fname.fileContents("local8bit"); + } + + return normalize_c(file_content); +} + + +void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph) +{ + docstring const tmpstr = contentsOfPlaintextFile(f); + + if (tmpstr.empty()) + return; + + Cursor & cur = cursor(); + cap::replaceSelection(cur); + buffer_.undo().recordUndo(cur); + if (asParagraph) + cur.innerText()->insertStringAsParagraphs(cur, tmpstr); + else + cur.innerText()->insertStringAsLines(cur, tmpstr); + + updateMetrics(); + buffer_.changed(); } } // namespace lyx