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