]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
Get rid of FileInfo::getNumberofLinks().
[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 "debug.h"
29 #include "dispatchresult.h"
30 #include "factory.h"
31 #include "FloatList.h"
32 #include "funcrequest.h"
33 #include "FuncStatus.h"
34 #include "gettext.h"
35 #include "intl.h"
36 #include "insetiterator.h"
37 #include "lyx_cb.h" // added for Dispatch functions
38 #include "lyx_main.h"
39 #include "lyxfind.h"
40 #include "lyxfunc.h"
41 #include "lyxtext.h"
42 #include "lyxrc.h"
43 #include "lastfiles.h"
44 #include "metricsinfo.h"
45 #include "paragraph.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "pariterator.h"
49 #include "rowpainter.h"
50 #include "undo.h"
51 #include "vspace.h"
52
53 #include "insets/insetref.h"
54 #include "insets/insettext.h"
55
56 #include "frontends/Alert.h"
57 #include "frontends/Dialogs.h"
58 #include "frontends/FileDialog.h"
59 #include "frontends/LyXView.h"
60 #include "frontends/LyXScreenFactory.h"
61 #include "frontends/screen.h"
62 #include "frontends/WorkArea.h"
63 #include "frontends/WorkAreaFactory.h"
64
65 #include "graphics/Previews.h"
66
67 #include "support/filefilterlist.h"
68 #include "support/filetools.h"
69 #include "support/forkedcontr.h"
70 #include "support/path_defines.h"
71 #include "support/tostr.h"
72 #include "support/types.h"
73
74 #include <boost/bind.hpp>
75
76 #include <functional>
77
78 using lyx::pos_type;
79
80 using lyx::support::AddPath;
81 using lyx::support::bformat;
82 using lyx::support::FileFilterList;
83 using lyx::support::FileSearch;
84 using lyx::support::ForkedcallsController;
85 using lyx::support::IsDirWriteable;
86 using lyx::support::MakeDisplayPath;
87 using lyx::support::MakeAbsPath;
88 using lyx::support::strToUnsignedInt;
89 using lyx::support::system_lyxdir;
90
91 using std::endl;
92 using std::istringstream;
93 using std::make_pair;
94 using std::min;
95 using std::max;
96 using std::string;
97 using std::mem_fun_ref;
98
99
100 extern BufferList bufferlist;
101
102
103 namespace {
104
105 unsigned int const saved_positions_num = 20;
106
107 // All the below connection objects are needed because of a bug in some
108 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
109 // to these connections we avoid a segfault upon startup, and also at exit.
110 // (Lgb)
111
112 boost::signals::connection dispatchcon;
113 boost::signals::connection timecon;
114 boost::signals::connection doccon;
115 boost::signals::connection resizecon;
116 boost::signals::connection kpresscon;
117 boost::signals::connection selectioncon;
118 boost::signals::connection lostcon;
119
120
121 /// Get next inset of this class from current cursor position
122 template <class T>
123 T * getInsetByCode(LCursor & cur, InsetBase::Code code)
124 {
125         T * inset = 0;
126         DocIterator it = cur;
127         if (it.nextInset() &&
128             it.nextInset()->lyxCode() == code) {
129                 inset = static_cast<T*>(it.nextInset());
130         }
131         return inset;
132 }
133
134
135 } // anon namespace
136
137
138 BufferView::Pimpl::Pimpl(BufferView & bv, LyXView * owner,
139                          int width, int height)
140         : bv_(&bv), owner_(owner), buffer_(0), cursor_timeout(400),
141           using_xterm_cursor(false), cursor_(bv) ,
142           anchor_ref_(0), offset_ref_(0)
143 {
144         xsel_cache_.set = false;
145
146         workarea_.reset(WorkAreaFactory::create(*owner_, width, height));
147         screen_.reset(LyXScreenFactory::create(workarea()));
148
149         // Setup the signals
150         doccon = workarea().scrollDocView
151                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
152         resizecon = workarea().workAreaResize
153                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
154         dispatchcon = workarea().dispatch
155                 .connect(boost::bind(&BufferView::Pimpl::workAreaDispatch, this, _1));
156         kpresscon = workarea().workAreaKeyPress
157                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
158         selectioncon = workarea().selectionRequested
159                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
160         lostcon = workarea().selectionLost
161                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
162
163         timecon = cursor_timeout.timeout
164                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
165         cursor_timeout.start();
166         saved_positions.resize(saved_positions_num);
167 }
168
169
170 void BufferView::Pimpl::addError(ErrorItem const & ei)
171 {
172         errorlist_.push_back(ei);
173 }
174
175
176 void BufferView::Pimpl::showReadonly(bool)
177 {
178         owner_->updateWindowTitle();
179         owner_->getDialogs().updateBufferDependent(false);
180 }
181
182
183 void BufferView::Pimpl::connectBuffer(Buffer & buf)
184 {
185         if (errorConnection_.connected())
186                 disconnectBuffer();
187
188         errorConnection_ =
189                 buf.error.connect(
190                         boost::bind(&BufferView::Pimpl::addError, this, _1));
191
192         messageConnection_ =
193                 buf.message.connect(
194                         boost::bind(&LyXView::message, owner_, _1));
195
196         busyConnection_ =
197                 buf.busy.connect(
198                         boost::bind(&LyXView::busy, owner_, _1));
199
200         titleConnection_ =
201                 buf.updateTitles.connect(
202                         boost::bind(&LyXView::updateWindowTitle, owner_));
203
204         timerConnection_ =
205                 buf.resetAutosaveTimers.connect(
206                         boost::bind(&LyXView::resetAutosaveTimer, owner_));
207
208         readonlyConnection_ =
209                 buf.readonly.connect(
210                         boost::bind(&BufferView::Pimpl::showReadonly, this, _1));
211
212         closingConnection_ =
213                 buf.closing.connect(
214                         boost::bind(&BufferView::Pimpl::setBuffer, this, (Buffer *)0));
215 }
216
217
218 void BufferView::Pimpl::disconnectBuffer()
219 {
220         errorConnection_.disconnect();
221         messageConnection_.disconnect();
222         busyConnection_.disconnect();
223         titleConnection_.disconnect();
224         timerConnection_.disconnect();
225         readonlyConnection_.disconnect();
226         closingConnection_.disconnect();
227 }
228
229
230 void BufferView::Pimpl::newFile(string const & filename, string const & tname,
231         bool isNamed)
232 {
233         setBuffer(::newFile(filename, tname, isNamed));
234 }
235
236
237 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
238 {
239         // get absolute path of file and add ".lyx" to the filename if
240         // necessary
241         string s = FileSearch(string(), filename, "lyx");
242
243         bool const found = !s.empty();
244
245         if (!found)
246                 s = filename;
247
248         // file already open?
249         if (bufferlist.exists(s)) {
250                 string const file = MakeDisplayPath(s, 20);
251                 string text = bformat(_("The document %1$s is already "
252                                         "loaded.\n\nDo you want to revert "
253                                         "to the saved version?"), file);
254                 int const ret = Alert::prompt(_("Revert to saved document?"),
255                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
256
257                 if (ret != 0) {
258                         setBuffer(bufferlist.getBuffer(s));
259                         return true;
260                 }
261                 // FIXME: should be LFUN_REVERT
262                 if (!bufferlist.close(bufferlist.getBuffer(s), false))
263                         return false;
264                 // Fall through to new load. (Asger)
265         }
266
267         Buffer * b;
268
269         if (found) {
270                 b = bufferlist.newBuffer(s);
271                 connectBuffer(*b);
272                 if (!::loadLyXFile(b, s)) {
273                         bufferlist.release(b);
274                         return false;
275                 }
276         } else {
277                 string text = bformat(_("The document %1$s does not yet "
278                                         "exist.\n\nDo you want to create "
279                                         "a new document?"), s);
280                 int const ret = Alert::prompt(_("Create new document?"),
281                          text, 0, 1, _("&Create"), _("Cancel"));
282
283                 if (ret == 0)
284                         b = ::newFile(s, string(), true);
285                 else
286                         return false;
287         }
288
289         setBuffer(b);
290         bv_->showErrorList(_("Parse"));
291
292         if (tolastfiles)
293                 LyX::ref().lastfiles().newFile(b->fileName());
294
295         return true;
296 }
297
298
299 WorkArea & BufferView::Pimpl::workarea() const
300 {
301         return *workarea_.get();
302 }
303
304
305 LyXScreen & BufferView::Pimpl::screen() const
306 {
307         return *screen_.get();
308 }
309
310
311 Painter & BufferView::Pimpl::painter() const
312 {
313         return workarea().getPainter();
314 }
315
316
317 void BufferView::Pimpl::setBuffer(Buffer * b)
318 {
319         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
320                             << b << ')' << endl;
321         if (buffer_)
322                 disconnectBuffer();
323
324         // if we are closing current buffer, switch to the first in
325         // buffer list.
326         if (!b) {
327                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
328                 // we are closing the buffer, use the first buffer as current
329                 buffer_ = bufferlist.first();
330                 owner_->getDialogs().hideBufferDependent();
331         } else {
332                 // set current buffer
333                 buffer_ = b;
334         }
335
336         // reset old cursor
337         cursor_ = LCursor(*bv_);
338         anchor_ref_ = 0;
339         offset_ref_ = 0;
340         
341
342         // if we're quitting lyx, don't bother updating stuff
343         if (quitting)
344                 return;
345
346         if (buffer_) {
347                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
348                 connectBuffer(*buffer_);
349
350                 cursor_.push(buffer_->inset());
351                 cursor_.resetAnchor();
352                 buffer_->text().init(bv_);
353                 buffer_->text().setCurrentFont(cursor_);
354
355                 // Buffer-dependent dialogs should be updated or
356                 // hidden. This should go here because some dialogs (eg ToC)
357                 // require bv_->text.
358                 owner_->getDialogs().updateBufferDependent(true);
359         }
360
361         update();
362         updateScrollbar();
363         owner_->updateMenubar();
364         owner_->updateToolbars();
365         owner_->updateLayoutChoice();
366         owner_->updateWindowTitle();
367
368         // This is done after the layout combox has been populated
369         if (buffer_)
370                 owner_->setLayout(cursor_.paragraph().layout()->name());
371
372         if (buffer_ && lyx::graphics::Previews::status() != LyXRC::PREVIEW_OFF)
373                 lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
374 }
375
376
377 void BufferView::Pimpl::resizeCurrentBuffer()
378 {
379         lyxerr[Debug::DEBUG] << "resizeCurrentBuffer" << endl;
380         owner_->busy(true);
381         owner_->message(_("Formatting document..."));
382
383         LyXText * text = bv_->text();
384         if (!text)
385                 return;
386
387         text->init(bv_);
388         update();
389
390         switchKeyMap();
391         owner_->busy(false);
392
393         // reset the "Formatting..." message
394         owner_->clearMessage();
395
396         updateScrollbar();
397 }
398
399
400 void BufferView::Pimpl::updateScrollbar()
401 {
402         if (!bv_->text()) {
403                 lyxerr[Debug::DEBUG] << "no text in updateScrollbar" << endl;
404                 workarea().setScrollbarParams(0, 0, 0);
405                 return;
406         }
407
408         LyXText & t = *bv_->text();
409         if (anchor_ref_ >  int(t.paragraphs().size()) - 1) {
410                 anchor_ref_ = int(t.paragraphs().size()) - 1;
411                 offset_ref_ = 0;
412         }
413             
414         lyxerr[Debug::GUI]
415                 << "Updating scrollbar: height: " << t.paragraphs().size()
416                 << " curr par: " << bv_->cursor().bottom().pit()
417                 << " default height " << defaultRowHeight() << endl;
418
419         //it would be better to fix the scrollbar to understand
420         //values in [0..1] and divide everything by wh
421         int const wh = workarea().workHeight() / 4;
422         int const h = t.getPar(anchor_ref_).height();
423         workarea().setScrollbarParams(t.paragraphs().size() * wh, anchor_ref_ * wh + int(offset_ref_ * wh / float(h)), int (wh * defaultRowHeight() / float(h)));
424 //      workarea().setScrollbarParams(t.paragraphs().size(), anchor_ref_, 1);
425 }
426
427
428 void BufferView::Pimpl::scrollDocView(int value)
429 {
430         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
431
432         if (!buffer_)
433                 return;
434
435         screen().hideCursor();
436
437         int const wh = workarea().workHeight() / 4;
438
439         LyXText & t = *bv_->text();
440
441         float const bar = value / float(wh * t.paragraphs().size());
442
443         anchor_ref_ = int(bar * t.paragraphs().size());
444         t.redoParagraph(anchor_ref_);
445         int const h = t.getPar(anchor_ref_).height();
446         offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
447         lyxerr << "scrolling: " << value << std::endl;
448         update();
449
450         if (!lyxrc.cursor_follows_scrollbar)
451                 return;
452
453         int const height = 2 * defaultRowHeight();
454         int const first = height;
455         int const last = workarea().workHeight() - height;
456         LCursor & cur = bv_->cursor();
457         
458         bv_funcs::CurStatus st = bv_funcs::status(bv_, cur);
459
460         switch (st) {
461         case bv_funcs::CUR_ABOVE:
462                 t.setCursorFromCoordinates(cur, 0, first);
463                 cur.clearSelection();
464                 break;
465         case bv_funcs::CUR_BELOW:
466                 t.setCursorFromCoordinates(cur, 0, last);
467                 cur.clearSelection();
468                 break;
469         case bv_funcs::CUR_INSIDE:
470                 int const y = bv_funcs::getPos(cur).y_;
471                 int const newy = min(last, max(y, first));
472                 if (y != newy) {
473                         cur.reset(bv_->buffer()->inset());
474                         t.setCursorFromCoordinates(cur, 0, newy);
475                 }
476         }
477         owner_->updateLayoutChoice();
478 }
479
480
481 void BufferView::Pimpl::scroll(int lines)
482 {
483 //      if (!buffer_)
484 //              return;
485 //
486 //      LyXText const * t = bv_->text();
487 //      int const line_height = defaultRowHeight();
488 //
489 //      // The new absolute coordinate
490 //      int new_top_y = top_y() + lines * line_height;
491 //
492 //      // Restrict to a valid value
493 //      new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
494 //      new_top_y = std::max(0, new_top_y);
495 //
496 //      scrollDocView(new_top_y);
497 //
498 //      // Update the scrollbar.
499 //      workarea().setScrollbarParams(t->height(), top_y(), defaultRowHeight());
500 }
501
502
503 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
504                                          key_modifier::state state)
505 {
506         bv_->owner()->getLyXFunc().processKeySym(key, state);
507
508         /* This is perhaps a bit of a hack. When we move
509          * around, or type, it's nice to be able to see
510          * the cursor immediately after the keypress. So
511          * we reset the toggle timeout and force the visibility
512          * of the cursor. Note we cannot do this inside
513          * dispatch() itself, because that's called recursively.
514          */
515         if (available()) {
516                 cursor_timeout.restart();
517                 screen().showCursor(*bv_);
518         }
519 }
520
521
522 void BufferView::Pimpl::selectionRequested()
523 {
524         static string sel;
525
526         if (!available())
527                 return;
528
529         LCursor & cur = bv_->cursor();
530
531         if (!cur.selection()) {
532                 xsel_cache_.set = false;
533                 return;
534         }
535
536         if (!xsel_cache_.set ||
537             cur.back() != xsel_cache_.cursor ||
538             cur.anchor_.back() != xsel_cache_.anchor)
539         {
540                 xsel_cache_.cursor = cur.back();
541                 xsel_cache_.anchor = cur.anchor_.back();
542                 xsel_cache_.set = cur.selection();
543                 sel = cur.selectionAsString(false);
544                 if (!sel.empty())
545                         workarea().putClipboard(sel);
546         }
547 }
548
549
550 void BufferView::Pimpl::selectionLost()
551 {
552         if (available()) {
553                 screen().hideCursor();
554                 bv_->cursor().clearSelection();
555                 xsel_cache_.set = false;
556         }
557 }
558
559
560 void BufferView::Pimpl::workAreaResize()
561 {
562         static int work_area_width;
563         static int work_area_height;
564
565         bool const widthChange = workarea().workWidth() != work_area_width;
566         bool const heightChange = workarea().workHeight() != work_area_height;
567
568         // update from work area
569         work_area_width = workarea().workWidth();
570         work_area_height = workarea().workHeight();
571
572         if (buffer_ && widthChange) {
573                 // The visible LyXView need a resize
574                 resizeCurrentBuffer();
575         }
576
577         if (widthChange || heightChange)
578                 update();
579
580         // always make sure that the scrollbar is sane.
581         updateScrollbar();
582         owner_->updateLayoutChoice();
583 }
584
585
586 bool BufferView::Pimpl::fitCursor()
587 {
588         if (bv_funcs::status(bv_, bv_->cursor()) == bv_funcs::CUR_INSIDE) {
589                 int asc, des;
590                 bv_->cursor().getDim(asc, des);
591                 Point p = bv_funcs::getPos(bv_->cursor());
592                 if (p.y_ - asc >= 0 && p.y_ + des < bv_->workHeight())
593                         return false;
594         }
595         bv_->center();
596         return true;
597 }
598
599
600 void BufferView::Pimpl::update(bool fitcursor, bool forceupdate)
601 {
602         lyxerr << "BufferView::Pimpl::update(fc=" << fitcursor << ", fu="
603                << forceupdate << ")  buffer: " << buffer_ << endl;
604         
605         // check needed to survive LyX startup
606         if (buffer_) {
607                 // update macro store
608                 buffer_->buildMacros();
609                 // first drawing step
610
611                 CoordCache backup;
612                 std::swap(theCoords, backup);
613                 //
614                 ViewMetricsInfo vi = metrics();
615
616                 if (fitcursor && fitCursor()) {
617                         forceupdate = true;
618                         vi = metrics();
619                 }
620                 if (forceupdate) {
621                         // second drawing step
622                         screen().redraw(*bv_, vi);
623                 } else
624                         std::swap(theCoords, backup);
625         } else
626                 screen().greyOut();
627
628         // and the scrollbar
629         updateScrollbar();
630         bv_->owner()->view_state_changed();
631 }
632
633
634 // Callback for cursor timer
635 void BufferView::Pimpl::cursorToggle()
636 {
637         if (buffer_) {
638                 screen().toggleCursor(*bv_);
639
640                 // Use this opportunity to deal with any child processes that
641                 // have finished but are waiting to communicate this fact
642                 // to the rest of LyX.
643                 ForkedcallsController & fcc = ForkedcallsController::get();
644                 if (fcc.processesCompleted())
645                         fcc.handleCompletedProcesses();
646         }
647
648         cursor_timeout.restart();
649 }
650
651
652 bool BufferView::Pimpl::available() const
653 {
654         return buffer_ && bv_->text();
655 }
656
657
658 Change const BufferView::Pimpl::getCurrentChange()
659 {
660         if (!bv_->buffer()->params().tracking_changes)
661                 return Change(Change::UNCHANGED);
662
663         LyXText * text = bv_->getLyXText();
664         LCursor & cur = bv_->cursor();
665
666         if (!cur.selection())
667                 return Change(Change::UNCHANGED);
668
669         return text->getPar(cur.selBegin().pit()).
670                         lookupChangeFull(cur.selBegin().pos());
671 }
672
673
674 void BufferView::Pimpl::savePosition(unsigned int i)
675 {
676         if (i >= saved_positions_num)
677                 return;
678         BOOST_ASSERT(bv_->cursor().inTexted());
679         saved_positions[i] = Position(buffer_->fileName(),
680                                       bv_->cursor().paragraph().id(),
681                                       bv_->cursor().pos());
682         if (i > 0)
683                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
684 }
685
686
687 void BufferView::Pimpl::restorePosition(unsigned int i)
688 {
689         if (i >= saved_positions_num)
690                 return;
691
692         string const fname = saved_positions[i].filename;
693
694         bv_->cursor().clearSelection();
695
696         if (fname != buffer_->fileName()) {
697                 Buffer * b = 0;
698                 if (bufferlist.exists(fname))
699                         b = bufferlist.getBuffer(fname);
700                 else {
701                         b = bufferlist.newBuffer(fname);
702                         ::loadLyXFile(b, fname); // don't ask, just load it
703                 }
704                 if (b)
705                         setBuffer(b);
706         }
707
708         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
709         if (par == buffer_->par_iterator_end())
710                 return;
711
712         bv_->text()->setCursor(bv_->cursor(), par.pit(),
713                 min(par->size(), saved_positions[i].par_pos));
714
715         if (i > 0)
716                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
717 }
718
719
720 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
721 {
722         return i < saved_positions_num && !saved_positions[i].filename.empty();
723 }
724
725
726 void BufferView::Pimpl::switchKeyMap()
727 {
728         if (!lyxrc.rtl_support)
729                 return;
730
731         Intl & intl = owner_->getIntl();
732         if (bv_->getLyXText()->real_current_font.isRightToLeft()) {
733                 if (intl.keymap == Intl::PRIMARY)
734                         intl.KeyMapSec();
735         } else {
736                 if (intl.keymap == Intl::SECONDARY)
737                         intl.KeyMapPrim();
738         }
739 }
740
741
742 void BufferView::Pimpl::center()
743 {
744         CursorSlice const & bot = bv_->cursor().bottom();
745         lyx::pit_type const pit = bot.pit();
746         bot.text()->redoParagraph(pit);
747         Paragraph const & par = bot.text()->paragraphs()[pit];
748         anchor_ref_ = pit;
749         offset_ref_ = bv_funcs::coordOffset(bv_->cursor()).y_ + par.ascent()
750                 - workarea().workHeight() / 2;
751 }
752
753
754 void BufferView::Pimpl::stuffClipboard(string const & content) const
755 {
756         workarea().putClipboard(content);
757 }
758
759
760 void BufferView::Pimpl::MenuInsertLyXFile(string const & filenm)
761 {
762         string filename = filenm;
763
764         if (filename.empty()) {
765                 // Launch a file browser
766                 string initpath = lyxrc.document_path;
767
768                 if (available()) {
769                         string const trypath = owner_->buffer()->filePath();
770                         // If directory is writeable, use this as default.
771                         if (IsDirWriteable(trypath))
772                                 initpath = trypath;
773                 }
774
775                 FileDialog fileDlg(_("Select LyX document to insert"),
776                         LFUN_FILE_INSERT,
777                         make_pair(string(_("Documents|#o#O")),
778                                   string(lyxrc.document_path)),
779                         make_pair(string(_("Examples|#E#e")),
780                                   string(AddPath(system_lyxdir(), "examples"))));
781
782                 FileDialog::Result result =
783                         fileDlg.open(initpath,
784                                      FileFilterList(_("LyX Documents (*.lyx)")),
785                                      string());
786
787                 if (result.first == FileDialog::Later)
788                         return;
789
790                 filename = result.second;
791
792                 // check selected filename
793                 if (filename.empty()) {
794                         owner_->message(_("Canceled."));
795                         return;
796                 }
797         }
798
799         // get absolute path of file and add ".lyx" to the filename if
800         // necessary
801         filename = FileSearch(string(), filename, "lyx");
802
803         string const disp_fn = MakeDisplayPath(filename);
804         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
805
806         bv_->cursor().clearSelection();
807         bv_->text()->breakParagraph(bv_->cursor());
808
809         BOOST_ASSERT(bv_->cursor().inTexted());
810
811         string const fname = MakeAbsPath(filename);
812         bool const res = bv_->buffer()->readFile(fname, bv_->cursor().pit());
813         bv_->resize();
814
815         string s = res ? _("Document %1$s inserted.")
816                        : _("Could not insert document %1$s");
817         owner_->message(bformat(s, disp_fn));
818 }
819
820
821 void BufferView::Pimpl::trackChanges()
822 {
823         Buffer * buf = bv_->buffer();
824         bool const tracking = buf->params().tracking_changes;
825
826         if (!tracking) {
827                 for_each(buf->par_iterator_begin(),
828                          buf->par_iterator_end(),
829                          bind(&Paragraph::trackChanges, _1, Change::UNCHANGED));
830                 buf->params().tracking_changes = true;
831
832                 // we cannot allow undos beyond the freeze point
833                 buf->undostack().clear();
834         } else {
835                 update();
836                 bv_->text()->setCursor(bv_->cursor(), 0, 0);
837 #ifdef WITH_WARNINGS
838 #warning changes FIXME
839 #endif
840                 bool found = lyx::find::findNextChange(bv_);
841                 if (found) {
842                         owner_->getDialogs().show("changes");
843                         return;
844                 }
845
846                 for_each(buf->par_iterator_begin(),
847                          buf->par_iterator_end(),
848                          mem_fun_ref(&Paragraph::untrackChanges));
849
850                 buf->params().tracking_changes = false;
851         }
852
853         buf->redostack().clear();
854 }
855
856
857 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
858 {
859         lyxerr << "BufferView::Pimpl::workAreaDispatch: request: "
860           << cmd0 << std::endl;
861         // this is only called for mouse related events including
862         // LFUN_FILE_OPEN generated by drag-and-drop.
863         FuncRequest cmd = cmd0;
864
865         // handle drag&drop
866         if (cmd.action == LFUN_FILE_OPEN) {
867                 owner_->dispatch(cmd);
868                 return true;
869         }
870
871         if (!bv_->buffer())
872                 return false;
873
874         LCursor cur(*bv_);
875         cur.push(bv_->buffer()->inset());
876         cur.selection() = bv_->cursor().selection();
877
878         // Doesn't go through lyxfunc, so we need to update
879         // the layout choice etc. ourselves
880
881         // e.g. Qt mouse press when no buffer
882         if (!available())
883                 return false;
884
885         screen().hideCursor();
886
887         // Either the inset under the cursor or the
888         // surrounding LyXText will handle this event.
889
890         // Build temporary cursor.
891         cmd.y = min(max(cmd.y,-1), bv_->workHeight());
892         InsetBase * inset = bv_->text()->editXY(cur, cmd.x, cmd.y);
893         lyxerr << " * hit inset at tip: " << inset << endl;
894         lyxerr << " * created temp cursor:" << cur << endl;
895
896         // Put anchor at the same position.
897         cur.resetAnchor();
898
899         // Try to dispatch to an non-editable inset near this position
900         // via the temp cursor. If the inset wishes to change the real
901         // cursor it has to do so explicitly by using
902         //  cur.bv().cursor() = cur;  (or similar)
903         if (inset)
904                 inset->dispatch(cur, cmd);
905
906         // Now dispatch to the temporary cursor. If the real cursor should
907         // be modified, the inset's dispatch has to do so explicitly.
908         if (!cur.result().dispatched())
909                 cur.dispatch(cmd);
910
911         if (cur.result().dispatched()) {
912                 // Redraw if requested or necessary.
913                 update(cur.result().update(), cur.result().update());
914         }
915
916         // see workAreaKeyPress
917         cursor_timeout.restart();
918         screen().showCursor(*bv_);
919
920         // skip these when selecting
921         if (cmd.action != LFUN_MOUSE_MOTION) {
922                 owner_->updateLayoutChoice();
923                 owner_->updateToolbars();
924         }
925
926         // slight hack: this is only called currently when we
927         // clicked somewhere, so we force through the display
928         // of the new status here.
929         owner_->clearMessage();
930         return true;
931 }
932
933
934 FuncStatus BufferView::Pimpl::getStatus(FuncRequest const & cmd)
935 {
936         Buffer * buf = bv_->buffer();
937
938         FuncStatus flag;
939
940         switch (cmd.action) {
941
942         case LFUN_UNDO:
943                 flag.enabled(!buf->undostack().empty());
944                 break;
945         case LFUN_REDO:
946                 flag.enabled(!buf->redostack().empty());
947                 break;
948         case LFUN_FILE_INSERT:
949         case LFUN_FILE_INSERT_ASCII_PARA:
950         case LFUN_FILE_INSERT_ASCII:
951         case LFUN_FONT_STATE:
952         case LFUN_INSERT_LABEL:
953         case LFUN_BOOKMARK_SAVE:
954         case LFUN_REF_GOTO:
955         case LFUN_WORD_FIND:
956         case LFUN_WORD_REPLACE:
957         case LFUN_MARK_OFF:
958         case LFUN_MARK_ON:
959         case LFUN_SETMARK:
960         case LFUN_CENTER:
961                 flag.enabled(true);
962                 break;
963                 
964         case LFUN_BOOKMARK_GOTO:
965                 flag.enabled(bv_->isSavedPosition(strToUnsignedInt(cmd.argument)));
966                 break;
967         case LFUN_TRACK_CHANGES:
968                 flag.enabled(true);
969                 flag.setOnOff(buf->params().tracking_changes);
970                 break;
971
972         case LFUN_MERGE_CHANGES:
973         case LFUN_ACCEPT_CHANGE: // what about these two
974         case LFUN_REJECT_CHANGE: // what about these two
975         case LFUN_ACCEPT_ALL_CHANGES:
976         case LFUN_REJECT_ALL_CHANGES:
977                 flag.enabled(buf && buf->params().tracking_changes);
978                 break;
979         default:
980                 flag.enabled(false);
981         }
982
983         return flag;
984 }
985
986
987
988 bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
989 {
990         //lyxerr << "BufferView::Pimpl::dispatch  cmd: " << cmd << std::endl;
991         // Make sure that the cached BufferView is correct.
992         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
993                 << " action[" << cmd.action << ']'
994                 << " arg[" << cmd.argument << ']'
995                 << " x[" << cmd.x << ']'
996                 << " y[" << cmd.y << ']'
997                 << " button[" << cmd.button() << ']'
998                 << endl;
999
1000         LCursor & cur = bv_->cursor();
1001
1002         switch (cmd.action) {
1003
1004         case LFUN_UNDO:
1005                 if (available()) {
1006                         cur.message(_("Undo"));
1007                         cur.clearSelection();
1008                         if (!textUndo(*bv_))
1009                                 cur.message(_("No further undo information"));
1010                         update();
1011                         switchKeyMap();
1012                 }
1013                 break;
1014
1015         case LFUN_REDO:
1016                 if (available()) {
1017                         cur.message(_("Redo"));
1018                         cur.clearSelection();
1019                         if (!textRedo(*bv_))
1020                                 cur.message(_("No further redo information"));
1021                         update();
1022                         switchKeyMap();
1023                 }
1024                 break;
1025
1026         case LFUN_FILE_INSERT:
1027                 MenuInsertLyXFile(cmd.argument);
1028                 break;
1029
1030         case LFUN_FILE_INSERT_ASCII_PARA:
1031                 InsertAsciiFile(bv_, cmd.argument, true);
1032                 break;
1033
1034         case LFUN_FILE_INSERT_ASCII:
1035                 InsertAsciiFile(bv_, cmd.argument, false);
1036                 break;
1037
1038         case LFUN_FONT_STATE:
1039                 cur.message(cur.currentState());
1040                 break;
1041
1042         case LFUN_BOOKMARK_SAVE:
1043                 savePosition(strToUnsignedInt(cmd.argument));
1044                 break;
1045
1046         case LFUN_BOOKMARK_GOTO:
1047                 restorePosition(strToUnsignedInt(cmd.argument));
1048                 break;
1049
1050         case LFUN_REF_GOTO: {
1051                 string label = cmd.argument;
1052                 if (label.empty()) {
1053                         InsetRef * inset =
1054                                 getInsetByCode<InsetRef>(bv_->cursor(),
1055                                                          InsetBase::REF_CODE);
1056                         if (inset) {
1057                                 label = inset->getContents();
1058                                 savePosition(0);
1059                         }
1060                 }
1061
1062                 if (!label.empty())
1063                         bv_->gotoLabel(label);
1064                 break;
1065         }
1066
1067         case LFUN_TRACK_CHANGES:
1068                 trackChanges();
1069                 break;
1070
1071         case LFUN_MERGE_CHANGES:
1072                 owner_->getDialogs().show("changes");
1073                 break;
1074
1075         case LFUN_ACCEPT_ALL_CHANGES: {
1076                 bv_->cursor().reset(bv_->buffer()->inset());
1077 #ifdef WITH_WARNINGS
1078 #warning FIXME changes
1079 #endif
1080                 while (lyx::find::findNextChange(bv_))
1081                         bv_->getLyXText()->acceptChange(bv_->cursor());
1082                 update();
1083                 break;
1084         }
1085
1086         case LFUN_REJECT_ALL_CHANGES: {
1087                 bv_->cursor().reset(bv_->buffer()->inset());
1088 #ifdef WITH_WARNINGS
1089 #warning FIXME changes
1090 #endif
1091                 while (lyx::find::findNextChange(bv_))
1092                         bv_->getLyXText()->rejectChange(bv_->cursor());
1093                 break;
1094         }
1095
1096         case LFUN_WORD_FIND:
1097                 lyx::find::find(bv_, cmd);
1098                 break;
1099
1100         case LFUN_WORD_REPLACE:
1101                 lyx::find::replace(bv_, cmd);
1102                 break;
1103
1104         case LFUN_MARK_OFF:
1105                 cur.clearSelection();
1106                 cur.resetAnchor();
1107                 cur.message(N_("Mark off"));
1108                 break;
1109
1110         case LFUN_MARK_ON:
1111                 cur.clearSelection();
1112                 cur.mark() = true;
1113                 cur.resetAnchor();
1114                 cur.message(N_("Mark on"));
1115                 break;
1116
1117         case LFUN_SETMARK:
1118                 cur.clearSelection();
1119                 if (cur.mark()) {
1120                         cur.mark() = false;
1121                         cur.message(N_("Mark removed"));
1122                 } else {
1123                         cur.mark() = true;
1124                         cur.message(N_("Mark set"));
1125                 }
1126                 cur.resetAnchor();
1127                 break;
1128
1129         case LFUN_CENTER:
1130                 bv_->center();
1131                 break;
1132
1133         default:
1134                 return false;
1135         }
1136
1137         return true;
1138 }
1139
1140
1141 ViewMetricsInfo BufferView::Pimpl::metrics()
1142 {
1143         // remove old position cache
1144         theCoords.clear();
1145         BufferView & bv = *bv_;
1146         LyXText * const text = bv.text();
1147         if (anchor_ref_ > int(text->paragraphs().size() - 1)) {
1148                 anchor_ref_ = int(text->paragraphs().size() - 1);
1149                 offset_ref_ = 0;
1150         }
1151
1152         lyx::pit_type const pit = anchor_ref_;
1153         int pit1 = pit;
1154         int pit2 = pit;
1155         size_t npit = text->paragraphs().size();
1156         lyxerr << "npit: " << npit << " pit1: " << pit1 
1157                 << " pit2: " << pit2 << endl;
1158
1159         // rebreak anchor par   
1160         text->redoParagraph(pit);
1161         int y0 = text->getPar(pit1).ascent() - offset_ref_;
1162         
1163         // redo paragraphs above cursor if necessary
1164         int y1 = y0;
1165         while (y1 > 0 && pit1 > 0) {
1166                 y1 -= text->getPar(pit1).ascent();
1167                 --pit1;
1168                 text->redoParagraph(pit1);
1169                 y1 -= text->getPar(pit1).descent();
1170         }
1171
1172         
1173         // take care of ascent of first line
1174         y1 -= text->getPar(pit1).ascent();
1175
1176         //normalize anchor for next time
1177         anchor_ref_ = pit1;
1178         offset_ref_ = -y1;
1179
1180         // grey at the beginning is ugly
1181         if (pit1 == 0 && y1 > 0) {
1182                 y0 -= y1;
1183                 y1 = 0;
1184                 anchor_ref_ = 0;
1185         }
1186         
1187         // redo paragraphs below cursor if necessary
1188         int y2 = y0;
1189         while (y2 < bv.workHeight() && pit2 < int(npit) - 1) {
1190                 y2 += text->getPar(pit2).descent();
1191                 ++pit2;
1192                 text->redoParagraph(pit2);
1193                 y2 += text->getPar(pit2).ascent();
1194         }
1195
1196         // take care of descent of last line 
1197         y2 += text->getPar(pit2).descent();
1198
1199         // the coordinates of all these paragraphs are correct, cache them
1200         int y = y1;
1201         for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) {
1202                 y += text->getPar(pit).ascent();
1203                 theCoords.pars_[text][pit] = Point(0, y);
1204                 y += text->getPar(pit).descent();
1205         }
1206         
1207         lyxerr << "bv:metrics:  y1: " << y1 << " y2: " << y2 << endl;
1208         return ViewMetricsInfo(pit1, pit2, y1, y2);
1209 }