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