]> git.lyx.org Git - lyx.git/blob - src/BufferView.C
Fix loop when opening TOC widget in an empty document, basically by Richard Heck.
[lyx.git] / src / BufferView.C
1 /**
2  * \file BufferView.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alfredo Braunstein
7  * \author Lars Gullik Bjønnes
8  * \author John Levon
9  * \author André Pönitz
10  * \author Jürgen Vigna
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "BufferView.h"
18
19 #include "buffer.h"
20 #include "buffer_funcs.h"
21 #include "bufferlist.h"
22 #include "bufferparams.h"
23 #include "bufferview_funcs.h"
24 #include "coordcache.h"
25 #include "CutAndPaste.h"
26 #include "debug.h"
27 #include "dispatchresult.h"
28 #include "errorlist.h"
29 #include "factory.h"
30 #include "FloatList.h"
31 #include "funcrequest.h"
32 #include "FuncStatus.h"
33 #include "gettext.h"
34 #include "intl.h"
35 #include "insetiterator.h"
36 #include "language.h"
37 #include "LaTeXFeatures.h"
38 #include "lyx_cb.h" // added for Dispatch functions
39 #include "lyx_main.h"
40 #include "lyxfind.h"
41 #include "lyxfunc.h"
42 #include "lyxlayout.h"
43 #include "lyxtext.h"
44 #include "lyxtextclass.h"
45 #include "lyxrc.h"
46 #include "session.h"
47 #include "paragraph.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "pariterator.h"
51 #include "texrow.h"
52 #include "toc.h"
53 #include "undo.h"
54 #include "vspace.h"
55 #include "WordLangTuple.h"
56 #include "metricsinfo.h"
57
58 #include "insets/insetbibtex.h"
59 #include "insets/insetcommand.h" // ChangeRefs
60 #include "insets/insetref.h"
61 #include "insets/insettext.h"
62
63 #include "frontends/Alert.h"
64 #include "frontends/FileDialog.h"
65 #include "frontends/FontMetrics.h"
66
67 #include "graphics/Previews.h"
68
69 #include "support/convert.h"
70 #include "support/filefilterlist.h"
71 #include "support/filetools.h"
72 #include "support/package.h"
73 #include "support/types.h"
74
75 #include <boost/bind.hpp>
76 #include <boost/current_function.hpp>
77
78 #include <functional>
79 #include <vector>
80
81
82 namespace lyx {
83
84 using support::addPath;
85 using support::bformat;
86 using support::FileFilterList;
87 using support::FileName;
88 using support::fileSearch;
89 using support::isDirWriteable;
90 using support::isFileReadable;
91 using support::makeDisplayPath;
92 using support::package;
93
94 using std::distance;
95 using std::endl;
96 using std::istringstream;
97 using std::make_pair;
98 using std::min;
99 using std::max;
100 using std::mem_fun_ref;
101 using std::string;
102 using std::vector;
103
104 namespace Alert = frontend::Alert;
105
106 namespace {
107
108 /// Return an inset of this class if it exists at the current cursor position
109 template <class T>
110 T * getInsetByCode(LCursor & cur, InsetBase::Code code)
111 {
112         T * inset = 0;
113         DocIterator it = cur;
114         if (it.nextInset() &&
115             it.nextInset()->lyxCode() == code) {
116                 inset = static_cast<T*>(it.nextInset());
117         }
118         return inset;
119 }
120
121 } // anon namespace
122
123
124 BufferView::BufferView()
125         : width_(0), height_(0), buffer_(0), wh_(0),
126           cursor_(*this),
127           multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0),
128           intl_(new Intl), last_inset_(0)
129 {
130         xsel_cache_.set = false;
131         intl_->initKeyMapper(lyxrc.use_kbmap);
132 }
133
134
135 BufferView::~BufferView()
136 {
137 }
138
139
140 Buffer * BufferView::buffer() const
141 {
142         return buffer_;
143 }
144
145
146 void BufferView::setBuffer(Buffer * b)
147 {
148         LYXERR(Debug::INFO) << BOOST_CURRENT_FUNCTION
149                             << "[ b = " << b << "]" << endl;
150
151         if (buffer_) {
152                 // Save the actual cursor position and anchor inside the
153                 // buffer so that it can be restored in case we rechange
154                 // to this buffer later on.
155                 buffer_->saveCursor(cursor_.selectionBegin(),
156                                     cursor_.selectionEnd());
157                 // update bookmark pit of the current buffer before switch
158                 for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i) {
159                         BookmarksSection::Bookmark const & bm = LyX::ref().session().bookmarks().bookmark(i);
160                         if (buffer()->fileName() != bm.filename.absFilename())
161                                 continue;
162                         // if top_id or bottom_pit, bottom_pos has been changed, update bookmark
163                         // see http://bugzilla.lyx.org/show_bug.cgi?id=3092
164                         pit_type new_pit;
165                         pos_type new_pos;
166                         int new_id;
167                         boost::tie(new_pit, new_pos, new_id) = moveToPosition(bm.bottom_pit, bm.bottom_pos, bm.top_id, bm.top_pos);
168                         if (bm.bottom_pit != new_pit || bm.bottom_pos != new_pos || bm.top_id != new_id )
169                                 const_cast<BookmarksSection::Bookmark &>(bm).updatePos(new_pit, new_pos, new_id);
170                 }
171                 // current buffer is going to be switched-off, save cursor pos
172                 // Ideally, the whole cursor stack should be saved, but session
173                 // currently can only handle bottom (whole document) level pit and pos.
174                 // That is to say, if a cursor is in a nested inset, it will be
175                 // restore to the left of the top level inset.
176                 LyX::ref().session().lastFilePos().save(FileName(buffer_->fileName()),
177                         boost::tie(cursor_.bottom().pit(), cursor_.bottom().pos()) );
178         }
179
180         // If we're quitting lyx, don't bother updating stuff
181         if (quitting) {
182                 buffer_ = 0;
183                 return;
184         }
185
186         // If we are closing current buffer, switch to the first in
187         // buffer list.
188         if (!b) {
189                 LYXERR(Debug::INFO) << BOOST_CURRENT_FUNCTION
190                                     << " No Buffer!" << endl;
191                 // We are closing the buffer, use the first buffer as current
192                 buffer_ = theBufferList().first();
193         } else {
194                 // Set current buffer
195                 buffer_ = b;
196         }
197
198         // Reset old cursor
199         cursor_ = LCursor(*this);
200         anchor_ref_ = 0;
201         offset_ref_ = 0;
202
203         if (buffer_) {
204                 LYXERR(Debug::INFO) << BOOST_CURRENT_FUNCTION
205                                     << "Buffer addr: " << buffer_ << endl;
206                 cursor_.push(buffer_->inset());
207                 cursor_.resetAnchor();
208                 buffer_->text().setCurrentFont(cursor_);
209                 if (buffer_->getCursor().size() > 0 &&
210                     buffer_->getAnchor().size() > 0)
211                 {
212                         cursor_.setCursor(buffer_->getAnchor().asDocIterator(&(buffer_->inset())));
213                         cursor_.resetAnchor();
214                         cursor_.setCursor(buffer_->getCursor().asDocIterator(&(buffer_->inset())));
215                         cursor_.setSelection();
216                         // do not set selection to the new buffer because we
217                         // only paste recent selection.
218                 }
219         }
220
221         if (buffer_)
222                 updateMetrics(false);    
223
224         if (buffer_ && graphics::Previews::status() != LyXRC::PREVIEW_OFF)
225                 graphics::Previews::get().generateBufferPreviews(*buffer_);
226 }
227
228
229 bool BufferView::loadLyXFile(FileName const & filename, bool tolastfiles)
230 {
231         // File already open?
232         if (theBufferList().exists(filename.absFilename())) {
233                 docstring const file = makeDisplayPath(filename.absFilename(), 20);
234                 docstring text = bformat(_("The document %1$s is already "
235                                                      "loaded.\n\nDo you want to revert "
236                                                      "to the saved version?"), file);
237                 int const ret = Alert::prompt(_("Revert to saved document?"),
238                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
239
240                 if (ret != 0) {
241                         setBuffer(theBufferList().getBuffer(filename.absFilename()));
242                         return true;
243                 }
244                 // FIXME: should be LFUN_REVERT
245                 if (!theBufferList().close(theBufferList().getBuffer(filename.absFilename()), false))
246                         return false;
247                 // Fall through to new load. (Asger)
248                 buffer_ = 0;
249         }
250
251         Buffer * b = 0;
252
253         if (isFileReadable(filename)) {
254                 b = theBufferList().newBuffer(filename.absFilename());
255                 if (!lyx::loadLyXFile(b, filename)) {
256                         theBufferList().release(b);
257                         return false;
258                 }
259         } else {
260                 docstring text = bformat(_("The document %1$s does not yet "
261                                                      "exist.\n\nDo you want to create "
262                                                      "a new document?"), from_utf8(filename.absFilename()));
263                 int const ret = Alert::prompt(_("Create new document?"),
264                          text, 0, 1, _("&Create"), _("Cancel"));
265
266                 if (ret == 0) {
267                         b = newFile(filename.absFilename(), string(), true);
268                         if (!b)
269                                 return false;
270                 } else
271                         return false;
272         }
273
274         setBuffer(b);
275         // Send the "errors" signal in case of parsing errors
276         b->errors("Parse");
277
278         // Update the labels and section numbering.
279         updateLabels(*buffer_);
280         // scroll to the position when the file was last closed
281         if (lyxrc.use_lastfilepos) {
282                 pit_type pit;
283                 pos_type pos;
284                 boost::tie(pit, pos) = LyX::ref().session().lastFilePos().load(filename);
285                 // if successfully move to pit (returned par_id is not zero), update metrics and reset font
286                 if (moveToPosition(pit, pos, 0, 0).get<1>()) {
287                         if (fitCursor())
288                                 updateMetrics(false);
289                         buffer_->text().setCurrentFont(cursor_);
290                 }
291         }
292
293         if (tolastfiles)
294                 LyX::ref().session().lastFiles().add(FileName(b->fileName()));
295
296         return true;
297 }
298
299
300 void BufferView::resize()
301 {
302         if (!buffer_)
303                 return;
304
305         LYXERR(Debug::DEBUG) << BOOST_CURRENT_FUNCTION << endl;
306
307         updateMetrics(false);
308         switchKeyMap();
309 }
310
311
312 bool BufferView::fitCursor()
313 {
314         if (bv_funcs::status(this, cursor_) == bv_funcs::CUR_INSIDE) {
315                 frontend::FontMetrics const & fm =
316                         theFontMetrics(cursor_.getFont());
317                 int const asc = fm.maxAscent();
318                 int const des = fm.maxDescent();
319                 Point const p = bv_funcs::getPos(*this, cursor_, cursor_.boundary());
320                 if (p.y_ - asc >= 0 && p.y_ + des < height_)
321                         return false;
322         }
323         center();
324         return true;
325 }
326
327
328 bool BufferView::multiParSel()
329 {
330         if (!cursor_.selection())
331                 return false;
332         bool ret = multiparsel_cache_;
333         multiparsel_cache_ = cursor_.selBegin().pit() != cursor_.selEnd().pit();
334         // Either this, or previous selection spans paragraphs
335         return ret || multiparsel_cache_;
336 }
337
338
339 bool BufferView::update(Update::flags flags)
340 {
341         // last_inset_ points to the last visited inset. This pointer may become
342         // invalid because of keyboard editing. Since all such operations
343         // causes screen update(), I reset last_inset_ to avoid such a problem.
344         last_inset_ = 0;
345         // This is close to a hot-path.
346         LYXERR(Debug::DEBUG)
347                 << BOOST_CURRENT_FUNCTION
348                 << "[fitcursor = " << (flags & Update::FitCursor)
349                 << ", forceupdate = " << (flags & Update::Force)
350                 << ", singlepar = " << (flags & Update::SinglePar)
351                 << "]  buffer: " << buffer_ << endl;
352
353         // Check needed to survive LyX startup
354         if (!buffer_)
355                 return false;
356
357         LYXERR(Debug::WORKAREA) << "BufferView::update" << std::endl;
358
359         // Update macro store
360         buffer_->buildMacros();
361
362         // Now do the first drawing step if needed. This consists on updating
363         // the CoordCache in updateMetrics().
364         // The second drawing step is done in WorkArea::redraw() if needed.
365
366         // Case when no explicit update is requested.
367         if (!flags) {
368                 // no need to redraw anything.
369                 metrics_info_.update_strategy = NoScreenUpdate;
370                 return false;
371         }
372
373         if (flags == Update::Decoration) {
374                 metrics_info_.update_strategy = DecorationUpdate;
375                 return true;
376         }
377
378         if (flags == Update::FitCursor 
379                 || flags == (Update::Decoration | Update::FitCursor)) {
380                 bool const fit_cursor = fitCursor();
381                 // tell the frontend to update the screen if needed.
382                 if (fit_cursor) {
383                         updateMetrics(false);
384                         return true;
385                 }
386                 if (flags & Update::Decoration) {
387                         metrics_info_.update_strategy = DecorationUpdate;
388                         return true;
389                 }
390                 // no screen update is needed.
391                 metrics_info_.update_strategy = NoScreenUpdate;
392                 return false;
393         }
394
395         bool full_metrics = flags & Update::Force;
396         if (flags & Update::MultiParSel)
397                 full_metrics |= multiParSel();
398
399         bool const single_par = !full_metrics;
400         updateMetrics(single_par);
401
402         if (flags & Update::FitCursor && fitCursor())
403                 updateMetrics(false);
404
405         // tell the frontend to update the screen.
406         return true;
407 }
408
409
410 void BufferView::updateScrollbar()
411 {
412         if (!buffer_) {
413                 LYXERR(Debug::DEBUG) << BOOST_CURRENT_FUNCTION
414                                      << " no text in updateScrollbar" << endl;
415                 scrollbarParameters_.reset();
416                 return;
417         }
418
419         LyXText & t = buffer_->text();
420         TextMetrics & tm = text_metrics_[&t];
421
422         int const parsize = int(t.paragraphs().size() - 1);
423         if (anchor_ref_ >  parsize)  {
424                 anchor_ref_ = parsize;
425                 offset_ref_ = 0;
426         }
427
428         LYXERR(Debug::GUI)
429                 << BOOST_CURRENT_FUNCTION
430                 << " Updating scrollbar: height: " << t.paragraphs().size()
431                 << " curr par: " << cursor_.bottom().pit()
432                 << " default height " << defaultRowHeight() << endl;
433
434         // It would be better to fix the scrollbar to understand
435         // values in [0..1] and divide everything by wh
436
437         // estimated average paragraph height:
438         if (wh_ == 0)
439                 wh_ = height_ / 4;
440
441         int h = tm.parMetrics(anchor_ref_).height();
442
443         // Normalize anchor/offset (MV):
444         while (offset_ref_ > h && anchor_ref_ < parsize) {
445                 anchor_ref_++;
446                 offset_ref_ -= h;
447                 h = tm.parMetrics(anchor_ref_).height();
448         }
449         // Look at paragraph heights on-screen
450         int sumh = 0;
451         int nh = 0;
452         for (pit_type pit = anchor_ref_; pit <= parsize; ++pit) {
453                 if (sumh > height_)
454                         break;
455                 int const h2 = tm.parMetrics(pit).height();
456                 sumh += h2;
457                 nh++;
458         }
459
460         BOOST_ASSERT(nh);
461         int const hav = sumh / nh;
462         // More realistic average paragraph height
463         if (hav > wh_)
464                 wh_ = hav;
465
466         BOOST_ASSERT(h);
467         scrollbarParameters_.height = (parsize + 1) * wh_;
468         scrollbarParameters_.position = anchor_ref_ * wh_ + int(offset_ref_ * wh_ / float(h));
469         scrollbarParameters_.lineScrollHeight = int(wh_ * defaultRowHeight() / float(h));
470 }
471
472
473 ScrollbarParameters const & BufferView::scrollbarParameters() const
474 {
475         return scrollbarParameters_;
476 }
477
478
479 void BufferView::scrollDocView(int value)
480 {
481         LYXERR(Debug::GUI) << BOOST_CURRENT_FUNCTION
482                            << "[ value = " << value << "]" << endl;
483
484         if (!buffer_)
485                 return;
486
487         LyXText & t = buffer_->text();
488         TextMetrics & tm = text_metrics_[&t];
489
490         float const bar = value / float(wh_ * t.paragraphs().size());
491
492         anchor_ref_ = int(bar * t.paragraphs().size());
493         if (anchor_ref_ >  int(t.paragraphs().size()) - 1)
494                 anchor_ref_ = int(t.paragraphs().size()) - 1;
495
496         tm.redoParagraph(anchor_ref_);
497         int const h = tm.parMetrics(anchor_ref_).height();
498         offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
499         updateMetrics(false);
500 }
501
502
503 void BufferView::setCursorFromScrollbar()
504 {
505         if (!buffer_)
506                 return;
507
508         LyXText & t = buffer_->text();
509
510         int const height = 2 * defaultRowHeight();
511         int const first = height;
512         int const last = height_ - height;
513         LCursor & cur = cursor_;
514
515         bv_funcs::CurStatus st = bv_funcs::status(this, cur);
516
517         switch (st) {
518         case bv_funcs::CUR_ABOVE:
519                 // We reset the cursor because bv_funcs::status() does not
520                 // work when the cursor is within mathed.
521                 cur.reset(buffer_->inset());
522                 t.setCursorFromCoordinates(cur, 0, first);
523                 cur.clearSelection();
524                 break;
525         case bv_funcs::CUR_BELOW:
526                 // We reset the cursor because bv_funcs::status() does not
527                 // work when the cursor is within mathed.
528                 cur.reset(buffer_->inset());
529                 t.setCursorFromCoordinates(cur, 0, last);
530                 cur.clearSelection();
531                 break;
532         case bv_funcs::CUR_INSIDE:
533                 int const y = bv_funcs::getPos(*this, cur, cur.boundary()).y_;
534                 int const newy = min(last, max(y, first));
535                 if (y != newy) {
536                         cur.reset(buffer_->inset());
537                         t.setCursorFromCoordinates(cur, 0, newy);
538                 }
539         }
540 }
541
542
543 Change const BufferView::getCurrentChange() const
544 {
545         if (!cursor_.selection())
546                 return Change(Change::UNCHANGED);
547
548         DocIterator dit = cursor_.selectionBegin();
549         return dit.paragraph().lookupChange(dit.pos());
550 }
551
552
553 void BufferView::saveBookmark(unsigned int idx)
554 {
555         // tenatively save bookmark, id and pos will be used to
556         // acturately locate a bookmark in a 'live' lyx session.
557         // pit and pos will be updated with bottom level pit/pos 
558         // when lyx exits.
559         LyX::ref().session().bookmarks().save(
560                 FileName(buffer_->fileName()),
561                 cursor_.bottom().pit(),
562                 cursor_.bottom().pos(),
563                 cursor_.paragraph().id(),
564                 cursor_.pos(),
565                 idx
566         );
567         if (idx)
568                 // emit message signal.
569                 message(_("Save bookmark"));
570 }
571
572
573 boost::tuple<pit_type, pos_type, int> BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
574         int top_id, pos_type top_pos)
575 {
576         cursor_.clearSelection();
577
578         // if a valid par_id is given, try it first
579         // This is the case for a 'live' bookmark when unique paragraph ID
580         // is used to track bookmarks.
581         if (top_id > 0) {
582                 ParIterator par = buffer_->getParFromID(top_id);
583                 if (par != buffer_->par_iterator_end()) {
584                         DocIterator dit = makeDocIterator(par, min(par->size(), top_pos));
585                         // Some slices of the iterator may not be reachable (e.g. closed collapsable inset)
586                         // so the dociterator may need to be shortened. Otherwise, setCursor may
587                         // crash lyx when the cursor can not be set to these insets.
588                         size_t const n = dit.depth();
589                         for (size_t i = 0; i < n; ++i)
590                                 if (dit[i].inset().editable() != InsetBase::HIGHLY_EDITABLE) {
591                                         dit.resize(i);
592                                         break;
593                                 }
594                         setCursor(dit);
595                         // Note: return bottom (document) level pit.
596                         return boost::make_tuple(cursor_.bottom().pit(), cursor_.bottom().pos(), top_id);
597                 }
598         }
599         // if top_id == 0, or searching through top_id failed
600         // This is the case for a 'restored' bookmark when only bottom 
601         // (document level) pit was saved. Because of this, bookmark
602         // restoration is inaccurate. If a bookmark was within an inset,
603         // it will be restored to the left of the outmost inset that contains
604         // the bookmark.
605         if (static_cast<size_t>(bottom_pit) < buffer_->paragraphs().size()) {
606                 ParIterator it = buffer_->par_iterator_begin();
607                 ParIterator const end = buffer_->par_iterator_end();
608                 for (; it != end; ++it)
609                         if (it.pit() == bottom_pit) {
610                                 // restored pos may be bigger than it->size
611                                 setCursor(makeDocIterator(it, min(bottom_pos, it->size())));
612                                 return boost::make_tuple(bottom_pit, bottom_pos, it->id());
613                         }
614         }
615         // both methods fail
616         return boost::make_tuple(pit_type(0), pos_type(0), 0);
617 }
618
619
620 void BufferView::switchKeyMap()
621 {
622         if (!lyxrc.rtl_support)
623                 return;
624
625         if (cursor_.innerText()->real_current_font.isRightToLeft()) {
626                 if (intl_->keymap == Intl::PRIMARY)
627                         intl_->keyMapSec();
628         } else {
629                 if (intl_->keymap == Intl::SECONDARY)
630                         intl_->keyMapPrim();
631         }
632 }
633
634
635 int BufferView::workWidth() const
636 {
637         return width_;
638 }
639
640
641 void BufferView::center()
642 {
643         CursorSlice & bot = cursor_.bottom();
644         TextMetrics & tm = text_metrics_[bot.text()];
645         pit_type const pit = bot.pit();
646         tm.redoParagraph(pit);
647         ParagraphMetrics const & pm = tm.parMetrics(pit);
648         anchor_ref_ = pit;
649         offset_ref_ = bv_funcs::coordOffset(*this, cursor_, cursor_.boundary()).y_
650                 + pm.ascent() - height_ / 2;
651 }
652
653
654 FuncStatus BufferView::getStatus(FuncRequest const & cmd)
655 {
656         FuncStatus flag;
657
658         switch (cmd.action) {
659
660         case LFUN_UNDO:
661                 flag.enabled(!buffer_->undostack().empty());
662                 break;
663         case LFUN_REDO:
664                 flag.enabled(!buffer_->redostack().empty());
665                 break;
666         case LFUN_FILE_INSERT:
667         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
668         case LFUN_FILE_INSERT_PLAINTEXT:
669         case LFUN_BOOKMARK_SAVE:
670                 // FIXME: Actually, these LFUNS should be moved to LyXText
671                 flag.enabled(cursor_.inTexted());
672                 break;
673         case LFUN_FONT_STATE:
674         case LFUN_LABEL_INSERT:
675         case LFUN_PARAGRAPH_GOTO:
676         // FIXME handle non-trivially
677         case LFUN_OUTLINE_UP:
678         case LFUN_OUTLINE_DOWN:
679         case LFUN_OUTLINE_IN:
680         case LFUN_OUTLINE_OUT:
681         case LFUN_NOTE_NEXT:
682         case LFUN_REFERENCE_NEXT:
683         case LFUN_WORD_FIND:
684         case LFUN_WORD_REPLACE:
685         case LFUN_MARK_OFF:
686         case LFUN_MARK_ON:
687         case LFUN_MARK_TOGGLE:
688         case LFUN_SCREEN_RECENTER:
689         case LFUN_BIBTEX_DATABASE_ADD:
690         case LFUN_BIBTEX_DATABASE_DEL:
691         case LFUN_WORDS_COUNT:
692         case LFUN_NEXT_INSET_TOGGLE:
693                 flag.enabled(true);
694                 break;
695
696         case LFUN_LABEL_GOTO: {
697                 flag.enabled(!cmd.argument().empty()
698                     || getInsetByCode<InsetRef>(cursor_, InsetBase::REF_CODE));
699                 break;
700         }
701
702         case LFUN_CHANGES_TRACK:
703                 flag.enabled(true);
704                 flag.setOnOff(buffer_->params().trackChanges);
705                 break;
706
707         case LFUN_CHANGES_OUTPUT:
708                 flag.enabled(buffer_ && LaTeXFeatures::isAvailable("dvipost"));
709                 flag.setOnOff(buffer_->params().outputChanges);
710                 break;
711
712         case LFUN_CHANGES_MERGE:
713         case LFUN_CHANGE_NEXT:
714         case LFUN_ALL_CHANGES_ACCEPT:
715         case LFUN_ALL_CHANGES_REJECT:
716                 // TODO: context-sensitive enabling of LFUNs
717                 // In principle, these command should only be enabled if there
718                 // is a change in the document. However, without proper
719                 // optimizations, this will inevitably result in poor performance.
720                 flag.enabled(buffer_);
721                 break;
722
723         case LFUN_BUFFER_TOGGLE_COMPRESSION: {
724                 flag.setOnOff(buffer_->params().compressed);
725                 break;
726         }
727
728         default:
729                 flag.enabled(false);
730         }
731
732         return flag;
733 }
734
735
736 Update::flags BufferView::dispatch(FuncRequest const & cmd)
737 {
738         //lyxerr << BOOST_CURRENT_FUNCTION
739         //       << [ cmd = " << cmd << "]" << endl;
740
741         // Make sure that the cached BufferView is correct.
742         LYXERR(Debug::ACTION) << BOOST_CURRENT_FUNCTION
743                 << " action[" << cmd.action << ']'
744                 << " arg[" << to_utf8(cmd.argument()) << ']'
745                 << " x[" << cmd.x << ']'
746                 << " y[" << cmd.y << ']'
747                 << " button[" << cmd.button() << ']'
748                 << endl;
749
750         // FIXME: this should not be possible.
751         if (!buffer_)
752                 return Update::None;
753
754         LCursor & cur = cursor_;
755         // Default Update flags.
756         Update::flags updateFlags = Update::Force | Update::FitCursor;
757
758         switch (cmd.action) {
759
760         case LFUN_UNDO:
761                 cur.message(_("Undo"));
762                 cur.clearSelection();
763                 if (!textUndo(*this)) {
764                         cur.message(_("No further undo information"));
765                         updateFlags = Update::None;
766                 }
767                 switchKeyMap();
768                 break;
769
770         case LFUN_REDO:
771                 cur.message(_("Redo"));
772                 cur.clearSelection();
773                 if (!textRedo(*this)) {
774                         cur.message(_("No further redo information"));
775                         updateFlags = Update::None;
776                 }
777                 switchKeyMap();
778                 break;
779
780         case LFUN_FILE_INSERT:
781                 // FIXME UNICODE
782                 menuInsertLyXFile(to_utf8(cmd.argument()));
783                 break;
784
785         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
786                 // FIXME UNICODE
787                 insertPlaintextFile(this, to_utf8(cmd.argument()), true);
788                 break;
789
790         case LFUN_FILE_INSERT_PLAINTEXT:
791                 // FIXME UNICODE
792                 insertPlaintextFile(this, to_utf8(cmd.argument()), false);
793                 break;
794
795         case LFUN_FONT_STATE:
796                 cur.message(cur.currentState());
797                 break;
798
799         case LFUN_BOOKMARK_SAVE:
800                 saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
801                 break;
802
803         case LFUN_LABEL_GOTO: {
804                 docstring label = cmd.argument();
805                 if (label.empty()) {
806                         InsetRef * inset =
807                                 getInsetByCode<InsetRef>(cursor_,
808                                                          InsetBase::REF_CODE);
809                         if (inset) {
810                                 label = inset->getParam("reference");
811                                 // persistent=false: use temp_bookmark
812                                 saveBookmark(0);
813                         }
814                 }
815
816                 if (!label.empty())
817                         gotoLabel(label);
818                 break;
819         }
820
821         case LFUN_PARAGRAPH_GOTO: {
822                 int const id = convert<int>(to_utf8(cmd.argument()));
823                 int i = 0;
824                 for (Buffer * b = buffer_; i == 0 || b != buffer_; b = theBufferList().next(b)) {
825                         ParIterator par = b->getParFromID(id);
826                         if (par == b->par_iterator_end()) {
827                                 LYXERR(Debug::INFO)
828                                         << "No matching paragraph found! ["
829                                         << id << "]." << endl;
830                         } else {
831                                 LYXERR(Debug::INFO)
832                                         << "Paragraph " << par->id()
833                                         << " found in buffer `"
834                                         << b->fileName() << "'." << endl;
835
836                                 if (b == buffer_) {
837                                         // Set the cursor
838                                         setCursor(makeDocIterator(par, 0));
839                                         switchKeyMap();
840                                 } else {
841                                         // Switch to other buffer view and resend cmd
842                                         theLyXFunc().dispatch(FuncRequest(
843                                                 LFUN_BUFFER_SWITCH, b->fileName()));
844                                         theLyXFunc().dispatch(cmd);
845                                         updateFlags = Update::None;
846                                 }
847                                 break;
848                         }
849                         ++i;
850                 }
851                 break;
852         }
853
854         case LFUN_OUTLINE_UP:
855                 toc::outline(toc::Up, cursor_);
856                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
857                 updateLabels(*buffer_);
858                 break;
859         case LFUN_OUTLINE_DOWN:
860                 toc::outline(toc::Down, cursor_);
861                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
862                 updateLabels(*buffer_);
863                 break;
864         case LFUN_OUTLINE_IN:
865                 toc::outline(toc::In, cursor_);
866                 updateLabels(*buffer_);
867                 break;
868         case LFUN_OUTLINE_OUT:
869                 toc::outline(toc::Out, cursor_);
870                 updateLabels(*buffer_);
871                 break;
872
873         case LFUN_NOTE_NEXT:
874                 bv_funcs::gotoInset(this, InsetBase::NOTE_CODE, false);
875                 break;
876
877         case LFUN_REFERENCE_NEXT: {
878                 vector<InsetBase_code> tmp;
879                 tmp.push_back(InsetBase::LABEL_CODE);
880                 tmp.push_back(InsetBase::REF_CODE);
881                 bv_funcs::gotoInset(this, tmp, true);
882                 break;
883         }
884
885         case LFUN_CHANGES_TRACK:
886                 buffer_->params().trackChanges = !buffer_->params().trackChanges;
887                 break;
888
889         case LFUN_CHANGES_OUTPUT:
890                 buffer_->params().outputChanges = !buffer_->params().outputChanges;
891                 break;
892
893         case LFUN_CHANGE_NEXT:
894                 findNextChange(this);
895                 break;
896
897         case LFUN_CHANGES_MERGE:
898                 if (findNextChange(this))
899                         showDialog("changes");
900                 break;
901
902         case LFUN_ALL_CHANGES_ACCEPT:
903                 // select complete document
904                 cursor_.reset(buffer_->inset());
905                 cursor_.selHandle(true);
906                 buffer_->text().cursorBottom(cursor_);
907                 // accept everything in a single step to support atomic undo
908                 buffer_->text().acceptOrRejectChanges(cursor_, LyXText::ACCEPT);
909                 break;
910
911         case LFUN_ALL_CHANGES_REJECT:
912                 // select complete document
913                 cursor_.reset(buffer_->inset());
914                 cursor_.selHandle(true);
915                 buffer_->text().cursorBottom(cursor_);
916                 // reject everything in a single step to support atomic undo
917                 // Note: reject does not work recursively; the user may have to repeat the operation
918                 buffer_->text().acceptOrRejectChanges(cursor_, LyXText::REJECT);
919                 break;
920
921         case LFUN_WORD_FIND:
922                 find(this, cmd);
923                 break;
924
925         case LFUN_WORD_REPLACE:
926                 replace(this, cmd);
927                 break;
928
929         case LFUN_MARK_OFF:
930                 cur.clearSelection();
931                 cur.resetAnchor();
932                 cur.message(from_utf8(N_("Mark off")));
933                 break;
934
935         case LFUN_MARK_ON:
936                 cur.clearSelection();
937                 cur.mark() = true;
938                 cur.resetAnchor();
939                 cur.message(from_utf8(N_("Mark on")));
940                 break;
941
942         case LFUN_MARK_TOGGLE:
943                 cur.clearSelection();
944                 if (cur.mark()) {
945                         cur.mark() = false;
946                         cur.message(from_utf8(N_("Mark removed")));
947                 } else {
948                         cur.mark() = true;
949                         cur.message(from_utf8(N_("Mark set")));
950                 }
951                 cur.resetAnchor();
952                 break;
953
954         case LFUN_SCREEN_RECENTER:
955                 center();
956                 break;
957
958         case LFUN_BIBTEX_DATABASE_ADD: {
959                 LCursor tmpcur = cursor_;
960                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
961                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
962                                                 InsetBase::BIBTEX_CODE);
963                 if (inset) {
964                         if (inset->addDatabase(to_utf8(cmd.argument())))
965                                 buffer_->updateBibfilesCache();
966                 }
967                 break;
968         }
969
970         case LFUN_BIBTEX_DATABASE_DEL: {
971                 LCursor tmpcur = cursor_;
972                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
973                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
974                                                 InsetBase::BIBTEX_CODE);
975                 if (inset) {
976                         if (inset->delDatabase(to_utf8(cmd.argument())))
977                                 buffer_->updateBibfilesCache();
978                 }
979                 break;
980         }
981
982         case LFUN_WORDS_COUNT: {
983                 DocIterator from, to;
984                 if (cur.selection()) {
985                         from = cur.selectionBegin();
986                         to = cur.selectionEnd();
987                 } else {
988                         from = doc_iterator_begin(buffer_->inset());
989                         to = doc_iterator_end(buffer_->inset());
990                 }
991                 int const count = countWords(from, to);
992                 docstring message;
993                 if (count != 1) {
994                         if (cur.selection())
995                                 message = bformat(_("%1$d words in selection."),
996                                           count);
997                                 else
998                                         message = bformat(_("%1$d words in document."),
999                                                           count);
1000                 }
1001                 else {
1002                         if (cur.selection())
1003                                 message = _("One word in selection.");
1004                         else
1005                                 message = _("One word in document.");
1006                 }
1007
1008                 Alert::information(_("Count words"), message);
1009         }
1010                 break;
1011
1012         case LFUN_BUFFER_TOGGLE_COMPRESSION:
1013                 // turn compression on/off
1014                 buffer_->params().compressed = !buffer_->params().compressed;
1015                 break;
1016
1017         case LFUN_NEXT_INSET_TOGGLE: {
1018                 // this is the real function we want to invoke
1019                 FuncRequest tmpcmd = FuncRequest(LFUN_INSET_TOGGLE, cmd.origin);
1020                 // if there is an inset at cursor, see whether it
1021                 // wants to toggle.
1022                 InsetBase * inset = cur.nextInset();
1023                 if (inset && inset->isActive()) {
1024                         LCursor tmpcur = cur;
1025                         tmpcur.pushLeft(*inset);
1026                         inset->dispatch(tmpcur, tmpcmd);
1027                         if (tmpcur.result().dispatched()) {
1028                                 cur.dispatched();
1029                         }
1030                 }
1031                 // if it did not work, try the underlying inset.
1032                 if (!cur.result().dispatched())
1033                         cur.dispatch(tmpcmd);
1034
1035                 if (cur.result().dispatched())
1036                         cur.clearSelection();
1037
1038                 break;
1039         }
1040
1041         default:
1042                 updateFlags = Update::None;
1043         }
1044
1045         return updateFlags;
1046 }
1047
1048
1049 docstring const BufferView::requestSelection()
1050 {
1051         if (!buffer_)
1052                 return docstring();
1053
1054         LCursor & cur = cursor_;
1055
1056         if (!cur.selection()) {
1057                 xsel_cache_.set = false;
1058                 return docstring();
1059         }
1060
1061         if (!xsel_cache_.set ||
1062             cur.top() != xsel_cache_.cursor ||
1063             cur.anchor_.top() != xsel_cache_.anchor)
1064         {
1065                 xsel_cache_.cursor = cur.top();
1066                 xsel_cache_.anchor = cur.anchor_.top();
1067                 xsel_cache_.set = cur.selection();
1068                 return cur.selectionAsString(false);
1069         }
1070         return docstring();
1071 }
1072
1073
1074 void BufferView::clearSelection()
1075 {
1076         if (buffer_) {
1077                 cursor_.clearSelection();
1078                 // Clear the selection buffer. Otherwise a subsequent
1079                 // middle-mouse-button paste would use the selection buffer,
1080                 // not the more current external selection.
1081                 cap::clearSelection();
1082                 xsel_cache_.set = false;
1083                 // The buffer did not really change, but this causes the
1084                 // redraw we need because we cleared the selection above.
1085                 buffer_->changed();
1086         }
1087 }
1088
1089
1090 void BufferView::workAreaResize(int width, int height)
1091 {
1092         // Update from work area
1093         width_ = width;
1094         height_ = height;
1095
1096         // The complete text metrics will be redone.
1097         text_metrics_.clear();
1098
1099         if (buffer_)
1100                 resize();
1101 }
1102
1103
1104 InsetBase const * BufferView::getCoveringInset(LyXText const & text, int x, int y)
1105 {
1106         pit_type pit = text.getPitNearY(*this, y);
1107         BOOST_ASSERT(pit != -1);
1108         Paragraph const & par = text.getPar(pit);
1109
1110         LYXERR(Debug::DEBUG)
1111                 << BOOST_CURRENT_FUNCTION
1112                 << ": x: " << x
1113                 << " y: " << y
1114                 << "  pit: " << pit
1115                 << endl;
1116         InsetList::const_iterator iit = par.insetlist.begin();
1117         InsetList::const_iterator iend = par.insetlist.end();
1118         for (; iit != iend; ++iit) {
1119                 InsetBase * const inset = iit->inset;
1120                 if (inset->covers(*this, x, y)) {
1121                         if (!inset->descendable())
1122                                 // No need to go further down if the inset is not 
1123                                 // descendable.
1124                                 return inset;
1125
1126                         size_t cell_number = inset->nargs();
1127                         // Check all the inner cell.
1128                         for (size_t i = 0; i != cell_number; ++i) {
1129                                 LyXText const * inner_text = inset->getText(i);
1130                                 if (inner_text) {
1131                                         // Try deeper.
1132                                         InsetBase const * inset_deeper = 
1133                                                 getCoveringInset(*inner_text, x, y);
1134                                         if (inset_deeper)
1135                                                 return inset_deeper;
1136                                 }
1137                         }
1138
1139                         LYXERR(Debug::DEBUG)
1140                                 << BOOST_CURRENT_FUNCTION
1141                                 << ": Hit inset: " << inset << endl;
1142                         return inset;
1143                 }
1144         }
1145         LYXERR(Debug::DEBUG)
1146                 << BOOST_CURRENT_FUNCTION
1147                 << ": No inset hit. " << endl;
1148         return 0;
1149 }
1150
1151
1152 bool BufferView::workAreaDispatch(FuncRequest const & cmd0)
1153 {
1154         //lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl;
1155
1156         // This is only called for mouse related events including
1157         // LFUN_FILE_OPEN generated by drag-and-drop.
1158         FuncRequest cmd = cmd0;
1159
1160         // E.g. Qt mouse press when no buffer
1161         if (!buffer_)
1162                 return false;
1163
1164         LCursor cur(*this);
1165         cur.push(buffer_->inset());
1166         cur.selection() = cursor_.selection();
1167
1168         // Either the inset under the cursor or the
1169         // surrounding LyXText will handle this event.
1170
1171         // make sure we stay within the screen...
1172         cmd.y = min(max(cmd.y, -1), height_);
1173         
1174         if (cmd.action == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
1175                 
1176                 // Get inset under mouse, if there is one.
1177                 InsetBase const * covering_inset = 
1178                         getCoveringInset(buffer_->text(), cmd.x, cmd.y);
1179                 if (covering_inset == last_inset_)
1180                         // Same inset, no need to do anything...
1181                         return false;
1182
1183                 bool need_redraw = false;
1184                 // const_cast because of setMouseHover().
1185                 InsetBase * inset = const_cast<InsetBase *>(covering_inset);
1186                 if (last_inset_)
1187                         // Remove the hint on the last hovered inset (if any).
1188                         need_redraw |= last_inset_->setMouseHover(false);
1189                 if (inset)
1190                         // Highlighted the newly hovered inset (if any).
1191                         need_redraw |= inset->setMouseHover(true);
1192                 last_inset_ = inset;
1193
1194                 // if last metrics update was in singlepar mode, WorkArea::redraw() will
1195                 // not expose the button for redraw. We adjust here the metrics dimension
1196                 // to enable a full redraw.
1197                 // FIXME: It is possible to redraw only the area around the button!
1198                 if (need_redraw 
1199                         && metrics_info_.update_strategy == SingleParUpdate) {
1200                         // FIXME: It should be possible to redraw only the area around 
1201                         // the button by doing this:
1202                         //
1203                         //metrics_info_.singlepar = false;
1204                         //metrics_info_.y1 = ymin of button;
1205                         //metrics_info_.y2 = ymax of button;
1206                         //
1207                         // Unfortunately, rowpainter.C:paintText() does not distinguish
1208                         // between background updates and text updates. So we use the hammer
1209                         // solution for now. We could also avoid the updateMetrics() below
1210                         // by using the first and last pit of the CoordCache. Have a look
1211                         // at LyXText::getPitNearY() to see what I mean.
1212                         //
1213                         //metrics_info_.pit1 = first pit of CoordCache;
1214                         //metrics_info_.pit2 = last pit of CoordCache;
1215                         //metrics_info_.singlepar = false;
1216                         //metrics_info_.y1 = 0;
1217                         //metrics_info_.y2 = height_;
1218                         //
1219                         updateMetrics(false);
1220                 }
1221
1222                 // This event (moving without mouse click) is not passed further.
1223                 // This should be changed if it is further utilized.
1224                 return need_redraw;
1225         }
1226         
1227         // Build temporary cursor.
1228         InsetBase * inset = buffer_->text().editXY(cur, cmd.x, cmd.y);
1229
1230         // Put anchor at the same position.
1231         cur.resetAnchor();
1232
1233         // Try to dispatch to an non-editable inset near this position
1234         // via the temp cursor. If the inset wishes to change the real
1235         // cursor it has to do so explicitly by using
1236         //  cur.bv().cursor() = cur;  (or similar)
1237         if (inset) {
1238                 inset->dispatch(cur, cmd);
1239         }
1240
1241         // Now dispatch to the temporary cursor. If the real cursor should
1242         // be modified, the inset's dispatch has to do so explicitly.
1243         if (!cur.result().dispatched())
1244                 cur.dispatch(cmd);
1245
1246         // Redraw if requested and necessary.
1247         if (cur.result().dispatched() && cur.result().update())
1248                 return update(cur.result().update());
1249
1250         return false;
1251 }
1252
1253
1254 void BufferView::scroll(int /*lines*/)
1255 {
1256 //      if (!buffer_)
1257 //              return;
1258 //
1259 //      LyXText const * t = &buffer_->text();
1260 //      int const line_height = defaultRowHeight();
1261 //
1262 //      // The new absolute coordinate
1263 //      int new_top_y = top_y() + lines * line_height;
1264 //
1265 //      // Restrict to a valid value
1266 //      new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
1267 //      new_top_y = std::max(0, new_top_y);
1268 //
1269 //      scrollDocView(new_top_y);
1270 //
1271 }
1272
1273
1274 void BufferView::setCursorFromRow(int row)
1275 {
1276         int tmpid = -1;
1277         int tmppos = -1;
1278
1279         buffer_->texrow().getIdFromRow(row, tmpid, tmppos);
1280
1281         if (tmpid == -1)
1282                 buffer_->text().setCursor(cursor_, 0, 0);
1283         else
1284                 buffer_->text().setCursor(cursor_, buffer_->getParFromID(tmpid).pit(), tmppos);
1285 }
1286
1287
1288 void BufferView::gotoLabel(docstring const & label)
1289 {
1290         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it) {
1291                 vector<docstring> labels;
1292                 it->getLabelList(*buffer_, labels);
1293                 if (std::find(labels.begin(), labels.end(), label) != labels.end()) {
1294                         setCursor(it);
1295                         update();
1296                         return;
1297                 }
1298         }
1299 }
1300
1301
1302 TextMetrics const & BufferView::textMetrics(LyXText const * t) const
1303 {
1304         return const_cast<BufferView *>(this)->textMetrics(t);
1305 }
1306
1307
1308 TextMetrics & BufferView::textMetrics(LyXText const * t)
1309 {
1310         TextMetricsCache::iterator tmc_it  = text_metrics_.find(t);
1311         if (tmc_it == text_metrics_.end()) {
1312                 tmc_it = text_metrics_.insert(
1313                         make_pair(t, TextMetrics(this, const_cast<LyXText *>(t)))).first;
1314         }       
1315         return tmc_it->second;
1316 }
1317
1318
1319 ParagraphMetrics const & BufferView::parMetrics(LyXText const * t,
1320                 pit_type pit) const
1321 {
1322         return textMetrics(t).parMetrics(pit);
1323 }
1324
1325
1326 int BufferView::workHeight() const
1327 {
1328         return height_;
1329 }
1330
1331
1332 void BufferView::setCursor(DocIterator const & dit)
1333 {
1334         size_t const n = dit.depth();
1335         for (size_t i = 0; i < n; ++i)
1336                 dit[i].inset().edit(cursor_, true);
1337
1338         cursor_.setCursor(dit);
1339         cursor_.selection() = false;
1340 }
1341
1342
1343 bool BufferView::checkDepm(LCursor & cur, LCursor & old)
1344 {
1345         // Would be wrong to delete anything if we have a selection.
1346         if (cur.selection())
1347                 return false;
1348
1349         bool need_anchor_change = false;
1350         bool changed = cursor_.text()->deleteEmptyParagraphMechanism(cur, old,
1351                 need_anchor_change);
1352
1353         if (need_anchor_change)
1354                 cur.resetAnchor();
1355         
1356         if (!changed)
1357                 return false;
1358
1359         updateLabels(*buffer_);
1360
1361         updateMetrics(false);
1362         buffer_->changed();
1363         return true;
1364 }
1365
1366
1367 bool BufferView::mouseSetCursor(LCursor & cur)
1368 {
1369         BOOST_ASSERT(&cur.bv() == this);
1370
1371         // Has the cursor just left the inset?
1372         bool badcursor = false;
1373         bool leftinset = (&cursor_.inset() != &cur.inset());
1374         if (leftinset)
1375                 badcursor = cursor_.inset().notifyCursorLeaves(cursor_);
1376
1377         // do the dEPM magic if needed
1378         // FIXME: (1) move this to InsetText::notifyCursorLeaves?
1379         // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
1380         // the leftinset bool would not be necessary (badcursor instead).
1381         bool update = leftinset;
1382         if (!badcursor && cursor_.inTexted())
1383                 update |= checkDepm(cur, cursor_);
1384
1385         // if the cursor was in an empty script inset and the new
1386         // position is in the nucleus of the inset, notifyCursorLeaves
1387         // will kill the script inset itself. So we check all the
1388         // elements of the cursor to make sure that they are correct.
1389         // For an example, see bug 2933: 
1390         // http://bugzilla.lyx.org/show_bug.cgi?id=2933
1391         // The code below could maybe be moved to a DocIterator method.
1392         //lyxerr << "cur before " << cur <<std::endl;
1393         DocIterator dit(cur.inset());
1394         dit.push_back(cur.bottom());
1395         size_t i = 1;
1396         while (i < cur.depth() && dit.nextInset() == &cur[i].inset()) {
1397                 dit.push_back(cur[i]);
1398                 ++i;
1399         }
1400         //lyxerr << "5 cur after" << dit <<std::endl;
1401
1402         cursor_.setCursor(dit);
1403         cursor_.clearSelection();
1404         finishUndo();
1405         return update;
1406 }
1407
1408
1409 void BufferView::putSelectionAt(DocIterator const & cur,
1410                                 int length, bool backwards)
1411 {
1412         cursor_.clearSelection();
1413
1414         setCursor(cur);
1415
1416         if (length) {
1417                 if (backwards) {
1418                         cursor_.pos() += length;
1419                         cursor_.setSelection(cursor_, -length);
1420                 } else
1421                         cursor_.setSelection(cursor_, length);
1422                 cap::saveSelection(cursor_);
1423         }
1424 }
1425
1426
1427 LCursor & BufferView::cursor()
1428 {
1429         return cursor_;
1430 }
1431
1432
1433 LCursor const & BufferView::cursor() const
1434 {
1435         return cursor_;
1436 }
1437
1438
1439 pit_type BufferView::anchor_ref() const
1440 {
1441         return anchor_ref_;
1442 }
1443
1444
1445 ViewMetricsInfo const & BufferView::viewMetricsInfo()
1446 {
1447         return metrics_info_;
1448 }
1449
1450
1451 // FIXME: We should split-up updateMetrics() for the singlepar case.
1452 void BufferView::updateMetrics(bool singlepar)
1453 {
1454         LyXText & buftext = buffer_->text();
1455         TextMetrics & tm = textMetrics(&buftext);
1456         pit_type size = int(buftext.paragraphs().size());
1457
1458         if (anchor_ref_ > int(buftext.paragraphs().size() - 1)) {
1459                 anchor_ref_ = int(buftext.paragraphs().size() - 1);
1460                 offset_ref_ = 0;
1461         }
1462         
1463         // If the paragraph metrics has changed, we can not
1464         // use the singlepar optimisation.
1465         if (singlepar
1466                 // In Single Paragraph mode, rebreak only
1467                 // the (main text, not inset!) paragraph containing the cursor.
1468                 // (if this paragraph contains insets etc., rebreaking will
1469                 // recursively descend)
1470                 && tm.redoParagraph(cursor_.bottom().pit()))
1471                 singlepar = false;
1472
1473         pit_type const pit = anchor_ref_;
1474         int pit1 = pit;
1475         int pit2 = pit;
1476         size_t const npit = buftext.paragraphs().size();
1477
1478         // Rebreak anchor paragraph.
1479         if (!singlepar)
1480                 tm.redoParagraph(pit);
1481         
1482         // Clear out the position cache in case of full screen redraw.
1483         if (!singlepar)
1484                 coord_cache_.clear();
1485
1486         int y0 = tm.parMetrics(pit).ascent() - offset_ref_;
1487
1488         // Redo paragraphs above anchor if necessary.
1489         int y1 = y0;
1490         while (y1 > 0 && pit1 > 0) {
1491                 y1 -= tm.parMetrics(pit1).ascent();
1492                 --pit1;
1493                 if (!singlepar)
1494                         tm.redoParagraph(pit1);
1495                 y1 -= tm.parMetrics(pit1).descent();
1496         }
1497
1498
1499         // Take care of ascent of first line
1500         y1 -= tm.parMetrics(pit1).ascent();
1501
1502         // Normalize anchor for next time
1503         anchor_ref_ = pit1;
1504         offset_ref_ = -y1;
1505
1506         // Grey at the beginning is ugly
1507         if (pit1 == 0 && y1 > 0) {
1508                 y0 -= y1;
1509                 y1 = 0;
1510                 anchor_ref_ = 0;
1511         }
1512
1513         // Redo paragraphs below the anchor if necessary.
1514         int y2 = y0;
1515         while (y2 < height_ && pit2 < int(npit) - 1) {
1516                 y2 += tm.parMetrics(pit2).descent();
1517                 ++pit2;
1518                 if (!singlepar)
1519                         tm.redoParagraph(pit2);
1520                 y2 += tm.parMetrics(pit2).ascent();
1521         }
1522
1523         // Take care of descent of last line
1524         y2 += tm.parMetrics(pit2).descent();
1525
1526         // The coordinates of all these paragraphs are correct, cache them
1527         int y = y1;
1528         CoordCache::InnerParPosCache & parPos = coord_cache_.parPos()[&buftext];
1529         for (pit_type pit = pit1; pit <= pit2; ++pit) {
1530                 ParagraphMetrics const & pm = tm.parMetrics(pit);
1531                 y += pm.ascent();
1532                 parPos[pit] = Point(0, y);
1533                 if (singlepar && pit == cursor_.bottom().pit()) {
1534                         // In Single Paragraph mode, collect here the
1535                         // y1 and y2 of the (one) paragraph the cursor is in
1536                         y1 = y - pm.ascent();
1537                         y2 = y + pm.descent();
1538                 }
1539                 y += pm.descent();
1540         }
1541
1542         if (singlepar) {
1543                 // collect cursor paragraph iter bounds
1544                 pit1 = cursor_.bottom().pit();
1545                 pit2 = cursor_.bottom().pit();
1546         }
1547
1548         LYXERR(Debug::DEBUG)
1549                 << BOOST_CURRENT_FUNCTION
1550                 << " y1: " << y1
1551                 << " y2: " << y2
1552                 << " pit1: " << pit1
1553                 << " pit2: " << pit2
1554                 << " npit: " << npit
1555                 << " singlepar: " << singlepar
1556                 << "size: " << size
1557                 << endl;
1558
1559         metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, 
1560                 singlepar? SingleParUpdate: FullScreenUpdate, size);
1561
1562         if (lyxerr.debugging(Debug::WORKAREA)) {
1563                 LYXERR(Debug::WORKAREA) << "BufferView::updateMetrics" << endl;
1564                 coord_cache_.dump();
1565         }
1566 }
1567
1568
1569 void BufferView::menuInsertLyXFile(string const & filenm)
1570 {
1571         BOOST_ASSERT(cursor_.inTexted());
1572         string filename = filenm;
1573
1574         if (filename.empty()) {
1575                 // Launch a file browser
1576                 // FIXME UNICODE
1577                 string initpath = lyxrc.document_path;
1578
1579                 if (buffer_) {
1580                         string const trypath = buffer_->filePath();
1581                         // If directory is writeable, use this as default.
1582                         if (isDirWriteable(FileName(trypath)))
1583                                 initpath = trypath;
1584                 }
1585
1586                 // FIXME UNICODE
1587                 FileDialog fileDlg(_("Select LyX document to insert"),
1588                         LFUN_FILE_INSERT,
1589                         make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)),
1590                         make_pair(_("Examples|#E#e"), from_utf8(addPath(package().system_support().absFilename(), "examples"))));
1591
1592                 FileDialog::Result result =
1593                         fileDlg.open(from_utf8(initpath),
1594                                      FileFilterList(_("LyX Documents (*.lyx)")),
1595                                      docstring());
1596
1597                 if (result.first == FileDialog::Later)
1598                         return;
1599
1600                 // FIXME UNICODE
1601                 filename = to_utf8(result.second);
1602
1603                 // check selected filename
1604                 if (filename.empty()) {
1605                         // emit message signal.
1606                         message(_("Canceled."));
1607                         return;
1608                 }
1609         }
1610
1611         // Get absolute path of file and add ".lyx"
1612         // to the filename if necessary
1613         filename = fileSearch(string(), filename, "lyx").absFilename();
1614
1615         docstring const disp_fn = makeDisplayPath(filename);
1616         // emit message signal.
1617         message(bformat(_("Inserting document %1$s..."), disp_fn));
1618
1619         docstring res;
1620         Buffer buf("", false);
1621         if (lyx::loadLyXFile(&buf, FileName(filename))) {
1622                 ErrorList & el = buffer_->errorList("Parse");
1623                 // Copy the inserted document error list into the current buffer one.
1624                 el = buf.errorList("Parse");
1625                 recordUndo(cursor_);
1626                 cap::pasteParagraphList(cursor_, buf.paragraphs(),
1627                                              buf.params().textclass, el);
1628                 res = _("Document %1$s inserted.");
1629         } else
1630                 res = _("Could not insert document %1$s");
1631
1632         // emit message signal.
1633         message(bformat(res, disp_fn));
1634         buffer_->errors("Parse");
1635         resize();
1636 }
1637
1638 } // namespace lyx