]> git.lyx.org Git - lyx.git/blobdiff - src/BufferView_pimpl.C
the convert patch
[lyx.git] / src / BufferView_pimpl.C
index 63721950ec527afcff3ac94ba68db038e7f8c6ec..7491e27213e26af48a1458d9a254455cdedc8ea5 100644 (file)
@@ -23,6 +23,7 @@
 #include "buffer_funcs.h"
 #include "bufferlist.h"
 #include "bufferparams.h"
+#include "coordcache.h"
 #include "cursor.h"
 #include "debug.h"
 #include "dispatchresult.h"
@@ -33,6 +34,7 @@
 #include "gettext.h"
 #include "intl.h"
 #include "insetiterator.h"
+#include "LaTeXFeatures.h"
 #include "lyx_cb.h" // added for Dispatch functions
 #include "lyx_main.h"
 #include "lyxfind.h"
 #include "lyxtext.h"
 #include "lyxrc.h"
 #include "lastfiles.h"
+#include "metricsinfo.h"
 #include "paragraph.h"
 #include "paragraph_funcs.h"
 #include "ParagraphParameters.h"
 #include "pariterator.h"
+#include "rowpainter.h"
 #include "undo.h"
 #include "vspace.h"
 
@@ -53,6 +57,7 @@
 #include "frontends/Alert.h"
 #include "frontends/Dialogs.h"
 #include "frontends/FileDialog.h"
+#include "frontends/font_metrics.h"
 #include "frontends/LyXView.h"
 #include "frontends/LyXScreenFactory.h"
 #include "frontends/screen.h"
 
 #include "graphics/Previews.h"
 
+#include "support/convert.h"
+#include "support/filefilterlist.h"
 #include "support/filetools.h"
 #include "support/forkedcontr.h"
-#include "support/globbing.h"
-#include "support/path_defines.h"
-#include "support/tostr.h"
+#include "support/package.h"
+#include "support/types.h"
 
 #include <boost/bind.hpp>
 
+#include <functional>
+
 using lyx::pos_type;
 
 using lyx::support::AddPath;
@@ -78,14 +86,16 @@ using lyx::support::FileSearch;
 using lyx::support::ForkedcallsController;
 using lyx::support::IsDirWriteable;
 using lyx::support::MakeDisplayPath;
-using lyx::support::strToUnsignedInt;
-using lyx::support::system_lyxdir;
+using lyx::support::MakeAbsPath;
+using lyx::support::package;
 
 using std::endl;
 using std::istringstream;
 using std::make_pair;
 using std::min;
+using std::max;
 using std::string;
+using std::mem_fun_ref;
 
 
 extern BufferList bufferlist;
@@ -109,17 +119,32 @@ boost::signals::connection selectioncon;
 boost::signals::connection lostcon;
 
 
+/// Get next inset of this class from current cursor position
+template <class T>
+T * getInsetByCode(LCursor & cur, InsetBase::Code code)
+{
+       T * inset = 0;
+       DocIterator it = cur;
+       if (it.nextInset() &&
+           it.nextInset()->lyxCode() == code) {
+               inset = static_cast<T*>(it.nextInset());
+       }
+       return inset;
+}
+
+
 } // anon namespace
 
 
 BufferView::Pimpl::Pimpl(BufferView & bv, LyXView * owner,
-            int xpos, int ypos, int width, int height)
+                        int width, int height)
        : bv_(&bv), owner_(owner), buffer_(0), cursor_timeout(400),
-         using_xterm_cursor(false), cursor_(bv)
+         using_xterm_cursor(false), cursor_(bv) ,
+         anchor_ref_(0), offset_ref_(0)
 {
        xsel_cache_.set = false;
 
-       workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
+       workarea_.reset(WorkAreaFactory::create(*owner_, width, height));
        screen_.reset(LyXScreenFactory::create(workarea()));
 
        // Setup the signals
@@ -290,18 +315,6 @@ Painter & BufferView::Pimpl::painter() const
 }
 
 
-void BufferView::Pimpl::top_y(int y)
-{
-       top_y_ = y;
-}
-
-
-int BufferView::Pimpl::top_y() const
-{
-       return top_y_;
-}
-
-
 void BufferView::Pimpl::setBuffer(Buffer * b)
 {
        lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
@@ -309,12 +322,23 @@ void BufferView::Pimpl::setBuffer(Buffer * b)
        if (buffer_)
                disconnectBuffer();
 
-       // set current buffer
-       buffer_ = b;
+       // if we are closing current buffer, switch to the first in
+       // buffer list.
+       if (!b) {
+               lyxerr[Debug::INFO] << "  No Buffer!" << endl;
+               // we are closing the buffer, use the first buffer as current
+               buffer_ = bufferlist.first();
+               owner_->getDialogs().hideBufferDependent();
+       } else {
+               // set current buffer
+               buffer_ = b;
+       }
 
        // reset old cursor
-       top_y_ = 0;
        cursor_ = LCursor(*bv_);
+       anchor_ref_ = 0;
+       offset_ref_ = 0;
+
 
        // if we're quitting lyx, don't bother updating stuff
        if (quitting)
@@ -327,58 +351,33 @@ void BufferView::Pimpl::setBuffer(Buffer * b)
                cursor_.push(buffer_->inset());
                cursor_.resetAnchor();
                buffer_->text().init(bv_);
-
-               // If we don't have a text object for this, we make one
-               //if (bv_->text() == 0)
-               //      resizeCurrentBuffer();
+               buffer_->text().setCurrentFont(cursor_);
 
                // Buffer-dependent dialogs should be updated or
                // hidden. This should go here because some dialogs (eg ToC)
                // require bv_->text.
                owner_->getDialogs().updateBufferDependent(true);
-               owner_->setLayout(bv_->text()->getPar(0).layout()->name());
-       } else {
-               lyxerr[Debug::INFO] << "  No Buffer!" << endl;
-               // we are closing the buffer, use the first buffer as current
-               buffer_ = bufferlist.first();
-               owner_->getDialogs().hideBufferDependent();
        }
 
        update();
        updateScrollbar();
        owner_->updateMenubar();
-       owner_->updateToolbar();
+       owner_->updateToolbars();
        owner_->updateLayoutChoice();
        owner_->updateWindowTitle();
 
-       if (lyx::graphics::Previews::activated() && buffer_)
-               lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
-}
-
-
-bool BufferView::Pimpl::fitCursor()
-{
-       if (!screen().fitCursor(bv_))
-               return false;
-       updateScrollbar();
-       return true;
-}
-
+       // This is done after the layout combox has been populated
+       if (buffer_)
+               owner_->setLayout(cursor_.paragraph().layout()->name());
 
-void BufferView::Pimpl::redoCurrentBuffer()
-{
-       lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
-       if (buffer_ && bv_->text()) {
-               resizeCurrentBuffer();
-               updateScrollbar();
-               owner_->updateLayoutChoice();
-       }
+       if (buffer_ && lyx::graphics::Previews::status() != LyXRC::PREVIEW_OFF)
+               lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
 }
 
 
 void BufferView::Pimpl::resizeCurrentBuffer()
 {
-       lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
+       lyxerr[Debug::DEBUG] << "resizeCurrentBuffer" << endl;
        owner_->busy(true);
        owner_->message(_("Formatting document..."));
 
@@ -388,8 +387,6 @@ void BufferView::Pimpl::resizeCurrentBuffer()
 
        text->init(bv_);
        update();
-       bv_->cursor().updatePos();
-       fitCursor();
 
        switchKeyMap();
        owner_->busy(false);
@@ -404,20 +401,28 @@ void BufferView::Pimpl::resizeCurrentBuffer()
 void BufferView::Pimpl::updateScrollbar()
 {
        if (!bv_->text()) {
-               lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
-               lyxerr << "no text in updateScrollbar" << endl;
+               lyxerr[Debug::DEBUG] << "no text in updateScrollbar" << endl;
                workarea().setScrollbarParams(0, 0, 0);
                return;
        }
 
-       LyXText const & t = *bv_->text();
+       LyXText & t = *bv_->text();
+       if (anchor_ref_ >  int(t.paragraphs().size()) - 1) {
+               anchor_ref_ = int(t.paragraphs().size()) - 1;
+               offset_ref_ = 0;
+       }
 
        lyxerr[Debug::GUI]
-               << "Updating scrollbar: height: " << t.height()
-               << " top_y: " << top_y()
+               << "Updating scrollbar: height: " << t.paragraphs().size()
+               << " curr par: " << bv_->cursor().bottom().pit()
                << " default height " << defaultRowHeight() << endl;
 
-       workarea().setScrollbarParams(t.height(), top_y(), defaultRowHeight());
+       //it would be better to fix the scrollbar to understand
+       //values in [0..1] and divide everything by wh
+       int const wh = workarea().workHeight() / 4;
+       int const h = t.getPar(anchor_ref_).height();
+       workarea().setScrollbarParams(t.paragraphs().size() * wh, anchor_ref_ * wh + int(offset_ref_ * wh / float(h)), int (wh * defaultRowHeight() / float(h)));
+//     workarea().setScrollbarParams(t.paragraphs().size(), anchor_ref_, 1);
 }
 
 
@@ -430,48 +435,69 @@ void BufferView::Pimpl::scrollDocView(int value)
 
        screen().hideCursor();
 
-       top_y(value);
-       screen().redraw(*bv_);
+       int const wh = workarea().workHeight() / 4;
+
+       LyXText & t = *bv_->text();
+
+       float const bar = value / float(wh * t.paragraphs().size());
+
+       anchor_ref_ = int(bar * t.paragraphs().size());
+       t.redoParagraph(anchor_ref_);
+       int const h = t.getPar(anchor_ref_).height();
+       offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
+       lyxerr << "scrolling: " << value << std::endl;
+       update();
 
        if (!lyxrc.cursor_follows_scrollbar)
                return;
 
-       int const height = defaultRowHeight();
-       int const first = top_y() + height;
-       int const last = top_y() + workarea().workHeight() - height;
+       int const height = 2 * defaultRowHeight();
+       int const first = height;
+       int const last = workarea().workHeight() - height;
+       LCursor & cur = bv_->cursor();
 
-       bv_->cursor().reset(bv_->buffer()->inset());
-       LyXText * text = bv_->text();
-       int y = text->cursorY(bv_->cursor().front());
-       if (y < first)
-               y = first;
-       if (y > last)
-               y = last;
-       text->setCursorFromCoordinates(bv_->cursor(), 0, y);
+       bv_funcs::CurStatus st = bv_funcs::status(bv_, cur);
 
+       switch (st) {
+       case bv_funcs::CUR_ABOVE:
+               t.setCursorFromCoordinates(cur, 0, first);
+               cur.clearSelection();
+               break;
+       case bv_funcs::CUR_BELOW:
+               t.setCursorFromCoordinates(cur, 0, last);
+               cur.clearSelection();
+               break;
+       case bv_funcs::CUR_INSIDE:
+               int const y = bv_funcs::getPos(cur).y_;
+               int const newy = min(last, max(y, first));
+               if (y != newy) {
+                       cur.reset(bv_->buffer()->inset());
+                       t.setCursorFromCoordinates(cur, 0, newy);
+               }
+       }
        owner_->updateLayoutChoice();
 }
 
 
 void BufferView::Pimpl::scroll(int lines)
 {
-       if (!buffer_)
-               return;
-
-       LyXText const * t = bv_->text();
-       int const line_height = defaultRowHeight();
-
-       // The new absolute coordinate
-       int new_top_y = top_y() + lines * line_height;
-
-       // Restrict to a valid value
-       new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
-       new_top_y = std::max(0, new_top_y);
-
-       scrollDocView(new_top_y);
-
-       // Update the scrollbar.
-       workarea().setScrollbarParams(t->height(), top_y(), defaultRowHeight());
+//     if (!buffer_)
+//             return;
+//
+//     LyXText const * t = bv_->text();
+//     int const line_height = defaultRowHeight();
+//
+//     // The new absolute coordinate
+//     int new_top_y = top_y() + lines * line_height;
+//
+//     // Restrict to a valid value
+//     new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
+//     new_top_y = std::max(0, new_top_y);
+//
+//     scrollDocView(new_top_y);
+//
+//     // Update the scrollbar.
+//     workarea().setScrollbarParams(t->height(), top_y(), defaultRowHeight());
 }
 
 
@@ -558,22 +584,51 @@ void BufferView::Pimpl::workAreaResize()
 }
 
 
-void BufferView::Pimpl::update()
+bool BufferView::Pimpl::fitCursor()
 {
-       //lyxerr << "BufferView::Pimpl::update(), buffer: " << buffer_ << endl;
-       // fix cursor coordinate cache in case something went wrong
+       if (bv_funcs::status(bv_, bv_->cursor()) == bv_funcs::CUR_INSIDE) {
+               LyXFont const font = bv_->cursor().getFont();
+               int const asc = font_metrics::maxAscent(font);
+               int const des = font_metrics::maxDescent(font);
+               Point p = bv_funcs::getPos(bv_->cursor());
+               if (p.y_ - asc >= 0 && p.y_ + des < bv_->workHeight())
+                       return false;
+       }
+       bv_->center();
+       return true;
+}
+
+
+void BufferView::Pimpl::update(bool fitcursor, bool forceupdate)
+{
+       lyxerr << "BufferView::Pimpl::update(fc=" << fitcursor << ", fu="
+              << forceupdate << ")  buffer: " << buffer_ << endl;
 
        // check needed to survive LyX startup
        if (buffer_) {
-               // update all 'visible' paragraphs
-               lyx::par_type beg, end;
-               getParsInRange(buffer_->paragraphs(),
-                              top_y(), top_y() + workarea().workHeight(),
-                              beg, end);
-               bv_->text()->redoParagraphs(beg, end);
-               updateScrollbar();
-       }
-       screen().redraw(*bv_);
+               // update macro store
+               buffer_->buildMacros();
+               // first drawing step
+
+               CoordCache backup;
+               std::swap(theCoords, backup);
+               //
+               ViewMetricsInfo vi = metrics();
+
+               if (fitcursor && fitCursor()) {
+                       forceupdate = true;
+                       vi = metrics();
+               }
+               if (forceupdate) {
+                       // second drawing step
+                       screen().redraw(*bv_, vi);
+               } else
+                       std::swap(theCoords, backup);
+       } else
+               screen().greyOut();
+
+       // and the scrollbar
+       updateScrollbar();
        bv_->owner()->view_state_changed();
 }
 
@@ -613,7 +668,7 @@ Change const BufferView::Pimpl::getCurrentChange()
        if (!cur.selection())
                return Change(Change::UNCHANGED);
 
-       return text->getPar(cur.selBegin().par()).
+       return text->getPar(cur.selBegin().pit()).
                        lookupChangeFull(cur.selBegin().pos());
 }
 
@@ -627,7 +682,7 @@ void BufferView::Pimpl::savePosition(unsigned int i)
                                      bv_->cursor().paragraph().id(),
                                      bv_->cursor().pos());
        if (i > 0)
-               owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
+               owner_->message(bformat(_("Saved bookmark %1$d"), i));
 }
 
 
@@ -660,7 +715,7 @@ void BufferView::Pimpl::restorePosition(unsigned int i)
                min(par->size(), saved_positions[i].par_pos));
 
        if (i > 0)
-               owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
+               owner_->message(bformat(_("Moved to bookmark %1$d"), i));
 }
 
 
@@ -688,71 +743,19 @@ void BufferView::Pimpl::switchKeyMap()
 
 void BufferView::Pimpl::center()
 {
-       LyXText * text = bv_->text();
-
-       bv_->cursor().clearSelection();
-       int const half_height = workarea().workHeight() / 2;
-       int new_y = text->cursorY(bv_->cursor().front()) - half_height;
-       if (new_y < 0)
-               new_y = 0;
-
-       // FIXME: look at this comment again ...
-       // This updates top_y() but means the fitCursor() call
-       // from the update(FITCUR) doesn't realise that we might
-       // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
-       // the scrollbar to be updated as it should, so we have
-       // to do it manually. Any operation that does a center()
-       // and also might have moved top_y() must make sure to call
-       // updateScrollbar() currently. Never mind that this is a
-       // pretty obfuscated way of updating text->top_y()
-       top_y(new_y);
-}
-
-
-void BufferView::Pimpl::stuffClipboard(string const & stuff) const
-{
-       workarea().putClipboard(stuff);
+       CursorSlice const & bot = bv_->cursor().bottom();
+       lyx::pit_type const pit = bot.pit();
+       bot.text()->redoParagraph(pit);
+       Paragraph const & par = bot.text()->paragraphs()[pit];
+       anchor_ref_ = pit;
+       offset_ref_ = bv_funcs::coordOffset(bv_->cursor()).y_ + par.ascent()
+               - workarea().workHeight() / 2;
 }
 
 
-InsetBase * BufferView::Pimpl::getInsetByCode(InsetBase::Code /*code*/)
+void BufferView::Pimpl::stuffClipboard(string const & content) const
 {
-#ifdef WITH_WARNINGS
-#warning Does not work for mathed
-#endif
-       // Ok, this is a little bit too brute force but it
-       // should work for now. Better infrastructure is coming. (Lgb)
-
-#warning FIXME
-#if 0
-       Buffer * buf = bv_->buffer();
-       InsetIterator beg = inset_iterator_begin(buf->inset());
-
-       bool cursor_par_seen = false;
-
-       LCursor & cur = bv_->cursor();
-       LyXText * = bv_->getLyXText();
-       ParagraphList::iterator pit = text->getPar(cur.par());
-
-       for (; beg; ++beg) {
-               if (beg.par() == pit)
-                       cursor_par_seen = true;
-               if (cursor_par_seen) {
-                       if (beg.par() == pit && beg.pos() >= cur.pos())
-                               break;
-                       if (beg.getPar() != pit)
-                               break;
-               }
-       }
-       if (beg) {
-               // Now find the first inset that matches code.
-               for (; beg; ++beg) {
-                       if (beg->lyxCode() == code)
-                               return &(*beg);
-               }
-       }
-#endif
-       return 0;
+       workarea().putClipboard(content);
 }
 
 
@@ -776,7 +779,7 @@ void BufferView::Pimpl::MenuInsertLyXFile(string const & filenm)
                        make_pair(string(_("Documents|#o#O")),
                                  string(lyxrc.document_path)),
                        make_pair(string(_("Examples|#E#e")),
-                                 string(AddPath(system_lyxdir(), "examples"))));
+                                 string(AddPath(package().system_support(), "examples"))));
 
                FileDialog::Result result =
                        fileDlg.open(initpath,
@@ -801,12 +804,19 @@ void BufferView::Pimpl::MenuInsertLyXFile(string const & filenm)
 
        string const disp_fn = MakeDisplayPath(filename);
        owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
-       if (bv_->insertLyXFile(filename))
-               owner_->message(bformat(_("Document %1$s inserted."),
-                                       disp_fn));
-       else
-               owner_->message(bformat(_("Could not insert document %1$s"),
-                                       disp_fn));
+
+       bv_->cursor().clearSelection();
+       bv_->text()->breakParagraph(bv_->cursor());
+
+       BOOST_ASSERT(bv_->cursor().inTexted());
+
+       string const fname = MakeAbsPath(filename);
+       bool const res = bv_->buffer()->readFile(fname, bv_->cursor().pit());
+       bv_->resize();
+
+       string s = res ? _("Document %1$s inserted.")
+                      : _("Could not insert document %1$s");
+       owner_->message(bformat(s, disp_fn));
 }
 
 
@@ -816,9 +826,9 @@ void BufferView::Pimpl::trackChanges()
        bool const tracking = buf->params().tracking_changes;
 
        if (!tracking) {
-               ParIterator const end = buf->par_iterator_end();
-               for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
-                       it->trackChanges();
+               for_each(buf->par_iterator_begin(),
+                        buf->par_iterator_end(),
+                        bind(&Paragraph::trackChanges, _1, Change::UNCHANGED));
                buf->params().tracking_changes = true;
 
                // we cannot allow undos beyond the freeze point
@@ -835,9 +845,10 @@ void BufferView::Pimpl::trackChanges()
                        return;
                }
 
-               ParIterator const end = buf->par_iterator_end();
-               for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
-                       it->untrackChanges();
+               for_each(buf->par_iterator_begin(),
+                        buf->par_iterator_end(),
+                        mem_fun_ref(&Paragraph::untrackChanges));
+
                buf->params().tracking_changes = false;
        }
 
@@ -847,21 +858,23 @@ void BufferView::Pimpl::trackChanges()
 
 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
 {
+       lyxerr << "BufferView::Pimpl::workAreaDispatch: request: "
+         << cmd0 << std::endl;
        // this is only called for mouse related events including
        // LFUN_FILE_OPEN generated by drag-and-drop.
        FuncRequest cmd = cmd0;
 
-       // handle drag&deop
+       // handle drag&drop
        if (cmd.action == LFUN_FILE_OPEN) {
                owner_->dispatch(cmd);
                return true;
        }
 
-       cmd.y += bv_->top_y();
-       //lyxerr << "*** workAreaDispatch: request: " << cmd << std::endl;
+       if (!bv_->buffer())
+               return false;
+
        LCursor cur(*bv_);
        cur.push(bv_->buffer()->inset());
-       cur.resetAnchor();
        cur.selection() = bv_->cursor().selection();
 
        // Doesn't go through lyxfunc, so we need to update
@@ -873,37 +886,34 @@ bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
 
        screen().hideCursor();
 
-       // either the inset under the cursor or the
+       // Either the inset under the cursor or the
        // surrounding LyXText will handle this event.
 
-       // built temporary path to inset
+       // Build temporary cursor.
+       cmd.y = min(max(cmd.y,-1), bv_->workHeight());
        InsetBase * inset = bv_->text()->editXY(cur, cmd.x, cmd.y);
-       lyxerr << "hit inset at tip: " << inset << endl;
-       lyxerr << "created temp cursor:\n" << cur << endl;
+       lyxerr << " * hit inset at tip: " << inset << endl;
+       lyxerr << " * created temp cursor:" << cur << endl;
+
+       // Put anchor at the same position.
+       cur.resetAnchor();
 
        // Try to dispatch to an non-editable inset near this position
        // 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)'
-       DispatchResult res;
+       //  cur.bv().cursor() = cur;  (or similar)
        if (inset)
                inset->dispatch(cur, cmd);
 
-       // Now dispatch to the real cursor. Any change to the cursor
-       // is immediate.
-       if (!res.dispatched())
-               res = cur.dispatch(cmd);
-
-       // If the request was dispatched the temp cursor should have been
-       // in a way to be used as new 'real' cursor.
-       if (res.dispatched())
-               bv_->cursor() = cur;
+       // 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);
 
-       // Redraw if requested or necessary.
-       if (res.update())
-               update();
-       if (fitCursor())
-               update();
+       if (cur.result().dispatched()) {
+               // Redraw if requested or necessary.
+               update(cur.result().update(), cur.result().update());
+       }
 
        // see workAreaKeyPress
        cursor_timeout.restart();
@@ -912,7 +922,7 @@ bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
        // skip these when selecting
        if (cmd.action != LFUN_MOUSE_MOTION) {
                owner_->updateLayoutChoice();
-               owner_->updateToolbar();
+               owner_->updateToolbars();
        }
 
        // slight hack: this is only called currently when we
@@ -950,18 +960,26 @@ FuncStatus BufferView::Pimpl::getStatus(FuncRequest const & cmd)
        case LFUN_MARK_ON:
        case LFUN_SETMARK:
        case LFUN_CENTER:
-       case LFUN_BEGINNINGBUFSEL:
-       case LFUN_ENDBUFSEL:
+       case LFUN_WORDS_COUNT:
                flag.enabled(true);
                break;
+
        case LFUN_BOOKMARK_GOTO:
-               flag.enabled(bv_->isSavedPosition(strToUnsignedInt(cmd.argument)));
+               flag.enabled(bv_->isSavedPosition(convert<unsigned int>(cmd.argument)));
                break;
        case LFUN_TRACK_CHANGES:
                flag.enabled(true);
                flag.setOnOff(buf->params().tracking_changes);
                break;
 
+       case LFUN_OUTPUT_CHANGES: {
+               LaTeXFeatures features(*buf, buf->params(), false);
+               flag.enabled(buf && buf->params().tracking_changes
+                       && features.isAvailable("dvipost"));
+               flag.setOnOff(buf->params().output_changes);
+               break;
+       }
+
        case LFUN_MERGE_CHANGES:
        case LFUN_ACCEPT_CHANGE: // what about these two
        case LFUN_REJECT_CHANGE: // what about these two
@@ -1032,29 +1050,20 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
                cur.message(cur.currentState());
                break;
 
-       case LFUN_INSERT_LABEL: {
-               // Try and generate a valid label
-               string const contents = cmd.argument.empty() ?
-                       cur.getPossibleLabel() : cmd.argument;
-               InsetCommandParams icp("label", contents);
-               string data = InsetCommandMailer::params2string("label", icp);
-               owner_->getDialogs().show("label", data, 0);
-               break;
-       }
-
        case LFUN_BOOKMARK_SAVE:
-               savePosition(strToUnsignedInt(cmd.argument));
+               savePosition(convert<unsigned int>(cmd.argument));
                break;
 
        case LFUN_BOOKMARK_GOTO:
-               restorePosition(strToUnsignedInt(cmd.argument));
+               restorePosition(convert<unsigned int>(cmd.argument));
                break;
 
        case LFUN_REF_GOTO: {
                string label = cmd.argument;
                if (label.empty()) {
                        InsetRef * inset =
-                               static_cast<InsetRef*>(getInsetByCode(InsetBase::REF_CODE));
+                               getInsetByCode<InsetRef>(bv_->cursor(),
+                                                        InsetBase::REF_CODE);
                        if (inset) {
                                label = inset->getContents();
                                savePosition(0);
@@ -1070,6 +1079,13 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
                trackChanges();
                break;
 
+       case LFUN_OUTPUT_CHANGES: {
+               Buffer * buf = bv_->buffer();
+               bool const state = buf->params().output_changes;
+               buf->params().output_changes = !state;
+               break;
+       }
+
        case LFUN_MERGE_CHANGES:
                owner_->getDialogs().show("changes");
                break;
@@ -1092,7 +1108,6 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
 #endif
                while (lyx::find::findNextChange(bv_))
                        bv_->getLyXText()->rejectChange(bv_->cursor());
-               update();
                break;
        }
 
@@ -1106,7 +1121,6 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
 
        case LFUN_MARK_OFF:
                cur.clearSelection();
-               update();
                cur.resetAnchor();
                cur.message(N_("Mark off"));
                break;
@@ -1114,7 +1128,6 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
        case LFUN_MARK_ON:
                cur.clearSelection();
                cur.mark() = true;
-               update();
                cur.resetAnchor();
                cur.message(N_("Mark on"));
                break;
@@ -1129,32 +1142,115 @@ bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
                        cur.message(N_("Mark set"));
                }
                cur.resetAnchor();
-               update();
                break;
 
        case LFUN_CENTER:
                bv_->center();
                break;
 
-       case LFUN_BEGINNINGBUFSEL:
-               bv_->cursor().reset(bv_->buffer()->inset());
-               if (!cur.selection())
-                       cur.resetAnchor();
-               bv_->text()->cursorTop(cur);
-               finishUndo();
-               break;
+       case LFUN_WORDS_COUNT: {
+               DocIterator from, to;
+               if (cur.selection()) {
+                       from = cur.selectionBegin();
+                       to = cur.selectionEnd();
+               } else {
+                       from = doc_iterator_begin(bv_->buffer()->inset());
+                       to = doc_iterator_end(bv_->buffer()->inset());
+               }
+               int const count = countWords(from, to);
+               string message;
+               if (count != 1) {
+                       if (cur.selection())
+                               message = bformat(_("%1$d words in selection."),
+                                         count);
+                               else
+                                       message = bformat(_("%1$d words in document."),
+                                                         count);
+               }
+               else {
+                       if (cur.selection())
+                               message = _("One word in selection.");
+                       else
+                               message = _("One word in document.");
+               }
 
-       case LFUN_ENDBUFSEL:
-               bv_->cursor().reset(bv_->buffer()->inset());
-               if (!cur.selection())
-                       cur.resetAnchor();
-               bv_->text()->cursorBottom(cur);
-               finishUndo();
+               Alert::information(_("Count words"), message);
+       }
                break;
-
        default:
                return false;
        }
 
        return true;
 }
+
+
+ViewMetricsInfo BufferView::Pimpl::metrics()
+{
+       // remove old position cache
+       theCoords.clear();
+       BufferView & bv = *bv_;
+       LyXText * const text = bv.text();
+       if (anchor_ref_ > int(text->paragraphs().size() - 1)) {
+               anchor_ref_ = int(text->paragraphs().size() - 1);
+               offset_ref_ = 0;
+       }
+
+       lyx::pit_type const pit = anchor_ref_;
+       int pit1 = pit;
+       int pit2 = pit;
+       size_t npit = text->paragraphs().size();
+       lyxerr << "npit: " << npit << " pit1: " << pit1
+               << " pit2: " << pit2 << endl;
+
+       // rebreak anchor par
+       text->redoParagraph(pit);
+       int y0 = text->getPar(pit1).ascent() - offset_ref_;
+
+       // redo paragraphs above cursor if necessary
+       int y1 = y0;
+       while (y1 > 0 && pit1 > 0) {
+               y1 -= text->getPar(pit1).ascent();
+               --pit1;
+               text->redoParagraph(pit1);
+               y1 -= text->getPar(pit1).descent();
+       }
+
+
+       // take care of ascent of first line
+       y1 -= text->getPar(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;
+       }
+
+       // redo paragraphs below cursor if necessary
+       int y2 = y0;
+       while (y2 < bv.workHeight() && pit2 < int(npit) - 1) {
+               y2 += text->getPar(pit2).descent();
+               ++pit2;
+               text->redoParagraph(pit2);
+               y2 += text->getPar(pit2).ascent();
+       }
+
+       // take care of descent of last line
+       y2 += text->getPar(pit2).descent();
+
+       // the coordinates of all these paragraphs are correct, cache them
+       int y = y1;
+       for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) {
+               y += text->getPar(pit).ascent();
+               theCoords.pars_[text][pit] = Point(0, y);
+               y += text->getPar(pit).descent();
+       }
+
+       lyxerr << "bv:metrics:  y1: " << y1 << " y2: " << y2 << endl;
+       return ViewMetricsInfo(pit1, pit2, y1, y2);
+}