]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
This commit transfer the ErrorList handling from LyXView to Buffer. It also removes...
[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         // FIXME: is there a need for something like that?
791         //buf.errors.connect(boost::bind(&LyXView::showErrorList, owner_, _1));
792         if (::loadLyXFile(&buf, makeAbsPath(filename))) {
793                 lyx::cap::pasteParagraphList(cursor_, buf.paragraphs(),
794                                              buf.params().textclass);
795                 res = _("Document %1$s inserted.");
796         } else
797                 res = _("Could not insert document %1$s");
798
799         owner_->message(bformat(res, disp_fn));
800         owner_->showErrorList(_("Document insertion"));
801         resizeCurrentBuffer();
802 }
803
804
805 void BufferView::Pimpl::trackChanges()
806 {
807         bool const tracking = buffer_->params().tracking_changes;
808
809         if (!tracking) {
810                 for_each(buffer_->par_iterator_begin(),
811                          buffer_->par_iterator_end(),
812                          bind(&Paragraph::trackChanges, _1, Change::UNCHANGED));
813                 buffer_->params().tracking_changes = true;
814
815                 // We cannot allow undos beyond the freeze point
816                 buffer_->undostack().clear();
817         } else {
818                 cursor_.setCursor(doc_iterator_begin(buffer_->inset()));
819                 if (lyx::find::findNextChange(bv_)) {
820                         owner_->getDialogs().show("changes");
821                         return;
822                 }
823
824                 for_each(buffer_->par_iterator_begin(),
825                          buffer_->par_iterator_end(),
826                          mem_fun_ref(&Paragraph::untrackChanges));
827
828                 buffer_->params().tracking_changes = false;
829         }
830
831         buffer_->redostack().clear();
832 }
833
834
835 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
836 {
837         //lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl;
838
839         // This is only called for mouse related events including
840         // LFUN_FILE_OPEN generated by drag-and-drop.
841         FuncRequest cmd = cmd0;
842
843         // Handle drag&drop
844         if (cmd.action == LFUN_FILE_OPEN) {
845                 owner_->dispatch(cmd);
846                 return true;
847         }
848
849         if (!buffer_)
850                 return false;
851
852         LCursor cur(*bv_);
853         cur.push(buffer_->inset());
854         cur.selection() = cursor_.selection();
855
856         // Doesn't go through lyxfunc, so we need to update
857         // the layout choice etc. ourselves
858
859         // E.g. Qt mouse press when no buffer
860         if (!available())
861                 return false;
862
863         // Either the inset under the cursor or the
864         // surrounding LyXText will handle this event.
865
866         // Build temporary cursor.
867         cmd.y = min(max(cmd.y, -1), height_);
868         InsetBase * inset = bv_->text()->editXY(cur, cmd.x, cmd.y);
869         //lyxerr << BOOST_CURRENT_FUNCTION
870         //       << " * hit inset at tip: " << inset << endl;
871         //lyxerr << BOOST_CURRENT_FUNCTION
872         //       << " * created temp cursor:" << cur << endl;
873
874         // Put anchor at the same position.
875         cur.resetAnchor();
876
877         // Try to dispatch to an non-editable inset near this position
878         // via the temp cursor. If the inset wishes to change the real
879         // cursor it has to do so explicitly by using
880         //  cur.bv().cursor() = cur;  (or similar)
881         if (inset)
882                 inset->dispatch(cur, cmd);
883
884         // Now dispatch to the temporary cursor. If the real cursor should
885         // be modified, the inset's dispatch has to do so explicitly.
886         if (!cur.result().dispatched())
887                 cur.dispatch(cmd);
888
889         if (cur.result().dispatched()) {
890                 // Redraw if requested or necessary.
891                 if (cur.result().update())
892                         update(Update::FitCursor | Update::Force);
893                 else
894                         update(Update::FitCursor | Update::MultiParSel);
895         }
896
897         // Skip these when selecting
898         if (cmd.action != LFUN_MOUSE_MOTION) {
899                 owner_->updateLayoutChoice();
900                 owner_->updateToolbars();
901         }
902
903         // Slight hack: this is only called currently when we
904         // clicked somewhere, so we force through the display
905         // of the new status here.
906         owner_->clearMessage();
907         return true;
908 }
909
910
911 FuncStatus BufferView::Pimpl::getStatus(FuncRequest const & cmd)
912 {
913         FuncStatus flag;
914
915         switch (cmd.action) {
916
917         case LFUN_UNDO:
918                 flag.enabled(!buffer_->undostack().empty());
919                 break;
920         case LFUN_REDO:
921                 flag.enabled(!buffer_->redostack().empty());
922                 break;
923         case LFUN_FILE_INSERT:
924         case LFUN_FILE_INSERT_ASCII_PARA:
925         case LFUN_FILE_INSERT_ASCII:
926         case LFUN_BOOKMARK_SAVE:
927                 // FIXME: Actually, these LFUNS should be moved to LyXText
928                 flag.enabled(cursor_.inTexted());
929                 break;
930         case LFUN_FONT_STATE:
931         case LFUN_LABEL_INSERT:
932         case LFUN_PARAGRAPH_GOTO:
933         // FIXME handle non-trivially
934         case LFUN_OUTLINE_UP:
935         case LFUN_OUTLINE_DOWN:
936         case LFUN_OUTLINE_IN:
937         case LFUN_OUTLINE_OUT:
938         case LFUN_ERROR_NEXT:
939         case LFUN_NOTE_NEXT:
940         case LFUN_REFERENCE_NEXT:
941         case LFUN_WORD_FIND:
942         case LFUN_WORD_REPLACE:
943         case LFUN_MARK_OFF:
944         case LFUN_MARK_ON:
945         case LFUN_MARK_TOGGLE:
946         case LFUN_SCREEN_RECENTER:
947         case LFUN_BIBTEX_DATABASE_ADD:
948         case LFUN_BIBTEX_DATABASE_DEL:
949         case LFUN_WORDS_COUNT:
950                 flag.enabled(true);
951                 break;
952
953         case LFUN_LABEL_GOTO: {
954                 flag.enabled(!cmd.argument.empty()
955                     || getInsetByCode<InsetRef>(cursor_, InsetBase::REF_CODE));
956                 break;
957         }
958
959         case LFUN_BOOKMARK_GOTO:
960                 flag.enabled(isSavedPosition(convert<unsigned int>(cmd.argument)));
961                 break;
962         case LFUN_CHANGES_TRACK:
963                 flag.enabled(true);
964                 flag.setOnOff(buffer_->params().tracking_changes);
965                 break;
966
967         case LFUN_CHANGES_OUTPUT: {
968                 OutputParams runparams;
969                 LaTeXFeatures features(*buffer_, buffer_->params(), runparams);
970                 flag.enabled(buffer_ && buffer_->params().tracking_changes
971                         && features.isAvailable("dvipost"));
972                 flag.setOnOff(buffer_->params().output_changes);
973                 break;
974         }
975
976         case LFUN_CHANGES_MERGE:
977         case LFUN_CHANGE_ACCEPT: // what about these two
978         case LFUN_CHANGE_REJECT: // what about these two
979         case LFUN_ALL_CHANGES_ACCEPT:
980         case LFUN_ALL_CHANGES_REJECT:
981                 flag.enabled(buffer_ && buffer_->params().tracking_changes);
982                 break;
983
984         case LFUN_BUFFER_TOGGLE_COMPRESSION: {
985                 flag.setOnOff(buffer_->params().compressed);
986                 break;
987         }
988
989         default:
990                 flag.enabled(false);
991         }
992
993         return flag;
994 }
995
996
997
998 bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
999 {
1000         //lyxerr << BOOST_CURRENT_FUNCTION
1001         //       << [ cmd = " << cmd << "]" << endl;
1002
1003         // Make sure that the cached BufferView is correct.
1004         lyxerr[Debug::ACTION] << BOOST_CURRENT_FUNCTION
1005                 << " action[" << cmd.action << ']'
1006                 << " arg[" << cmd.argument << ']'
1007                 << " x[" << cmd.x << ']'
1008                 << " y[" << cmd.y << ']'
1009                 << " button[" << cmd.button() << ']'
1010                 << endl;
1011
1012         LCursor & cur = cursor_;
1013
1014         switch (cmd.action) {
1015
1016         case LFUN_UNDO:
1017                 if (available()) {
1018                         cur.message(_("Undo"));
1019                         cur.clearSelection();
1020                         if (!textUndo(*bv_))
1021                                 cur.message(_("No further undo information"));
1022                         update();
1023                         switchKeyMap();
1024                 }
1025                 break;
1026
1027         case LFUN_REDO:
1028                 if (available()) {
1029                         cur.message(_("Redo"));
1030                         cur.clearSelection();
1031                         if (!textRedo(*bv_))
1032                                 cur.message(_("No further redo information"));
1033                         update();
1034                         switchKeyMap();
1035                 }
1036                 break;
1037
1038         case LFUN_FILE_INSERT:
1039                 menuInsertLyXFile(cmd.argument);
1040                 break;
1041
1042         case LFUN_FILE_INSERT_ASCII_PARA:
1043                 insertAsciiFile(bv_, cmd.argument, true);
1044                 break;
1045
1046         case LFUN_FILE_INSERT_ASCII:
1047                 insertAsciiFile(bv_, cmd.argument, false);
1048                 break;
1049
1050         case LFUN_FONT_STATE:
1051                 cur.message(cur.currentState());
1052                 break;
1053
1054         case LFUN_BOOKMARK_SAVE:
1055                 savePosition(convert<unsigned int>(cmd.argument));
1056                 break;
1057
1058         case LFUN_BOOKMARK_GOTO:
1059                 restorePosition(convert<unsigned int>(cmd.argument));
1060                 break;
1061
1062         case LFUN_LABEL_GOTO: {
1063                 string label = cmd.argument;
1064                 if (label.empty()) {
1065                         InsetRef * inset =
1066                                 getInsetByCode<InsetRef>(cursor_,
1067                                                          InsetBase::REF_CODE);
1068                         if (inset) {
1069                                 label = inset->getContents();
1070                                 savePosition(0);
1071                         }
1072                 }
1073
1074                 if (!label.empty())
1075                         bv_->gotoLabel(label);
1076                 break;
1077         }
1078
1079         case LFUN_PARAGRAPH_GOTO: {
1080                 int const id = convert<int>(cmd.argument);
1081                 ParIterator par = buffer_->getParFromID(id);
1082                 if (par == buffer_->par_iterator_end()) {
1083                         lyxerr[Debug::INFO] << "No matching paragraph found! ["
1084                                             << id << ']' << endl;
1085                         break;
1086                 } else {
1087                         lyxerr[Debug::INFO] << "Paragraph " << par->id()
1088                                             << " found." << endl;
1089                 }
1090
1091                 // Set the cursor
1092                 bv_->setCursor(makeDocIterator(par, 0));
1093
1094                 update();
1095                 switchKeyMap();
1096                 break;
1097         }
1098
1099         case LFUN_OUTLINE_UP:
1100                 lyx::toc::outline(lyx::toc::Up, cursor_);
1101                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
1102                 updateLabels(*buffer_);
1103                 break;
1104         case LFUN_OUTLINE_DOWN:
1105                 lyx::toc::outline(lyx::toc::Down, cursor_);
1106                 cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
1107                 updateLabels(*buffer_);
1108                 break;
1109         case LFUN_OUTLINE_IN:
1110                 lyx::toc::outline(lyx::toc::In, cursor_);
1111                 updateLabels(*buffer_);
1112                 break;
1113         case LFUN_OUTLINE_OUT:
1114                 lyx::toc::outline(lyx::toc::Out, cursor_);
1115                 updateLabels(*buffer_);
1116                 break;
1117
1118         case LFUN_ERROR_NEXT:
1119                 bv_funcs::gotoInset(bv_, InsetBase::ERROR_CODE, false);
1120                 break;
1121
1122         case LFUN_NOTE_NEXT:
1123                 bv_funcs::gotoInset(bv_, InsetBase::NOTE_CODE, false);
1124                 break;
1125
1126         case LFUN_REFERENCE_NEXT: {
1127                 vector<InsetBase_code> tmp;
1128                 tmp.push_back(InsetBase::LABEL_CODE);
1129                 tmp.push_back(InsetBase::REF_CODE);
1130                 bv_funcs::gotoInset(bv_, tmp, true);
1131                 break;
1132         }
1133
1134         case LFUN_CHANGES_TRACK:
1135                 trackChanges();
1136                 break;
1137
1138         case LFUN_CHANGES_OUTPUT: {
1139                 bool const state = buffer_->params().output_changes;
1140                 buffer_->params().output_changes = !state;
1141                 break;
1142         }
1143
1144         case LFUN_CHANGES_MERGE:
1145                 if (lyx::find::findNextChange(bv_))
1146                         owner_->getDialogs().show("changes");
1147                 break;
1148
1149         case LFUN_ALL_CHANGES_ACCEPT: {
1150                 cursor_.reset(buffer_->inset());
1151 #ifdef WITH_WARNINGS
1152 #warning FIXME changes
1153 #endif
1154                 while (lyx::find::findNextChange(bv_))
1155                         bv_->getLyXText()->acceptChange(cursor_);
1156                 update();
1157                 break;
1158         }
1159
1160         case LFUN_ALL_CHANGES_REJECT: {
1161                 cursor_.reset(buffer_->inset());
1162 #ifdef WITH_WARNINGS
1163 #warning FIXME changes
1164 #endif
1165                 while (lyx::find::findNextChange(bv_))
1166                         bv_->getLyXText()->rejectChange(cursor_);
1167                 break;
1168         }
1169
1170         case LFUN_WORD_FIND:
1171                 lyx::find::find(bv_, cmd);
1172                 break;
1173
1174         case LFUN_WORD_REPLACE:
1175                 lyx::find::replace(bv_, cmd);
1176                 break;
1177
1178         case LFUN_MARK_OFF:
1179                 cur.clearSelection();
1180                 cur.resetAnchor();
1181                 cur.message(N_("Mark off"));
1182                 break;
1183
1184         case LFUN_MARK_ON:
1185                 cur.clearSelection();
1186                 cur.mark() = true;
1187                 cur.resetAnchor();
1188                 cur.message(N_("Mark on"));
1189                 break;
1190
1191         case LFUN_MARK_TOGGLE:
1192                 cur.clearSelection();
1193                 if (cur.mark()) {
1194                         cur.mark() = false;
1195                         cur.message(N_("Mark removed"));
1196                 } else {
1197                         cur.mark() = true;
1198                         cur.message(N_("Mark set"));
1199                 }
1200                 cur.resetAnchor();
1201                 break;
1202
1203         case LFUN_SCREEN_RECENTER:
1204                 center();
1205                 break;
1206
1207         case LFUN_BIBTEX_DATABASE_ADD: {
1208                 LCursor tmpcur = cursor_;
1209                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1210                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1211                                                 InsetBase::BIBTEX_CODE);
1212                 if (inset) {
1213                         if (inset->addDatabase(cmd.argument))
1214                                 buffer_->updateBibfilesCache();
1215                 }
1216                 break;
1217         }
1218
1219         case LFUN_BIBTEX_DATABASE_DEL: {
1220                 LCursor tmpcur = cursor_;
1221                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1222                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1223                                                 InsetBase::BIBTEX_CODE);
1224                 if (inset) {
1225                         if (inset->delDatabase(cmd.argument))
1226                                 buffer_->updateBibfilesCache();
1227                 }
1228                 break;
1229         }
1230
1231         case LFUN_WORDS_COUNT: {
1232                 DocIterator from, to;
1233                 if (cur.selection()) {
1234                         from = cur.selectionBegin();
1235                         to = cur.selectionEnd();
1236                 } else {
1237                         from = doc_iterator_begin(buffer_->inset());
1238                         to = doc_iterator_end(buffer_->inset());
1239                 }
1240                 int const count = countWords(from, to);
1241                 string message;
1242                 if (count != 1) {
1243                         if (cur.selection())
1244                                 message = bformat(_("%1$d words in selection."),
1245                                           count);
1246                                 else
1247                                         message = bformat(_("%1$d words in document."),
1248                                                           count);
1249                 }
1250                 else {
1251                         if (cur.selection())
1252                                 message = _("One word in selection.");
1253                         else
1254                                 message = _("One word in document.");
1255                 }
1256
1257                 Alert::information(_("Count words"), message);
1258         }
1259                 break;
1260
1261         case LFUN_BUFFER_TOGGLE_COMPRESSION:
1262                 // turn compression on/off
1263                 buffer_->params().compressed = !buffer_->params().compressed;
1264                 break;
1265
1266         default:
1267                 return false;
1268         }
1269
1270         return true;
1271 }
1272
1273
1274 void BufferView::Pimpl::updateMetrics(bool singlepar)
1275 {
1276         // Remove old position cache
1277         theCoords.clear();
1278         BufferView & bv = *bv_;
1279         LyXText * const text = bv.text();
1280         lyx::pit_type size = int(text->paragraphs().size());
1281
1282         if (anchor_ref_ > int(text->paragraphs().size() - 1)) {
1283                 anchor_ref_ = int(text->paragraphs().size() - 1);
1284                 offset_ref_ = 0;
1285         }
1286
1287         lyx::pit_type const pit = anchor_ref_;
1288         int pit1 = pit;
1289         int pit2 = pit;
1290         size_t const npit = text->paragraphs().size();
1291
1292         // Rebreak anchor paragraph. In Single Paragraph mode, rebreak only
1293         // the (main text, not inset!) paragraph containing the cursor.
1294         // (if this paragraph contains insets etc., rebreaking will
1295         // recursively descend)
1296         if (!singlepar || pit == cursor_.bottom().pit())
1297                 text->redoParagraph(pit);
1298         int y0 = text->getPar(pit).ascent() - offset_ref_;
1299
1300         // Redo paragraphs above anchor if necessary; again, in Single Par
1301         // mode, only if we encounter the (main text) one having the cursor.
1302         int y1 = y0;
1303         while (y1 > 0 && pit1 > 0) {
1304                 y1 -= text->getPar(pit1).ascent();
1305                 --pit1;
1306                 if (!singlepar || pit1 == cursor_.bottom().pit())
1307                         text->redoParagraph(pit1);
1308                 y1 -= text->getPar(pit1).descent();
1309         }
1310
1311
1312         // Take care of ascent of first line
1313         y1 -= text->getPar(pit1).ascent();
1314
1315         // Normalize anchor for next time
1316         anchor_ref_ = pit1;
1317         offset_ref_ = -y1;
1318
1319         // Grey at the beginning is ugly
1320         if (pit1 == 0 && y1 > 0) {
1321                 y0 -= y1;
1322                 y1 = 0;
1323                 anchor_ref_ = 0;
1324         }
1325
1326         // Redo paragraphs below the anchor if necessary. Single par mode:
1327         // only the one containing the cursor if encountered.
1328         int y2 = y0;
1329         while (y2 < bv.workHeight() && pit2 < int(npit) - 1) {
1330                 y2 += text->getPar(pit2).descent();
1331                 ++pit2;
1332                 if (!singlepar || pit2 == cursor_.bottom().pit())
1333                         text->redoParagraph(pit2);
1334                 y2 += text->getPar(pit2).ascent();
1335         }
1336
1337         // Take care of descent of last line
1338         y2 += text->getPar(pit2).descent();
1339
1340         // The coordinates of all these paragraphs are correct, cache them
1341         int y = y1;
1342         CoordCache::InnerParPosCache & parPos = theCoords.parPos()[text];
1343         for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) {
1344                 Paragraph const & par = text->getPar(pit);
1345                 y += par.ascent();
1346                 parPos[pit] = Point(0, y);
1347                 if (singlepar && pit == cursor_.bottom().pit()) {
1348                         // In Single Paragraph mode, collect here the
1349                         // y1 and y2 of the (one) paragraph the cursor is in
1350                         y1 = y - par.ascent();
1351                         y2 = y + par.descent();
1352                 }
1353                 y += par.descent();
1354         }
1355
1356         if (singlepar) {
1357                 // collect cursor paragraph iter bounds
1358                 pit1 = cursor_.bottom().pit();
1359                 pit2 = cursor_.bottom().pit();
1360         }
1361
1362         lyxerr[Debug::DEBUG]
1363                 << BOOST_CURRENT_FUNCTION
1364                 << " y1: " << y1
1365                 << " y2: " << y2
1366                 << " pit1: " << pit1
1367                 << " pit2: " << pit2
1368                 << " npit: " << npit
1369                 << " singlepar: " << singlepar
1370                 << "size: " << size
1371                 << endl;
1372
1373         metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, singlepar, size);
1374 }