]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
This commit continues the drastic diet operated on BufferView. It removes all boost...
[lyx.git] / src / BufferView_pimpl.C
1 /**
2  * \file BufferView_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Alfredo Braunstein
8  * \author Lars Gullik Bjønnes
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Dekel Tsur
14  * \author Jürgen Vigna
15  *
16  * Full author contact details are available in file CREDITS.
17  */
18
19 #include <config.h>
20
21 #include "BufferView_pimpl.h"
22 #include "buffer.h"
23 #include "buffer_funcs.h"
24 #include "bufferlist.h"
25 #include "bufferparams.h"
26 #include "coordcache.h"
27 #include "cursor.h"
28 #include "CutAndPaste.h"
29 #include "debug.h"
30 #include "dispatchresult.h"
31 #include "factory.h"
32 #include "FloatList.h"
33 #include "funcrequest.h"
34 #include "FuncStatus.h"
35 #include "gettext.h"
36 #include "intl.h"
37 #include "insetiterator.h"
38 #include "LaTeXFeatures.h"
39 #include "lyx_cb.h" // added for Dispatch functions
40 #include "lyx_main.h"
41 #include "lyxfind.h"
42 #include "lyxfunc.h"
43 #include "lyxtext.h"
44 #include "lyxrc.h"
45 #include "session.h"
46 #include "metricsinfo.h"
47 #include "paragraph.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "pariterator.h"
51 #include "toc.h"
52 #include "undo.h"
53 #include "vspace.h"
54
55 #include "insets/insetbibtex.h"
56 #include "insets/insetref.h"
57 #include "insets/insettext.h"
58
59 #include "frontends/Alert.h"
60 #include "frontends/Dialogs.h"
61 #include "frontends/FileDialog.h"
62 #include "frontends/font_metrics.h"
63 #include "frontends/Gui.h"
64 #include "frontends/LyXView.h"
65 #include "frontends/Selection.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 using lyx::frontend::Clipboard;
82 using lyx::frontend::Gui;
83
84 using lyx::pos_type;
85
86 using lyx::support::addPath;
87 using lyx::support::bformat;
88 using lyx::support::FileFilterList;
89 using lyx::support::fileSearch;
90 using lyx::support::isDirWriteable;
91 using lyx::support::makeDisplayPath;
92 using lyx::support::makeAbsPath;
93 using lyx::support::package;
94
95 using std::endl;
96 using std::istringstream;
97 using std::make_pair;
98 using std::min;
99 using std::max;
100 using std::string;
101 using std::mem_fun_ref;
102 using std::vector;
103
104 extern BufferList bufferlist;
105
106
107 namespace {
108
109 unsigned int const saved_positions_num = 20;
110
111
112 /// Return an inset of this class if it exists at the current cursor position
113 template <class T>
114 T * getInsetByCode(LCursor & cur, InsetBase::Code code)
115 {
116         T * inset = 0;
117         DocIterator it = cur;
118         if (it.nextInset() &&
119             it.nextInset()->lyxCode() == code) {
120                 inset = static_cast<T*>(it.nextInset());
121         }
122         return inset;
123 }
124
125 } // anon namespace
126
127
128 BufferView::Pimpl::Pimpl(BufferView & bv, LyXView * owner)
129         : bv_(&bv), owner_(owner), buffer_(0), wh_(0),
130           cursor_(bv),
131           multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0)
132 {
133         xsel_cache_.set = false;
134
135         saved_positions.resize(saved_positions_num);
136         // load saved bookmarks
137         lyx::Session::BookmarkList & bmList = LyX::ref().session().loadBookmarks();
138         for (lyx::Session::BookmarkList::iterator bm = bmList.begin();
139                 bm != bmList.end(); ++bm)
140                 if (bm->get<0>() < saved_positions_num)
141                         saved_positions[bm->get<0>()] = Position( bm->get<1>(), bm->get<2>(), bm->get<3>() );
142         // and then clear them
143         bmList.clear();
144 }
145
146
147 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
148 {
149         // Get absolute path of file and add ".lyx"
150         // to the filename if necessary
151         string s = fileSearch(string(), filename, "lyx");
152
153         bool const found = !s.empty();
154
155         if (!found)
156                 s = filename;
157
158         // File already open?
159         if (bufferlist.exists(s)) {
160                 string const file = makeDisplayPath(s, 20);
161                 string text = bformat(_("The document %1$s is already "
162                                         "loaded.\n\nDo you want to revert "
163                                         "to the saved version?"), file);
164                 int const ret = Alert::prompt(_("Revert to saved document?"),
165                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
166
167                 if (ret != 0) {
168                         setBuffer(bufferlist.getBuffer(s));
169                         return true;
170                 }
171                 // FIXME: should be LFUN_REVERT
172                 if (!bufferlist.close(bufferlist.getBuffer(s), false))
173                         return false;
174                 // Fall through to new load. (Asger)
175         }
176
177         Buffer * b = 0;
178
179         if (found) {
180                 b = bufferlist.newBuffer(s);
181                 if (!::loadLyXFile(b, s)) {
182                         bufferlist.release(b);
183                         return false;
184                 }
185         } else {
186                 string text = bformat(_("The document %1$s does not yet "
187                                         "exist.\n\nDo you want to create "
188                                         "a new document?"), s);
189                 int const ret = Alert::prompt(_("Create new document?"),
190                          text, 0, 1, _("&Create"), _("Cancel"));
191
192                 if (ret == 0)
193                         b = newFile(s, string(), true);
194                 else
195                         return false;
196         }
197
198         setBuffer(b);
199         owner_->showErrorList(_("Parse"));
200
201         // scroll to the position when the file was last closed
202         if (lyxrc.use_lastfilepos) {
203                 lyx::pit_type pit;
204                 lyx::pos_type pos;
205                 boost::tie(pit, pos) = LyX::ref().session().loadFilePosition(s);
206                 // I am not sure how to separate the following part to a function
207                 // so I will leave this to Lars.
208                 //
209                 // check pit since the document may be externally changed.
210                 if ( static_cast<size_t>(pit) < b->paragraphs().size() ) {
211                         ParIterator it = b->par_iterator_begin();
212                         ParIterator const end = b->par_iterator_end();
213                         for (; it != end; ++it)
214                                 if (it.pit() == pit) {
215                                         // restored pos may be bigger than it->size
216                                         bv_->setCursor(makeDocIterator(it, min(pos, it->size())));
217                                         bv_->update(Update::FitCursor);
218                                         break;
219                                 }
220                 }
221         }
222
223         if (tolastfiles)
224                 LyX::ref().session().addLastFile(b->fileName());
225
226         return true;
227 }
228
229
230 lyx::frontend::Gui & BufferView::Pimpl::gui() const
231 {
232         return owner_->gui();
233 }
234
235
236 int BufferView::Pimpl::width() const
237 {
238         return width_;
239 }
240
241
242 int BufferView::Pimpl::height() const
243 {
244         return height_;
245 }
246
247
248 void BufferView::Pimpl::setBuffer(Buffer * b)
249 {
250         lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
251                             << "[ b = " << b << "]" << endl;
252
253         if (buffer_) {
254                 // Save the actual cursor position and anchor inside the
255                 // buffer so that it can be restored in case we rechange
256                 // to this buffer later on.
257                 buffer_->saveCursor(cursor_.selectionBegin(),
258                                     cursor_.selectionEnd());
259                 // current buffer is going to be switched-off, save cursor pos
260                 LyX::ref().session().saveFilePosition(buffer_->fileName(),
261                         boost::tie(cursor_.pit(), cursor_.pos()) );
262         }
263
264         // If we are closing current buffer, switch to the first in
265         // buffer list.
266         if (!b) {
267                 lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
268                                     << " No Buffer!" << endl;
269                 // We are closing the buffer, use the first buffer as current
270                 buffer_ = bufferlist.first();
271                 owner_->getDialogs().hideBufferDependent();
272         } else {
273                 // Set current buffer
274                 buffer_ = b;
275         }
276
277         // Reset old cursor
278         cursor_ = LCursor(*bv_);
279         anchor_ref_ = 0;
280         offset_ref_ = 0;
281
282
283         // If we're quitting lyx, don't bother updating stuff
284         if (quitting)
285                 return;
286
287         if (buffer_) {
288                 lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
289                                     << "Buffer addr: " << buffer_ << endl;
290                 cursor_.push(buffer_->inset());
291                 cursor_.resetAnchor();
292                 buffer_->text().init(bv_);
293                 buffer_->text().setCurrentFont(cursor_);
294                 if (buffer_->getCursor().size() > 0 &&
295                     buffer_->getAnchor().size() > 0)
296                 {
297                         cursor_.setCursor(buffer_->getAnchor().asDocIterator(&(buffer_->inset())));
298                         cursor_.resetAnchor();
299                         cursor_.setCursor(buffer_->getCursor().asDocIterator(&(buffer_->inset())));
300                         cursor_.setSelection();
301                 }
302
303                 // Buffer-dependent dialogs should be updated or
304                 // hidden. This should go here because some dialogs (eg ToC)
305                 // require bv_->text.
306                 owner_->getDialogs().updateBufferDependent(true);
307         }
308
309         update();
310
311         if (buffer_ && lyx::graphics::Previews::status() != LyXRC::PREVIEW_OFF)
312                 lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
313 }
314
315 string BufferView::Pimpl::firstLayout()
316 {
317         string firstlayout;
318
319         // This is done after the layout combox has been populated
320         if (buffer_) {
321                 size_t i = cursor_.depth() - 1;
322                 // we know we'll eventually find a paragraph
323                 while (true) {
324                         CursorSlice const & slice = cursor_[i];
325                         if (!slice.inset().inMathed()) {
326                                 LyXLayout_ptr const layout = slice.paragraph().layout();
327                                 firstlayout = layout->name();
328                                 break;
329                         }
330                         BOOST_ASSERT(i>0);
331                         --i;
332                 }
333         }
334         return firstlayout;
335 }
336
337
338 void BufferView::Pimpl::resizeCurrentBuffer()
339 {
340         lyxerr[Debug::DEBUG] << BOOST_CURRENT_FUNCTION << endl;
341         owner_->busy(true);
342         owner_->message(_("Formatting document..."));
343
344         LyXText * text = bv_->text();
345         if (!text)
346                 return;
347
348         text->init(bv_);
349         update();
350
351         switchKeyMap();
352         owner_->busy(false);
353
354         // Reset the "Formatting..." message
355         owner_->clearMessage();
356 }
357
358
359 void BufferView::Pimpl::updateScrollbar()
360 {
361         if (!bv_->text()) {
362                 lyxerr[Debug::DEBUG] << BOOST_CURRENT_FUNCTION
363                                      << " no text in updateScrollbar" << endl;
364                 scrollbarParameters_.reset();
365                 return;
366         }
367
368         LyXText & t = *bv_->text();
369         int const parsize = int(t.paragraphs().size() - 1);
370         if (anchor_ref_ >  parsize)  {
371                 anchor_ref_ = parsize;
372                 offset_ref_ = 0;
373         }
374
375         lyxerr[Debug::GUI]
376                 << BOOST_CURRENT_FUNCTION
377                 << " Updating scrollbar: height: " << t.paragraphs().size()
378                 << " curr par: " << cursor_.bottom().pit()
379                 << " default height " << defaultRowHeight() << endl;
380
381         // It would be better to fix the scrollbar to understand
382         // values in [0..1] and divide everything by wh
383
384         // estimated average paragraph height:
385         if (wh_ == 0)
386                 wh_ = height_ / 4;
387         int h = t.getPar(anchor_ref_).height();
388
389         // Normalize anchor/offset (MV):
390         while (offset_ref_ > h && anchor_ref_ < parsize) {
391                 anchor_ref_++;
392                 offset_ref_ -= h;
393                 h = t.getPar(anchor_ref_).height();
394         }
395         // Look at paragraph heights on-screen
396         int sumh = 0;
397         int nh = 0;
398         for (lyx::pit_type pit = anchor_ref_; pit <= parsize; ++pit) {
399                 if (sumh > height_)
400                         break;
401                 int const h2 = t.getPar(pit).height();
402                 sumh += h2;
403                 nh++;
404         }
405         int const hav = sumh / nh;
406         // More realistic average paragraph height
407         if (hav > wh_)
408                 wh_ = hav;
409
410         scrollbarParameters_.height = (parsize + 1) * wh_;
411         scrollbarParameters_.position = anchor_ref_ * wh_ + int(offset_ref_ * wh_ / float(h));
412         scrollbarParameters_.lineScrollHeight = int(wh_ * defaultRowHeight() / float(h));
413 }
414
415
416 ScrollbarParameters const & BufferView::Pimpl::scrollbarParameters() const
417 {
418         return scrollbarParameters_;
419 }
420
421
422 void BufferView::Pimpl::scrollDocView(int value)
423 {
424         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
425                            << "[ value = " << value << "]" << endl;
426
427         if (!buffer_)
428                 return;
429
430         LyXText & t = *bv_->text();
431
432         float const bar = value / float(wh_ * t.paragraphs().size());
433
434         anchor_ref_ = int(bar * t.paragraphs().size());
435         if (anchor_ref_ >  int(t.paragraphs().size()) - 1)
436                 anchor_ref_ = int(t.paragraphs().size()) - 1;
437         t.redoParagraph(anchor_ref_);
438         int const h = t.getPar(anchor_ref_).height();
439         offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
440         update();
441
442         if (!lyxrc.cursor_follows_scrollbar)
443                 return;
444
445         int const height = 2 * defaultRowHeight();
446         int const first = height;
447         int const last = height_ - height;
448         LCursor & cur = cursor_;
449
450         bv_funcs::CurStatus st = bv_funcs::status(bv_, cur);
451
452         switch (st) {
453         case bv_funcs::CUR_ABOVE:
454                 t.setCursorFromCoordinates(cur, 0, first);
455                 cur.clearSelection();
456                 break;
457         case bv_funcs::CUR_BELOW:
458                 t.setCursorFromCoordinates(cur, 0, last);
459                 cur.clearSelection();
460                 break;
461         case bv_funcs::CUR_INSIDE:
462                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
463                 int const newy = min(last, max(y, first));
464                 if (y != newy) {
465                         cur.reset(buffer_->inset());
466                         t.setCursorFromCoordinates(cur, 0, newy);
467                 }
468         }
469         owner_->updateLayoutChoice();
470 }
471
472
473 void BufferView::Pimpl::scroll(int /*lines*/)
474 {
475 //      if (!buffer_)
476 //              return;
477 //
478 //      LyXText const * t = bv_->text();
479 //      int const line_height = defaultRowHeight();
480 //
481 //      // The new absolute coordinate
482 //      int new_top_y = top_y() + lines * line_height;
483 //
484 //      // Restrict to a valid value
485 //      new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
486 //      new_top_y = std::max(0, new_top_y);
487 //
488 //      scrollDocView(new_top_y);
489 //
490 //      // Update the scrollbar.
491 //      workArea_->setScrollbarParams(t->height(), top_y(), defaultRowHeight());}
492 }
493
494
495 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
496                                          key_modifier::state state)
497 {
498         owner_->getLyXFunc().processKeySym(key, state);
499 }
500
501
502 void BufferView::Pimpl::selectionRequested()
503 {
504         static string sel;
505
506         if (!available())
507                 return;
508
509         LCursor & cur = cursor_;
510
511         if (!cur.selection()) {
512                 xsel_cache_.set = false;
513                 return;
514         }
515
516         if (!xsel_cache_.set ||
517             cur.top() != xsel_cache_.cursor ||
518             cur.anchor_.top() != xsel_cache_.anchor)
519         {
520                 xsel_cache_.cursor = cur.top();
521                 xsel_cache_.anchor = cur.anchor_.top();
522                 xsel_cache_.set = cur.selection();
523                 sel = cur.selectionAsString(false);
524                 if (!sel.empty())
525                         owner_->gui().selection().put(sel);
526         }
527 }
528
529
530 void BufferView::Pimpl::selectionLost()
531 {
532         if (available()) {
533                 cursor_.clearSelection();
534                 xsel_cache_.set = false;
535         }
536 }
537
538
539 void BufferView::Pimpl::workAreaResize(int width, int height)
540 {
541         bool const widthChange = width != width_;
542         bool const heightChange = height != height_;
543
544         // Update from work area
545         width_ = width;
546         height_ = height;
547
548         if (buffer_ && widthChange) {
549                 // The visible LyXView need a resize
550                 resizeCurrentBuffer();
551         }
552
553         if (widthChange || heightChange)
554                 update();
555
556         owner_->updateLayoutChoice();
557 }
558
559
560 bool BufferView::Pimpl::fitCursor()
561 {
562         if (bv_funcs::status(bv_, cursor_) == bv_funcs::CUR_INSIDE) {
563                 LyXFont const font = cursor_.getFont();
564                 int const asc = font_metrics::maxAscent(font);
565                 int const des = font_metrics::maxDescent(font);
566                 Point const p = bv_funcs::getPos(cursor_, cursor_.boundary());
567                 if (p.y_ - asc >= 0 && p.y_ + des < height_)
568                         return false;
569         }
570         center();
571         return true;
572 }
573
574
575 bool BufferView::Pimpl::multiParSel()
576 {
577         if (!cursor_.selection())
578                 return false;
579         bool ret = multiparsel_cache_;
580         multiparsel_cache_ = cursor_.selBegin().pit() != cursor_.selEnd().pit();
581         // Either this, or previous selection spans paragraphs
582         return ret || multiparsel_cache_;
583 }
584
585
586 ViewMetricsInfo const & BufferView::Pimpl::viewMetricsInfo()
587 {
588         return metrics_info_;
589 }
590
591
592 bool BufferView::Pimpl::update(Update::flags flags)
593 {
594         // This is close to a hot-path.
595         if (lyxerr.debugging(Debug::DEBUG)) {
596                 lyxerr[Debug::DEBUG]
597                         << BOOST_CURRENT_FUNCTION
598                         << "[fitcursor = " << (flags & Update::FitCursor)
599                         << ", forceupdate = " << (flags & Update::Force)
600                         << ", singlepar = " << (flags & Update::SinglePar)
601                         << "]  buffer: " << buffer_ << endl;
602         }
603
604         // Check needed to survive LyX startup
605         if (!buffer_)
606                 return false;
607
608         // Update macro store
609         buffer_->buildMacros();
610
611         // First drawing step
612         updateMetrics(flags & Update::SinglePar);
613
614         // The second drawing step is done in WorkArea::redraw() if needed.
615         bool const need_second_step =
616                 (flags & (Update::Force | Update::FitCursor | Update::MultiParSel))
617                 && (fitCursor() || multiParSel());
618         
619         return need_second_step;
620 }
621
622
623 bool BufferView::Pimpl::available() const
624 {
625         return buffer_ && bv_->text();
626 }
627
628
629 Change const BufferView::Pimpl::getCurrentChange()
630 {
631         if (!buffer_->params().tracking_changes)
632                 return Change(Change::UNCHANGED);
633
634         LyXText * text = bv_->getLyXText();
635         LCursor & cur = cursor_;
636
637         if (!cur.selection())
638                 return Change(Change::UNCHANGED);
639
640         return text->getPar(cur.selBegin().pit()).
641                         lookupChange(cur.selBegin().pos());
642 }
643
644
645 void BufferView::Pimpl::savePosition(unsigned int i)
646 {
647         if (i >= saved_positions_num)
648                 return;
649         BOOST_ASSERT(cursor_.inTexted());
650         saved_positions[i] = Position(buffer_->fileName(),
651                                       cursor_.paragraph().id(),
652                                       cursor_.pos());
653         if (i > 0)
654                 owner_->message(bformat(_("Saved bookmark %1$d"), i));
655 }
656
657
658 void BufferView::Pimpl::restorePosition(unsigned int i)
659 {
660         if (i >= saved_positions_num)
661                 return;
662
663         string const fname = saved_positions[i].filename;
664
665         cursor_.clearSelection();
666
667         if (fname != buffer_->fileName()) {
668                 Buffer * b = 0;
669                 if (bufferlist.exists(fname))
670                         b = bufferlist.getBuffer(fname);
671                 else {
672                         b = bufferlist.newBuffer(fname);
673                         // Don't ask, just load it
674                         ::loadLyXFile(b, fname);
675                 }
676                 if (b)
677                         setBuffer(b);
678         }
679
680         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
681         if (par == buffer_->par_iterator_end())
682                 return;
683
684         bv_->setCursor(makeDocIterator(par, min(par->size(), saved_positions[i].par_pos)));
685
686         if (i > 0)
687                 owner_->message(bformat(_("Moved to bookmark %1$d"), i));
688 }
689
690
691 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
692 {
693         return i < saved_positions_num && !saved_positions[i].filename.empty();
694 }
695
696
697 void BufferView::Pimpl::saveSavedPositions()
698 {
699         // save bookmarks. It is better to use the pit interface
700         // but I do not know how to effectively convert between
701         // par_id and pit.
702         for (unsigned int i=1; i < saved_positions_num; ++i) {
703                 if ( isSavedPosition(i) )
704                         LyX::ref().session().saveBookmark( boost::tie(
705                                 i,
706                                 saved_positions[i].filename,
707                                 saved_positions[i].par_id,
708                                 saved_positions[i].par_pos) );
709         }
710 }
711
712
713 void BufferView::Pimpl::switchKeyMap()
714 {
715         if (!lyxrc.rtl_support)
716                 return;
717
718         Intl & intl = owner_->getIntl();
719         if (bv_->getLyXText()->real_current_font.isRightToLeft()) {
720                 if (intl.keymap == Intl::PRIMARY)
721                         intl.keyMapSec();
722         } else {
723                 if (intl.keymap == Intl::SECONDARY)
724                         intl.keyMapPrim();
725         }
726 }
727
728
729 void BufferView::Pimpl::center()
730 {
731         CursorSlice & bot = cursor_.bottom();
732         lyx::pit_type const pit = bot.pit();
733         bot.text()->redoParagraph(pit);
734         Paragraph const & par = bot.text()->paragraphs()[pit];
735         anchor_ref_ = pit;
736         offset_ref_ = bv_funcs::coordOffset(cursor_, cursor_.boundary()).y_
737                 + par.ascent() - height_ / 2;
738 }
739
740
741 void BufferView::Pimpl::menuInsertLyXFile(string const & filenm)
742 {
743         BOOST_ASSERT(cursor_.inTexted());
744         string filename = filenm;
745
746         if (filename.empty()) {
747                 // Launch a file browser
748                 string initpath = lyxrc.document_path;
749
750                 if (available()) {
751                         string const trypath = buffer_->filePath();
752                         // If directory is writeable, use this as default.
753                         if (isDirWriteable(trypath))
754                                 initpath = trypath;
755                 }
756
757                 FileDialog fileDlg(_("Select LyX document to insert"),
758                         LFUN_FILE_INSERT,
759                         make_pair(string(_("Documents|#o#O")),
760                                   string(lyxrc.document_path)),
761                         make_pair(string(_("Examples|#E#e")),
762                                   string(addPath(package().system_support(), "examples"))));
763
764                 FileDialog::Result result =
765                         fileDlg.open(initpath,
766                                      FileFilterList(_("LyX Documents (*.lyx)")),
767                                      string());
768
769                 if (result.first == FileDialog::Later)
770                         return;
771
772                 filename = result.second;
773
774                 // check selected filename
775                 if (filename.empty()) {
776                         owner_->message(_("Canceled."));
777                         return;
778                 }
779         }
780
781         // Get absolute path of file and add ".lyx"
782         // to the filename if necessary
783         filename = fileSearch(string(), filename, "lyx");
784
785         string const disp_fn = makeDisplayPath(filename);
786         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
787
788         string res;
789         Buffer buf("", false);
790         buf.error.connect(boost::bind(&LyXView::addError, owner_, _1));
791         if (::loadLyXFile(&buf, makeAbsPath(filename))) {
792                 lyx::cap::pasteParagraphList(cursor_, buf.paragraphs(),
793                                              buf.params().textclass);
794                 res = _("Document %1$s inserted.");
795         } else
796                 res = _("Could not insert document %1$s");
797
798         owner_->message(bformat(res, disp_fn));
799         owner_->showErrorList(_("Document insertion"));
800         resizeCurrentBuffer();
801 }
802
803
804 void BufferView::Pimpl::trackChanges()
805 {
806         bool const tracking = buffer_->params().tracking_changes;
807
808         if (!tracking) {
809                 for_each(buffer_->par_iterator_begin(),
810                          buffer_->par_iterator_end(),
811                          bind(&Paragraph::trackChanges, _1, Change::UNCHANGED));
812                 buffer_->params().tracking_changes = true;
813
814                 // We cannot allow undos beyond the freeze point
815                 buffer_->undostack().clear();
816         } else {
817                 cursor_.setCursor(doc_iterator_begin(buffer_->inset()));
818                 if (lyx::find::findNextChange(bv_)) {
819                         owner_->getDialogs().show("changes");
820                         return;
821                 }
822
823                 for_each(buffer_->par_iterator_begin(),
824                          buffer_->par_iterator_end(),
825                          mem_fun_ref(&Paragraph::untrackChanges));
826
827                 buffer_->params().tracking_changes = false;
828         }
829
830         buffer_->redostack().clear();
831 }
832
833
834 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
835 {
836         //lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl;
837
838         // This is only called for mouse related events including
839         // LFUN_FILE_OPEN generated by drag-and-drop.
840         FuncRequest cmd = cmd0;
841
842         // Handle drag&drop
843         if (cmd.action == LFUN_FILE_OPEN) {
844                 owner_->dispatch(cmd);
845                 return true;
846         }
847
848         if (!buffer_)
849                 return false;
850
851         LCursor cur(*bv_);
852         cur.push(buffer_->inset());
853         cur.selection() = cursor_.selection();
854
855         // Doesn't go through lyxfunc, so we need to update
856         // the layout choice etc. ourselves
857
858         // E.g. Qt mouse press when no buffer
859         if (!available())
860                 return false;
861
862         // Either the inset under the cursor or the
863         // surrounding LyXText will handle this event.
864
865         // Build temporary cursor.
866         cmd.y = min(max(cmd.y, -1), height_);
867         InsetBase * inset = bv_->text()->editXY(cur, cmd.x, cmd.y);
868         //lyxerr << BOOST_CURRENT_FUNCTION
869         //       << " * hit inset at tip: " << inset << endl;
870         //lyxerr << BOOST_CURRENT_FUNCTION
871         //       << " * created temp cursor:" << cur << endl;
872
873         // Put anchor at the same position.
874         cur.resetAnchor();
875
876         // Try to dispatch to an non-editable inset near this position
877         // via the temp cursor. If the inset wishes to change the real
878         // cursor it has to do so explicitly by using
879         //  cur.bv().cursor() = cur;  (or similar)
880         if (inset)
881                 inset->dispatch(cur, cmd);
882
883         // Now dispatch to the temporary cursor. If the real cursor should
884         // be modified, the inset's dispatch has to do so explicitly.
885         if (!cur.result().dispatched())
886                 cur.dispatch(cmd);
887
888         if (cur.result().dispatched()) {
889                 // Redraw if requested or necessary.
890                 if (cur.result().update())
891                         update(Update::FitCursor | Update::Force);
892                 else
893                         update(Update::FitCursor | Update::MultiParSel);
894         }
895
896         // Skip these when selecting
897         if (cmd.action != LFUN_MOUSE_MOTION) {
898                 owner_->updateLayoutChoice();
899                 owner_->updateToolbars();
900         }
901
902         // Slight hack: this is only called currently when we
903         // clicked somewhere, so we force through the display
904         // of the new status here.
905         owner_->clearMessage();
906         return true;
907 }
908
909
910 FuncStatus BufferView::Pimpl::getStatus(FuncRequest const & cmd)
911 {
912         FuncStatus flag;
913
914         switch (cmd.action) {
915
916         case LFUN_UNDO:
917                 flag.enabled(!buffer_->undostack().empty());
918                 break;
919         case LFUN_REDO:
920                 flag.enabled(!buffer_->redostack().empty());
921                 break;
922         case LFUN_FILE_INSERT:
923         case LFUN_FILE_INSERT_ASCII_PARA:
924         case LFUN_FILE_INSERT_ASCII:
925         case LFUN_BOOKMARK_SAVE:
926                 // FIXME: Actually, these LFUNS should be moved to LyXText
927                 flag.enabled(cursor_.inTexted());
928                 break;
929         case LFUN_FONT_STATE:
930         case LFUN_LABEL_INSERT:
931         case LFUN_PARAGRAPH_GOTO:
932         // FIXME handle non-trivially
933         case LFUN_OUTLINE_UP:
934         case LFUN_OUTLINE_DOWN:
935         case LFUN_OUTLINE_IN:
936         case LFUN_OUTLINE_OUT:
937         case LFUN_ERROR_NEXT:
938         case LFUN_NOTE_NEXT:
939         case LFUN_REFERENCE_NEXT:
940         case LFUN_WORD_FIND:
941         case LFUN_WORD_REPLACE:
942         case LFUN_MARK_OFF:
943         case LFUN_MARK_ON:
944         case LFUN_MARK_TOGGLE:
945         case LFUN_SCREEN_RECENTER:
946         case LFUN_BIBTEX_DATABASE_ADD:
947         case LFUN_BIBTEX_DATABASE_DEL:
948         case LFUN_WORDS_COUNT:
949                 flag.enabled(true);
950                 break;
951
952         case LFUN_LABEL_GOTO: {
953                 flag.enabled(!cmd.argument.empty()
954                     || getInsetByCode<InsetRef>(cursor_, InsetBase::REF_CODE));
955                 break;
956         }
957
958         case LFUN_BOOKMARK_GOTO:
959                 flag.enabled(isSavedPosition(convert<unsigned int>(cmd.argument)));
960                 break;
961         case LFUN_CHANGES_TRACK:
962                 flag.enabled(true);
963                 flag.setOnOff(buffer_->params().tracking_changes);
964                 break;
965
966         case LFUN_CHANGES_OUTPUT: {
967                 OutputParams runparams;
968                 LaTeXFeatures features(*buffer_, buffer_->params(), runparams);
969                 flag.enabled(buffer_ && buffer_->params().tracking_changes
970                         && features.isAvailable("dvipost"));
971                 flag.setOnOff(buffer_->params().output_changes);
972                 break;
973         }
974
975         case LFUN_CHANGES_MERGE:
976         case LFUN_CHANGE_ACCEPT: // what about these two
977         case LFUN_CHANGE_REJECT: // what about these two
978         case LFUN_ALL_CHANGES_ACCEPT:
979         case LFUN_ALL_CHANGES_REJECT:
980                 flag.enabled(buffer_ && buffer_->params().tracking_changes);
981                 break;
982
983         case LFUN_BUFFER_TOGGLE_COMPRESSION: {
984                 flag.setOnOff(buffer_->params().compressed);
985                 break;
986         }
987
988         default:
989                 flag.enabled(false);
990         }
991
992         return flag;
993 }
994
995
996
997 bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
998 {
999         //lyxerr << BOOST_CURRENT_FUNCTION
1000         //       << [ cmd = " << cmd << "]" << endl;
1001
1002         // Make sure that the cached BufferView is correct.
1003         lyxerr[Debug::ACTION] << BOOST_CURRENT_FUNCTION
1004                 << " action[" << cmd.action << ']'
1005                 << " arg[" << cmd.argument << ']'
1006                 << " x[" << cmd.x << ']'
1007                 << " y[" << cmd.y << ']'
1008                 << " button[" << cmd.button() << ']'
1009                 << endl;
1010
1011         LCursor & cur = cursor_;
1012
1013         switch (cmd.action) {
1014
1015         case LFUN_UNDO:
1016                 if (available()) {
1017                         cur.message(_("Undo"));
1018                         cur.clearSelection();
1019                         if (!textUndo(*bv_))
1020                                 cur.message(_("No further undo information"));
1021                         update();
1022                         switchKeyMap();
1023                 }
1024                 break;
1025
1026         case LFUN_REDO:
1027                 if (available()) {
1028                         cur.message(_("Redo"));
1029                         cur.clearSelection();
1030                         if (!textRedo(*bv_))
1031                                 cur.message(_("No further redo information"));
1032                         update();
1033                         switchKeyMap();
1034                 }
1035                 break;
1036
1037         case LFUN_FILE_INSERT:
1038                 menuInsertLyXFile(cmd.argument);
1039                 break;
1040
1041         case LFUN_FILE_INSERT_ASCII_PARA:
1042                 insertAsciiFile(bv_, cmd.argument, true);
1043                 break;
1044
1045         case LFUN_FILE_INSERT_ASCII:
1046                 insertAsciiFile(bv_, cmd.argument, false);
1047                 break;
1048
1049         case LFUN_FONT_STATE:
1050                 cur.message(cur.currentState());
1051                 break;
1052
1053         case LFUN_BOOKMARK_SAVE:
1054                 savePosition(convert<unsigned int>(cmd.argument));
1055                 break;
1056
1057         case LFUN_BOOKMARK_GOTO:
1058                 restorePosition(convert<unsigned int>(cmd.argument));
1059                 break;
1060
1061         case LFUN_LABEL_GOTO: {
1062                 string label = cmd.argument;
1063                 if (label.empty()) {
1064                         InsetRef * inset =
1065                                 getInsetByCode<InsetRef>(cursor_,
1066                                                          InsetBase::REF_CODE);
1067                         if (inset) {
1068                                 label = inset->getContents();
1069                                 savePosition(0);
1070                         }
1071                 }
1072
1073                 if (!label.empty())
1074                         bv_->gotoLabel(label);
1075                 break;
1076         }
1077
1078         case LFUN_PARAGRAPH_GOTO: {
1079                 int const id = convert<int>(cmd.argument);
1080                 ParIterator par = buffer_->getParFromID(id);
1081                 if (par == buffer_->par_iterator_end()) {
1082                         lyxerr[Debug::INFO] << "No matching paragraph found! ["
1083                                             << id << ']' << endl;
1084                         break;
1085                 } else {
1086                         lyxerr[Debug::INFO] << "Paragraph " << par->id()
1087                                             << " found." << endl;
1088                 }
1089
1090                 // Set the cursor
1091                 bv_->setCursor(makeDocIterator(par, 0));
1092
1093                 update();
1094                 switchKeyMap();
1095                 break;
1096         }
1097
1098         case LFUN_OUTLINE_UP:
1099                 lyx::toc::outline(lyx::toc::Up, cursor_);
1100                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
1101                 updateLabels(*buffer_);
1102                 break;
1103         case LFUN_OUTLINE_DOWN:
1104                 lyx::toc::outline(lyx::toc::Down, cursor_);
1105                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
1106                 updateLabels(*buffer_);
1107                 break;
1108         case LFUN_OUTLINE_IN:
1109                 lyx::toc::outline(lyx::toc::In, cursor_);
1110                 updateLabels(*buffer_);
1111                 break;
1112         case LFUN_OUTLINE_OUT:
1113                 lyx::toc::outline(lyx::toc::Out, cursor_);
1114                 updateLabels(*buffer_);
1115                 break;
1116
1117         case LFUN_ERROR_NEXT:
1118                 bv_funcs::gotoInset(bv_, InsetBase::ERROR_CODE, false);
1119                 break;
1120
1121         case LFUN_NOTE_NEXT:
1122                 bv_funcs::gotoInset(bv_, InsetBase::NOTE_CODE, false);
1123                 break;
1124
1125         case LFUN_REFERENCE_NEXT: {
1126                 vector<InsetBase_code> tmp;
1127                 tmp.push_back(InsetBase::LABEL_CODE);
1128                 tmp.push_back(InsetBase::REF_CODE);
1129                 bv_funcs::gotoInset(bv_, tmp, true);
1130                 break;
1131         }
1132
1133         case LFUN_CHANGES_TRACK:
1134                 trackChanges();
1135                 break;
1136
1137         case LFUN_CHANGES_OUTPUT: {
1138                 bool const state = buffer_->params().output_changes;
1139                 buffer_->params().output_changes = !state;
1140                 break;
1141         }
1142
1143         case LFUN_CHANGES_MERGE:
1144                 if (lyx::find::findNextChange(bv_))
1145                         owner_->getDialogs().show("changes");
1146                 break;
1147
1148         case LFUN_ALL_CHANGES_ACCEPT: {
1149                 cursor_.reset(buffer_->inset());
1150 #ifdef WITH_WARNINGS
1151 #warning FIXME changes
1152 #endif
1153                 while (lyx::find::findNextChange(bv_))
1154                         bv_->getLyXText()->acceptChange(cursor_);
1155                 update();
1156                 break;
1157         }
1158
1159         case LFUN_ALL_CHANGES_REJECT: {
1160                 cursor_.reset(buffer_->inset());
1161 #ifdef WITH_WARNINGS
1162 #warning FIXME changes
1163 #endif
1164                 while (lyx::find::findNextChange(bv_))
1165                         bv_->getLyXText()->rejectChange(cursor_);
1166                 break;
1167         }
1168
1169         case LFUN_WORD_FIND:
1170                 lyx::find::find(bv_, cmd);
1171                 break;
1172
1173         case LFUN_WORD_REPLACE:
1174                 lyx::find::replace(bv_, cmd);
1175                 break;
1176
1177         case LFUN_MARK_OFF:
1178                 cur.clearSelection();
1179                 cur.resetAnchor();
1180                 cur.message(N_("Mark off"));
1181                 break;
1182
1183         case LFUN_MARK_ON:
1184                 cur.clearSelection();
1185                 cur.mark() = true;
1186                 cur.resetAnchor();
1187                 cur.message(N_("Mark on"));
1188                 break;
1189
1190         case LFUN_MARK_TOGGLE:
1191                 cur.clearSelection();
1192                 if (cur.mark()) {
1193                         cur.mark() = false;
1194                         cur.message(N_("Mark removed"));
1195                 } else {
1196                         cur.mark() = true;
1197                         cur.message(N_("Mark set"));
1198                 }
1199                 cur.resetAnchor();
1200                 break;
1201
1202         case LFUN_SCREEN_RECENTER:
1203                 center();
1204                 break;
1205
1206         case LFUN_BIBTEX_DATABASE_ADD: {
1207                 LCursor tmpcur = cursor_;
1208                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1209                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1210                                                 InsetBase::BIBTEX_CODE);
1211                 if (inset) {
1212                         if (inset->addDatabase(cmd.argument))
1213                                 buffer_->updateBibfilesCache();
1214                 }
1215                 break;
1216         }
1217
1218         case LFUN_BIBTEX_DATABASE_DEL: {
1219                 LCursor tmpcur = cursor_;
1220                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1221                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1222                                                 InsetBase::BIBTEX_CODE);
1223                 if (inset) {
1224                         if (inset->delDatabase(cmd.argument))
1225                                 buffer_->updateBibfilesCache();
1226                 }
1227                 break;
1228         }
1229
1230         case LFUN_WORDS_COUNT: {
1231                 DocIterator from, to;
1232                 if (cur.selection()) {
1233                         from = cur.selectionBegin();
1234                         to = cur.selectionEnd();
1235                 } else {
1236                         from = doc_iterator_begin(buffer_->inset());
1237                         to = doc_iterator_end(buffer_->inset());
1238                 }
1239                 int const count = countWords(from, to);
1240                 string message;
1241                 if (count != 1) {
1242                         if (cur.selection())
1243                                 message = bformat(_("%1$d words in selection."),
1244                                           count);
1245                                 else
1246                                         message = bformat(_("%1$d words in document."),
1247                                                           count);
1248                 }
1249                 else {
1250                         if (cur.selection())
1251                                 message = _("One word in selection.");
1252                         else
1253                                 message = _("One word in document.");
1254                 }
1255
1256                 Alert::information(_("Count words"), message);
1257         }
1258                 break;
1259
1260         case LFUN_BUFFER_TOGGLE_COMPRESSION:
1261                 // turn compression on/off
1262                 buffer_->params().compressed = !buffer_->params().compressed;
1263                 break;
1264
1265         default:
1266                 return false;
1267         }
1268
1269         return true;
1270 }
1271
1272
1273 void BufferView::Pimpl::updateMetrics(bool singlepar)
1274 {
1275         // Remove old position cache
1276         theCoords.clear();
1277         BufferView & bv = *bv_;
1278         LyXText * const text = bv.text();
1279         lyx::pit_type size = int(text->paragraphs().size());
1280
1281         if (anchor_ref_ > int(text->paragraphs().size() - 1)) {
1282                 anchor_ref_ = int(text->paragraphs().size() - 1);
1283                 offset_ref_ = 0;
1284         }
1285
1286         lyx::pit_type const pit = anchor_ref_;
1287         int pit1 = pit;
1288         int pit2 = pit;
1289         size_t const npit = text->paragraphs().size();
1290
1291         // Rebreak anchor paragraph. In Single Paragraph mode, rebreak only
1292         // the (main text, not inset!) paragraph containing the cursor.
1293         // (if this paragraph contains insets etc., rebreaking will
1294         // recursively descend)
1295         if (!singlepar || pit == cursor_.bottom().pit())
1296                 text->redoParagraph(pit);
1297         int y0 = text->getPar(pit).ascent() - offset_ref_;
1298
1299         // Redo paragraphs above anchor if necessary; again, in Single Par
1300         // mode, only if we encounter the (main text) one having the cursor.
1301         int y1 = y0;
1302         while (y1 > 0 && pit1 > 0) {
1303                 y1 -= text->getPar(pit1).ascent();
1304                 --pit1;
1305                 if (!singlepar || pit1 == cursor_.bottom().pit())
1306                         text->redoParagraph(pit1);
1307                 y1 -= text->getPar(pit1).descent();
1308         }
1309
1310
1311         // Take care of ascent of first line
1312         y1 -= text->getPar(pit1).ascent();
1313
1314         // Normalize anchor for next time
1315         anchor_ref_ = pit1;
1316         offset_ref_ = -y1;
1317
1318         // Grey at the beginning is ugly
1319         if (pit1 == 0 && y1 > 0) {
1320                 y0 -= y1;
1321                 y1 = 0;
1322                 anchor_ref_ = 0;
1323         }
1324
1325         // Redo paragraphs below the anchor if necessary. Single par mode:
1326         // only the one containing the cursor if encountered.
1327         int y2 = y0;
1328         while (y2 < bv.workHeight() && pit2 < int(npit) - 1) {
1329                 y2 += text->getPar(pit2).descent();
1330                 ++pit2;
1331                 if (!singlepar || pit2 == cursor_.bottom().pit())
1332                         text->redoParagraph(pit2);
1333                 y2 += text->getPar(pit2).ascent();
1334         }
1335
1336         // Take care of descent of last line
1337         y2 += text->getPar(pit2).descent();
1338
1339         // The coordinates of all these paragraphs are correct, cache them
1340         int y = y1;
1341         CoordCache::InnerParPosCache & parPos = theCoords.parPos()[text];
1342         for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) {
1343                 Paragraph const & par = text->getPar(pit);
1344                 y += par.ascent();
1345                 parPos[pit] = Point(0, y);
1346                 if (singlepar && pit == cursor_.bottom().pit()) {
1347                         // In Single Paragraph mode, collect here the
1348                         // y1 and y2 of the (one) paragraph the cursor is in
1349                         y1 = y - par.ascent();
1350                         y2 = y + par.descent();
1351                 }
1352                 y += par.descent();
1353         }
1354
1355         if (singlepar) {
1356                 // collect cursor paragraph iter bounds
1357                 pit1 = cursor_.bottom().pit();
1358                 pit2 = cursor_.bottom().pit();
1359         }
1360
1361         lyxerr[Debug::DEBUG]
1362                 << BOOST_CURRENT_FUNCTION
1363                 << " y1: " << y1
1364                 << " y2: " << y2
1365                 << " pit1: " << pit1
1366                 << " pit2: " << pit2
1367                 << " npit: " << npit
1368                 << " singlepar: " << singlepar
1369                 << "size: " << size
1370                 << endl;
1371
1372         metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, singlepar, size);
1373 }