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