]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
723cbad73c9f41915d70bdbd9f913562ec51142b
[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 Braustein
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 "bufferlist.h"
23 #include "buffer.h"
24 #include "buffer_funcs.h"
25 #include "bufferview_funcs.h"
26 #include "lfuns.h"
27 #include "debug.h"
28 #include "factory.h"
29 #include "FloatList.h"
30 #include "funcrequest.h"
31 #include "gettext.h"
32 #include "intl.h"
33 #include "iterators.h"
34 #include "Lsstream.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 "ParagraphParameters.h"
44 #include "TextCache.h"
45 #include "undo_funcs.h"
46
47 #include "insets/insetfloatlist.h"
48 #include "insets/insetgraphics.h"
49 #include "insets/insetinclude.h"
50 #include "insets/insetref.h"
51 #include "insets/insettext.h"
52
53 #include "frontends/Alert.h"
54 #include "frontends/Dialogs.h"
55 #include "frontends/FileDialog.h"
56 #include "frontends/LyXView.h"
57 #include "frontends/LyXScreenFactory.h"
58 #include "frontends/mouse_state.h"
59 #include "frontends/screen.h"
60 #include "frontends/WorkArea.h"
61 #include "frontends/WorkAreaFactory.h"
62
63 #include "mathed/formulabase.h"
64
65 #include "graphics/Previews.h"
66
67 #include "support/LAssert.h"
68 #include "support/tostr.h"
69 #include "support/filetools.h"
70 #include "support/path_defines.h"
71
72 #include <boost/bind.hpp>
73 #include <boost/signals/connection.hpp>
74
75 #include <unistd.h>
76 #include <sys/wait.h>
77
78
79 using std::vector;
80 using std::find_if;
81 using std::find;
82 using std::pair;
83 using std::endl;
84 using std::make_pair;
85 using std::min;
86
87 using lyx::pos_type;
88 using namespace lyx::support;
89 using namespace bv_funcs;
90
91 extern BufferList bufferlist;
92
93
94 namespace {
95
96 unsigned int const saved_positions_num = 20;
97
98 // All the below connection objects are needed because of a bug in some
99 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
100 // to these connections we avoid a segfault upon startup, and also at exit.
101 // (Lgb)
102
103 boost::signals::connection dispatchcon;
104 boost::signals::connection timecon;
105 boost::signals::connection doccon;
106 boost::signals::connection resizecon;
107 boost::signals::connection kpresscon;
108 boost::signals::connection selectioncon;
109 boost::signals::connection lostcon;
110
111
112 } // anon namespace
113
114
115 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
116              int xpos, int ypos, int width, int height)
117         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
118           using_xterm_cursor(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_ = buf.error.connect(boost::bind(&BufferView::Pimpl::addError, this, _1));
163         messageConnection_ = buf.message.connect(boost::bind(&LyXView::message, owner_, _1));
164         busyConnection_ = buf.busy.connect(boost::bind(&LyXView::busy, owner_, _1));
165         titleConnection_ = buf.updateTitles.connect(boost::bind(&LyXView::updateWindowTitle, owner_));
166         timerConnection_ = buf.resetAutosaveTimers.connect(boost::bind(&LyXView::resetAutosaveTimer, owner_));
167         readonlyConnection_ = buf.readonly.connect(boost::bind(&BufferView::Pimpl::showReadonly, this, _1));
168         closingConnection_ = buf.closing.connect(boost::bind(&BufferView::Pimpl::buffer, this, (Buffer *)0));
169 }
170
171
172 void BufferView::Pimpl::disconnectBuffer()
173 {
174         errorConnection_.disconnect();
175         messageConnection_.disconnect();
176         busyConnection_.disconnect();
177         titleConnection_.disconnect();
178         timerConnection_.disconnect();
179         readonlyConnection_.disconnect();
180         closingConnection_.disconnect();
181 }
182
183
184 bool BufferView::Pimpl::newFile(string const & filename,
185                                 string const & tname,
186                                 bool isNamed)
187 {
188         Buffer * b = ::newFile(filename, tname, isNamed);
189         buffer(b);
190         return true;
191 }
192
193
194 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
195 {
196         // get absolute path of file and add ".lyx" to the filename if
197         // necessary
198         string s = FileSearch(string(), filename, "lyx");
199
200         bool const found = !s.empty();
201
202         if (!found)
203                 s = filename;
204
205         // file already open?
206         if (bufferlist.exists(s)) {
207                 string const file = MakeDisplayPath(s, 20);
208                 string text = bformat(_("The document %1$s is already "
209                                         "loaded.\n\nDo you want to revert "
210                                         "to the saved version?"), file);
211                 int const ret = Alert::prompt(_("Revert to saved document?"),
212                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
213
214                 if (ret != 0) {
215                         buffer(bufferlist.getBuffer(s));
216                         return true;
217                 } else {
218                         // FIXME: should be LFUN_REVERT
219                         if (!bufferlist.close(bufferlist.getBuffer(s), false))
220                                 return false;
221                         // Fall through to new load. (Asger)
222                 }
223         }
224
225         Buffer * b;
226
227         if (found) {
228                 b = bufferlist.newBuffer(s);
229                 connectBuffer(*b);
230                 if (!::loadLyXFile(b, s)) {
231                         bufferlist.release(b);
232                         return false;
233                 }
234         } else {
235                 string text = bformat(_("The document %1$s does not yet "
236                                         "exist.\n\nDo you want to create "
237                                         "a new document?"), s);
238                 int const ret = Alert::prompt(_("Create new document?"),
239                          text, 0, 1, _("&Create"), _("Cancel"));
240
241                 if (ret == 0)
242                         b = ::newFile(s, string(), true);
243                 else
244                         return false;
245         }
246
247         buffer(b);
248         bv_->showErrorList(_("Parse"));
249
250         if (tolastfiles)
251                 lastfiles->newFile(b->fileName());
252
253         return true;
254 }
255
256
257 WorkArea & BufferView::Pimpl::workarea() const
258 {
259         return *workarea_.get();
260 }
261
262
263 LyXScreen & BufferView::Pimpl::screen() const
264 {
265         return *screen_.get();
266 }
267
268
269 Painter & BufferView::Pimpl::painter() const
270 {
271         return workarea().getPainter();
272 }
273
274
275 void BufferView::Pimpl::buffer(Buffer * b)
276 {
277         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
278                             << b << ')' << endl;
279         if (buffer_) {
280                 disconnectBuffer();
281                 // Put the old text into the TextCache, but
282                 // only if the buffer is still loaded.
283                 // Also set the owner of the test to 0
284                 //              bv_->text->owner(0);
285                 textcache.add(buffer_, workarea().workWidth(), bv_->text);
286                 if (lyxerr.debugging())
287                         textcache.show(lyxerr, "BufferView::buffer");
288
289                 bv_->text = 0;
290         }
291
292         // set current buffer
293         buffer_ = b;
294
295         // if we're quitting lyx, don't bother updating stuff
296         if (quitting)
297                 return;
298
299         // if we are closing the buffer, use the first buffer as current
300         if (!buffer_)
301                 buffer_ = bufferlist.first();
302
303         if (buffer_) {
304                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
305                 connectBuffer(*buffer_);
306
307                 // If we don't have a text object for this, we make one
308                 if (bv_->text == 0)
309                         resizeCurrentBuffer();
310
311                 // FIXME: needed when ?
312                 bv_->text->top_y(screen().topCursorVisible(bv_->text));
313
314                 // Buffer-dependent dialogs should be updated or
315                 // hidden. This should go here because some dialogs (eg ToC)
316                 // require bv_->text.
317                 owner_->getDialogs().updateBufferDependent(true);
318         } else {
319                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
320                 owner_->getDialogs().hideBufferDependent();
321
322                 // Also remove all remaining text's from the testcache.
323                 // (there should not be any!) (if there is any it is a
324                 // bug!)
325                 if (lyxerr.debugging())
326                         textcache.show(lyxerr, "buffer delete all");
327                 textcache.clear();
328         }
329
330         update();
331         updateScrollbar();
332         owner_->updateMenubar();
333         owner_->updateToolbar();
334         owner_->updateLayoutChoice();
335         owner_->updateWindowTitle();
336
337         if (buffer_) {
338                 // Don't forget to update the Layout
339                 string const layoutname =
340                         bv_->text->cursor.par()->layout()->name();
341                 owner_->setLayout(layoutname);
342         }
343
344         if (lyx::graphics::Previews::activated() && buffer_)
345                 lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
346 }
347
348
349 bool BufferView::Pimpl::fitCursor()
350 {
351         bool ret;
352
353         if (bv_->theLockingInset()) {
354                 bv_->theLockingInset()->fitInsetCursor(bv_);
355                 ret = true;
356         } else {
357                 ret = screen().fitCursor(bv_->text, bv_);
358         }
359
360         //dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
361
362         // We need to always update, in case we did a
363         // paste and we stayed anchored to a row, but
364         // the actual height of the doc changed ...
365         updateScrollbar();
366         return ret;
367 }
368
369
370 void BufferView::Pimpl::redoCurrentBuffer()
371 {
372         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
373         if (buffer_ && bv_->text) {
374                 resizeCurrentBuffer();
375                 updateScrollbar();
376                 owner_->updateLayoutChoice();
377                 update();
378         }
379 }
380
381
382 void BufferView::Pimpl::resizeCurrentBuffer()
383 {
384         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
385
386         ParagraphList::iterator par;
387         ParagraphList::iterator selstartpar;
388         ParagraphList::iterator selendpar;
389         UpdatableInset * the_locking_inset = 0;
390
391         pos_type pos = 0;
392         pos_type selstartpos = 0;
393         pos_type selendpos = 0;
394         bool selection = false;
395         bool mark_set  = false;
396
397         owner_->busy(true);
398
399         owner_->message(_("Formatting document..."));
400
401         if (bv_->text) {
402                 par = bv_->text->cursor.par();
403                 pos = bv_->text->cursor.pos();
404                 selstartpar = bv_->text->selection.start.par();
405                 selstartpos = bv_->text->selection.start.pos();
406                 selendpar = bv_->text->selection.end.par();
407                 selendpos = bv_->text->selection.end.pos();
408                 selection = bv_->text->selection.set();
409                 mark_set = bv_->text->selection.mark();
410                 the_locking_inset = bv_->theLockingInset();
411                 bv_->text->fullRebreak();
412                 update();
413         } else {
414                 lyxerr << "text not available!" << endl;
415                 // See if we have a text in TextCache that fits
416                 // the new buffer_ with the correct width.
417                 bv_->text = textcache.findFit(buffer_, workarea().workWidth());
418                 if (bv_->text) {
419                         lyxerr << "text in cache!" << endl;
420                         if (lyxerr.debugging()) {
421                                 lyxerr << "Found a LyXText that fits:" << endl;
422                                 textcache.show(lyxerr, make_pair(buffer_, make_pair(workarea().workWidth(), bv_->text)));
423                         }
424                         // Set the owner of the newly found text
425                         //      bv_->text->owner(bv_);
426                         if (lyxerr.debugging())
427                                 textcache.show(lyxerr, "resizeCurrentBuffer");
428                 } else {
429                         lyxerr << "no text in cache!" << endl;
430                         bv_->text = new LyXText(bv_, 0, false, bv_->buffer()->paragraphs);
431                         bv_->text->init(bv_);
432                 }
433
434                 par = bv_->text->ownerParagraphs().end();
435                 selstartpar = bv_->text->ownerParagraphs().end();
436                 selendpar = bv_->text->ownerParagraphs().end();
437         }
438
439 #warning does not help much
440         bv_->text->redoParagraphs(bv_->text->ownerParagraphs().begin(),
441                 bv_->text->ownerParagraphs().end());
442
443         if (par != bv_->text->ownerParagraphs().end()) {
444                 bv_->text->selection.set(true);
445                 // At this point just to avoid the Delete-Empty-Paragraph-
446                 // Mechanism when setting the cursor.
447                 bv_->text->selection.mark(mark_set);
448                 if (selection) {
449                         bv_->text->setCursor(selstartpar, selstartpos);
450                         bv_->text->selection.cursor = bv_->text->cursor;
451                         bv_->text->setCursor(selendpar, selendpos);
452                         bv_->text->setSelection();
453                         bv_->text->setCursor(par, pos);
454                 } else {
455                         bv_->text->setCursor(par, pos);
456                         bv_->text->selection.cursor = bv_->text->cursor;
457                         bv_->text->selection.set(false);
458                 }
459                 // remake the inset locking
460                 bv_->theLockingInset(the_locking_inset);
461         }
462
463         bv_->text->top_y(screen().topCursorVisible(bv_->text));
464
465         switchKeyMap();
466         owner_->busy(false);
467
468         // reset the "Formatting..." message
469         owner_->clearMessage();
470
471         updateScrollbar();
472 }
473
474
475 void BufferView::Pimpl::updateScrollbar()
476 {
477         if (!bv_->text) {
478                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
479                 workarea().setScrollbarParams(0, 0, 0);
480                 return;
481         }
482
483         LyXText const & t = *bv_->text;
484
485         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", top_y() "
486                 << t.top_y() << ", default height " << defaultRowHeight() << endl;
487
488         workarea().setScrollbarParams(t.height, t.top_y(), defaultRowHeight());
489 }
490
491
492 void BufferView::Pimpl::scrollDocView(int value)
493 {
494         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
495
496         if (!buffer_)
497                 return;
498
499         screen().hideCursor();
500
501         bv_->text->top_y(value);
502         screen().redraw(*bv_);
503
504         if (!lyxrc.cursor_follows_scrollbar)
505                 return;
506
507         int const height = defaultRowHeight();
508         int const first = int((bv_->text->top_y() + height));
509         int const last = int((bv_->text->top_y() + workarea().workHeight() - height));
510
511         LyXText * text = bv_->text;
512         if (text->cursor.y() < first)
513                 text->setCursorFromCoordinates(0, first);
514         else if (text->cursor.y() > last)
515                 text->setCursorFromCoordinates(0, last);
516
517         owner_->updateLayoutChoice();
518 }
519
520
521 void BufferView::Pimpl::scroll(int lines)
522 {
523         if (!buffer_) {
524                 return;
525         }
526
527         LyXText const * t = bv_->text;
528         int const line_height = defaultRowHeight();
529
530         // The new absolute coordinate
531         int new_top_y = t->top_y() + lines * line_height;
532
533         // Restrict to a valid value
534         new_top_y = std::min(t->height - 4 * line_height, new_top_y);
535         new_top_y = std::max(0, new_top_y);
536
537         scrollDocView(new_top_y);
538
539         // Update the scrollbar.
540         workarea().setScrollbarParams(t->height, t->top_y(), defaultRowHeight());
541 }
542
543
544 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
545                                          key_modifier::state state)
546 {
547         bv_->owner()->getLyXFunc().processKeySym(key, state);
548
549         /* This is perhaps a bit of a hack. When we move
550          * around, or type, it's nice to be able to see
551          * the cursor immediately after the keypress. So
552          * we reset the toggle timeout and force the visibility
553          * of the cursor. Note we cannot do this inside
554          * dispatch() itself, because that's called recursively.
555          */
556         if (available()) {
557                 cursor_timeout.restart();
558                 screen().showCursor(*bv_);
559         }
560 }
561
562
563 void BufferView::Pimpl::selectionRequested()
564 {
565         static string sel;
566
567         if (!available())
568                 return;
569
570         LyXText * text = bv_->getLyXText();
571
572         if (text->selection.set() &&
573                 (!bv_->text->xsel_cache.set() ||
574                  text->selection.start != bv_->text->xsel_cache.start ||
575                  text->selection.end != bv_->text->xsel_cache.end))
576         {
577                 bv_->text->xsel_cache = text->selection;
578                 sel = text->selectionAsString(bv_->buffer(), false);
579         } else if (!text->selection.set()) {
580                 sel = string();
581                 bv_->text->xsel_cache.set(false);
582         }
583         if (!sel.empty()) {
584                 workarea().putClipboard(sel);
585         }
586 }
587
588
589 void BufferView::Pimpl::selectionLost()
590 {
591         if (available()) {
592                 screen().hideCursor();
593                 bv_->getLyXText()->clearSelection();
594                 bv_->text->xsel_cache.set(false);
595         }
596 }
597
598
599 void BufferView::Pimpl::workAreaResize()
600 {
601         static int work_area_width;
602         static int work_area_height;
603
604         bool const widthChange = workarea().workWidth() != work_area_width;
605         bool const heightChange = workarea().workHeight() != work_area_height;
606
607         // update from work area
608         work_area_width = workarea().workWidth();
609         work_area_height = workarea().workHeight();
610
611         if (buffer_ != 0) {
612                 if (widthChange) {
613                         // The visible LyXView need a resize
614                         resizeCurrentBuffer();
615
616                         // Remove all texts from the textcache
617                         // This is not _really_ what we want to do. What
618                         // we really want to do is to delete in textcache
619                         // that does not have a BufferView with matching
620                         // width, but as long as we have only one BufferView
621                         // deleting all gives the same result.
622                         if (lyxerr.debugging())
623                                 textcache.show(lyxerr, "Expose delete all");
624                         textcache.clear();
625                 }
626         }
627
628         if (widthChange || heightChange)
629                 update();
630
631         // always make sure that the scrollbar is sane.
632         updateScrollbar();
633         owner_->updateLayoutChoice();
634 }
635
636
637 void BufferView::Pimpl::update()
638 {
639         lyxerr << "BufferView::update()" << endl;
640         // fix cursor coordinate cache in case something went wrong
641         if (bv_->getLyXText()) {
642                 // check needed to survive LyX startup
643                 bv_->getLyXText()->redoCursor();
644                 fitCursor();
645         }
646         screen().redraw(*bv_);
647 }
648
649
650 // Callback for cursor timer
651 void BufferView::Pimpl::cursorToggle()
652 {
653         if (!buffer_) {
654                 cursor_timeout.restart();
655                 return;
656         }
657
658         screen().toggleCursor(*bv_);
659
660         cursor_timeout.restart();
661 }
662
663
664 bool BufferView::Pimpl::available() const
665 {
666         if (buffer_ && bv_->text)
667                 return true;
668         return false;
669 }
670
671
672 Change const BufferView::Pimpl::getCurrentChange()
673 {
674         if (!bv_->buffer()->params.tracking_changes)
675                 return Change(Change::UNCHANGED);
676
677         LyXText * text = bv_->getLyXText();
678
679         if (!text->selection.set())
680                 return Change(Change::UNCHANGED);
681
682         LyXCursor const & cur = text->selection.start;
683         return cur.par()->lookupChangeFull(cur.pos());
684 }
685
686
687 void BufferView::Pimpl::beforeChange(LyXText * text)
688 {
689         text->clearSelection();
690 }
691
692
693 void BufferView::Pimpl::savePosition(unsigned int i)
694 {
695         if (i >= saved_positions_num)
696                 return;
697         saved_positions[i] = Position(buffer_->fileName(),
698                                       bv_->text->cursor.par()->id(),
699                                       bv_->text->cursor.pos());
700         if (i > 0)
701                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
702 }
703
704
705 void BufferView::Pimpl::restorePosition(unsigned int i)
706 {
707         if (i >= saved_positions_num)
708                 return;
709
710         string const fname = saved_positions[i].filename;
711
712         beforeChange(bv_->text);
713
714         if (fname != buffer_->fileName()) {
715                 Buffer * b;
716                 if (bufferlist.exists(fname))
717                         b = bufferlist.getBuffer(fname);
718                 else {
719                         b = bufferlist.newBuffer(fname);
720                         ::loadLyXFile(b, fname); // don't ask, just load it
721                 }
722                 if (b != 0)
723                         buffer(b);
724         }
725
726         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
727         if (par == buffer_->par_iterator_end())
728                 return;
729
730         bv_->text->setCursor(par.pit(),
731                              min(par->size(), saved_positions[i].par_pos));
732
733         update();
734         if (i > 0)
735                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
736 }
737
738
739 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
740 {
741         if (i >= saved_positions_num)
742                 return false;
743
744         return !saved_positions[i].filename.empty();
745 }
746
747
748 void BufferView::Pimpl::switchKeyMap()
749 {
750         if (!lyxrc.rtl_support)
751                 return;
752
753         LyXText * text = bv_->getLyXText();
754         if (text->real_current_font.isRightToLeft()
755             && !(bv_->theLockingInset()
756                  && bv_->theLockingInset()->lyxCode() == InsetOld::ERT_CODE))
757         {
758                 if (owner_->getIntl().keymap == Intl::PRIMARY)
759                         owner_->getIntl().KeyMapSec();
760         } else {
761                 if (owner_->getIntl().keymap == Intl::SECONDARY)
762                         owner_->getIntl().KeyMapPrim();
763         }
764 }
765
766
767 void BufferView::Pimpl::insetUnlock()
768 {
769         if (bv_->theLockingInset()) {
770                 bv_->theLockingInset()->insetUnlock(bv_);
771                 bv_->theLockingInset(0);
772                 finishUndo();
773         }
774 }
775
776
777 void BufferView::Pimpl::center()
778 {
779         LyXText * text = bv_->text;
780
781         beforeChange(text);
782         int const half_height = workarea().workHeight() / 2;
783         int new_y = 0;
784
785         if (text->cursor.y() > half_height)
786                 new_y = text->cursor.y() - half_height;
787
788         // FIXME: look at this comment again ...
789
790         // FIXME: can we do this w/o calling screen directly ?
791         // This updates top_y() but means the fitCursor() call
792         // from the update(FITCUR) doesn't realise that we might
793         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
794         // the scrollbar to be updated as it should, so we have
795         // to do it manually. Any operation that does a center()
796         // and also might have moved top_y() must make sure to call
797         // updateScrollbar() currently. Never mind that this is a
798         // pretty obfuscated way of updating t->top_y()
799         text->top_y(new_y);
800         //screen().draw();
801         update();
802 }
803
804
805 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
806 {
807         workarea().putClipboard(stuff);
808 }
809
810
811 /*
812  * Dispatch functions for actions which can be valid for BufferView->text
813  * and/or InsetText->text!!!
814  */
815
816
817 InsetOld * BufferView::Pimpl::getInsetByCode(InsetOld::Code code)
818 {
819 #if 0
820         LyXCursor cursor = bv_->getLyXText()->cursor;
821         Buffer::inset_iterator it =
822                 find_if(Buffer::inset_iterator(
823                         cursor.par(), cursor.pos()),
824                         buffer_->inset_iterator_end(),
825                         lyx::compare_memfun(&Inset::lyxCode, code));
826         return it != buffer_->inset_iterator_end() ? (*it) : 0;
827 #else
828         // Ok, this is a little bit too brute force but it
829         // should work for now. Better infrastructure is coming. (Lgb)
830
831         Buffer * b = bv_->buffer();
832         LyXCursor cursor = bv_->getLyXText()->cursor;
833
834         Buffer::inset_iterator beg = b->inset_iterator_begin();
835         Buffer::inset_iterator end = b->inset_iterator_end();
836
837         bool cursor_par_seen = false;
838
839         for (; beg != end; ++beg) {
840                 if (beg.getPar() == cursor.par()) {
841                         cursor_par_seen = true;
842                 }
843                 if (cursor_par_seen) {
844                         if (beg.getPar() == cursor.par()
845                             && beg.getPos() >= cursor.pos()) {
846                                 break;
847                         } else if (beg.getPar() != cursor.par()) {
848                                 break;
849                         }
850                 }
851
852         }
853         if (beg != end) {
854                 // Now find the first inset that matches code.
855                 for (; beg != end; ++beg) {
856                         if (beg->lyxCode() == code) {
857                                 return &(*beg);
858                         }
859                 }
860         }
861         return 0;
862 #endif
863 }
864
865
866 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
867 {
868         string filename = filen;
869
870         if (filename.empty()) {
871                 // Launch a file browser
872                 string initpath = lyxrc.document_path;
873
874                 if (available()) {
875                         string const trypath = owner_->buffer()->filePath();
876                         // If directory is writeable, use this as default.
877                         if (IsDirWriteable(trypath))
878                                 initpath = trypath;
879                 }
880
881                 FileDialog fileDlg(_("Select LyX document to insert"),
882                         LFUN_FILE_INSERT,
883                         make_pair(string(_("Documents|#o#O")),
884                                   string(lyxrc.document_path)),
885                         make_pair(string(_("Examples|#E#e")),
886                                   string(AddPath(system_lyxdir(), "examples"))));
887
888                 FileDialog::Result result =
889                         fileDlg.open(initpath,
890                                        _("*.lyx| LyX Documents (*.lyx)"));
891
892                 if (result.first == FileDialog::Later)
893                         return;
894
895                 filename = result.second;
896
897                 // check selected filename
898                 if (filename.empty()) {
899                         owner_->message(_("Canceled."));
900                         return;
901                 }
902         }
903
904         // get absolute path of file and add ".lyx" to the filename if
905         // necessary
906         filename = FileSearch(string(), filename, "lyx");
907
908         string const disp_fn = MakeDisplayPath(filename);
909         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
910         if (bv_->insertLyXFile(filename))
911                 owner_->message(bformat(_("Document %1$s inserted."),
912                                         disp_fn));
913         else
914                 owner_->message(bformat(_("Could not insert document %1$s"),
915                                         disp_fn));
916 }
917
918
919 void BufferView::Pimpl::trackChanges()
920 {
921         Buffer * buf(bv_->buffer());
922         bool const tracking(buf->params.tracking_changes);
923
924         if (!tracking) {
925                 ParIterator const end = buf->par_iterator_end();
926                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
927                         it->trackChanges();
928                 buf->params.tracking_changes = true;
929
930                 // we cannot allow undos beyond the freeze point
931                 buf->undostack.clear();
932         } else {
933                 update();
934                 bv_->text->setCursor(buf->paragraphs.begin(), 0);
935 #warning changes FIXME
936                 //moveCursorUpdate(false);
937
938                 bool found = lyx::find::findNextChange(bv_);
939                 if (found) {
940                         owner_->getDialogs().show("changes");
941                         return;
942                 }
943
944                 ParIterator const end = buf->par_iterator_end();
945                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
946                         it->untrackChanges();
947                 buf->params.tracking_changes = false;
948         }
949
950         buf->redostack.clear();
951 }
952
953
954 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & ev)
955 {
956         switch (ev.action) {
957         case LFUN_MOUSE_PRESS:
958         case LFUN_MOUSE_MOTION:
959         case LFUN_MOUSE_RELEASE:
960         case LFUN_MOUSE_DOUBLE:
961         case LFUN_MOUSE_TRIPLE:
962         {
963                 // We pass those directly to the Bufferview, since
964                 // otherwise selection handling breaks down
965
966                 // Doesn't go through lyxfunc, so we need to update
967                 // the layout choice etc. ourselves
968
969                 // e.g. Qt mouse press when no buffer
970                 if (!available())
971                         return false;
972
973                 screen().hideCursor();
974
975                 bool const res = dispatch(ev);
976                 
977                 // see workAreaKeyPress
978                 cursor_timeout.restart();
979                 screen().showCursor(*bv_);
980
981                 // FIXME: we should skip these when selecting
982                 owner_->updateLayoutChoice();
983                 owner_->updateToolbar();
984                 fitCursor();
985
986                 // slight hack: this is only called currently when we
987                 // clicked somewhere, so we force through the display
988                 // of the new status here.
989                 owner_->clearMessage();
990
991                 return res;
992         }
993         default:
994                 owner_->dispatch(ev);
995                 return true;
996         }
997 }
998
999
1000 bool BufferView::Pimpl::dispatch(FuncRequest const & ev_in)
1001 {
1002         // Make sure that the cached BufferView is correct.
1003         FuncRequest ev = ev_in;
1004         ev.setView(bv_);
1005
1006         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
1007                 << " action[" << ev.action << ']'
1008                 << " arg[" << ev.argument << ']'
1009                 << " x[" << ev.x << ']'
1010                 << " y[" << ev.y << ']'
1011                 << " button[" << ev.button() << ']'
1012                 << endl;
1013
1014         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1015
1016         switch (ev.action) {
1017
1018         case LFUN_SCROLL_INSET:
1019                 // this is not handled here as this function is only active
1020                 // if we have a locking_inset and that one is (or contains)
1021                 // a tabular-inset
1022                 break;
1023
1024         case LFUN_FILE_INSERT:
1025                 MenuInsertLyXFile(ev.argument);
1026                 break;
1027
1028         case LFUN_FILE_INSERT_ASCII_PARA:
1029                 InsertAsciiFile(bv_, ev.argument, true);
1030                 break;
1031
1032         case LFUN_FILE_INSERT_ASCII:
1033                 InsertAsciiFile(bv_, ev.argument, false);
1034                 break;
1035
1036         case LFUN_LANGUAGE:
1037                 lang(bv_, ev.argument);
1038                 switchKeyMap();
1039                 owner_->view_state_changed();
1040                 break;
1041
1042         case LFUN_EMPH:
1043                 emph(bv_);
1044                 owner_->view_state_changed();
1045                 break;
1046
1047         case LFUN_BOLD:
1048                 bold(bv_);
1049                 owner_->view_state_changed();
1050                 break;
1051
1052         case LFUN_NOUN:
1053                 noun(bv_);
1054                 owner_->view_state_changed();
1055                 break;
1056
1057         case LFUN_CODE:
1058                 code(bv_);
1059                 owner_->view_state_changed();
1060                 break;
1061
1062         case LFUN_SANS:
1063                 sans(bv_);
1064                 owner_->view_state_changed();
1065                 break;
1066
1067         case LFUN_ROMAN:
1068                 roman(bv_);
1069                 owner_->view_state_changed();
1070                 break;
1071
1072         case LFUN_DEFAULT:
1073                 styleReset(bv_);
1074                 owner_->view_state_changed();
1075                 break;
1076
1077         case LFUN_UNDERLINE:
1078                 underline(bv_);
1079                 owner_->view_state_changed();
1080                 break;
1081
1082         case LFUN_FONT_SIZE:
1083                 fontSize(bv_, ev.argument);
1084                 owner_->view_state_changed();
1085                 break;
1086
1087         case LFUN_FONT_STATE:
1088                 owner_->getLyXFunc().setMessage(currentState(bv_));
1089                 break;
1090
1091         case LFUN_INSERT_LABEL: {
1092                 // Try and generate a valid label
1093                 string const contents = ev.argument.empty() ?
1094                         getPossibleLabel(*bv_) : ev.argument;
1095                 InsetCommandParams icp("label", contents);
1096                 string data = InsetCommandMailer::params2string("label", icp);
1097                 owner_->getDialogs().show("label", data, 0);
1098         }
1099         break;
1100
1101         case LFUN_BOOKMARK_SAVE:
1102                 savePosition(strToUnsignedInt(ev.argument));
1103                 break;
1104
1105         case LFUN_BOOKMARK_GOTO:
1106                 restorePosition(strToUnsignedInt(ev.argument));
1107                 break;
1108
1109         case LFUN_REF_GOTO: {
1110                 string label = ev.argument;
1111                 if (label.empty()) {
1112                         InsetRef * inset =
1113                                 static_cast<InsetRef*>(getInsetByCode(InsetOld::REF_CODE));
1114                         if (inset) {
1115                                 label = inset->getContents();
1116                                 savePosition(0);
1117                         }
1118                 }
1119
1120                 if (!label.empty())
1121                         bv_->gotoLabel(label);
1122         }
1123         break;
1124
1125         // --- accented characters ---------------------------
1126
1127         case LFUN_UMLAUT:
1128         case LFUN_CIRCUMFLEX:
1129         case LFUN_GRAVE:
1130         case LFUN_ACUTE:
1131         case LFUN_TILDE:
1132         case LFUN_CEDILLA:
1133         case LFUN_MACRON:
1134         case LFUN_DOT:
1135         case LFUN_UNDERDOT:
1136         case LFUN_UNDERBAR:
1137         case LFUN_CARON:
1138         case LFUN_SPECIAL_CARON:
1139         case LFUN_BREVE:
1140         case LFUN_TIE:
1141         case LFUN_HUNG_UMLAUT:
1142         case LFUN_CIRCLE:
1143         case LFUN_OGONEK:
1144                 if (ev.argument.empty()) {
1145                         // As always...
1146                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1147                 } else {
1148                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1149                         owner_->getIntl().getTransManager()
1150                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1151                         update();
1152                 }
1153                 break;
1154
1155         case LFUN_MATH_MACRO:
1156         case LFUN_MATH_DELIM:
1157         case LFUN_INSERT_MATRIX:
1158         case LFUN_INSERT_MATH:
1159         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1160         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1161         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1162                 mathDispatch(ev);
1163                 break;
1164
1165         case LFUN_INSET_APPLY: {
1166                 string const name = ev.getArg(0);
1167
1168                 InsetBase * inset = owner_->getDialogs().getOpenInset(name);
1169                 if (inset) {
1170                         // This works both for 'original' and 'mathed' insets.
1171                         // Note that the localDispatch performs updateInset
1172                         // also.
1173                         FuncRequest fr(bv_, LFUN_INSET_MODIFY, ev.argument);
1174                         inset->localDispatch(fr);
1175                 } else {
1176                         FuncRequest fr(bv_, LFUN_INSET_INSERT, ev.argument);
1177                         dispatch(fr);
1178                 }
1179         }
1180         break;
1181
1182         case LFUN_INSET_INSERT: {
1183                 InsetOld * inset = createInset(ev);
1184                 if (inset && insertInset(inset)) {
1185                         updateInset();
1186
1187                         string const name = ev.getArg(0);
1188                         if (name == "bibitem") {
1189                                 // We need to do a redraw because the maximum
1190                                 // InsetBibitem width could have changed
1191 #warning check whether the update() is needed at all
1192                                 bv_->update();
1193                         }
1194                 } else {
1195                         delete inset;
1196                 }
1197         }
1198         break;
1199
1200         case LFUN_FLOAT_LIST:
1201                 if (tclass.floats().typeExist(ev.argument)) {
1202                         InsetOld * inset = new InsetFloatList(ev.argument);
1203                         if (!insertInset(inset, tclass.defaultLayoutName()))
1204                                 delete inset;
1205                 } else {
1206                         lyxerr << "Non-existent float type: "
1207                                << ev.argument << endl;
1208                 }
1209                 break;
1210
1211         case LFUN_LAYOUT_PARAGRAPH: {
1212                 string data;
1213                 params2string(*bv_->getLyXText()->cursor.par(), data);
1214
1215                 data = "show\n" + data;
1216                 bv_->owner()->getDialogs().show("paragraph", data);
1217                 break;
1218         }
1219
1220         case LFUN_PARAGRAPH_UPDATE: {
1221                 if (!bv_->owner()->getDialogs().visible("paragraph"))
1222                         break;
1223                 Paragraph const & par = *bv_->getLyXText()->cursor.par();
1224
1225                 string data;
1226                 params2string(par, data);
1227
1228                 // Will the paragraph accept changes from the dialog?
1229                 InsetOld * const inset = par.inInset();
1230                 bool const accept =
1231                         !(inset && inset->forceDefaultParagraphs(inset));
1232
1233                 data = "update " + tostr(accept) + '\n' + data;
1234                 bv_->owner()->getDialogs().update("paragraph", data);
1235                 break;
1236         }
1237
1238         case LFUN_PARAGRAPH_APPLY:
1239                 setParagraphParams(*bv_, ev.argument);
1240                 break;
1241
1242         case LFUN_THESAURUS_ENTRY:
1243         {
1244                 string arg = ev.argument;
1245
1246                 if (arg.empty()) {
1247                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1248                                                                    false);
1249
1250                         // FIXME
1251                         if (arg.size() > 100 || arg.empty()) {
1252                                 // Get word or selection
1253                                 bv_->getLyXText()->selectWordWhenUnderCursor(lyx::WHOLE_WORD);
1254                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1255                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1256                         }
1257                 }
1258
1259                 bv_->owner()->getDialogs().show("thesaurus", arg);
1260         }
1261                 break;
1262
1263         case LFUN_TRACK_CHANGES:
1264                 trackChanges();
1265                 break;
1266
1267         case LFUN_MERGE_CHANGES:
1268                 owner_->getDialogs().show("changes");
1269                 break;
1270
1271         case LFUN_ACCEPT_ALL_CHANGES: {
1272                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1273 #warning FIXME changes
1274                 //moveCursorUpdate(false);
1275
1276                 while (lyx::find::findNextChange(bv_))
1277                         bv_->getLyXText()->acceptChange();
1278
1279                 update();
1280                 break;
1281         }
1282
1283         case LFUN_REJECT_ALL_CHANGES: {
1284                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1285 #warning FIXME changes
1286                 //moveCursorUpdate(false);
1287
1288                 while (lyx::find::findNextChange(bv_))
1289                         bv_->getLyXText()->rejectChange();
1290
1291                 update();
1292                 break;
1293         }
1294
1295         case LFUN_ACCEPT_CHANGE: {
1296                 bv_->getLyXText()->acceptChange();
1297                 update();
1298                 break;
1299         }
1300
1301         case LFUN_REJECT_CHANGE: {
1302                 bv_->getLyXText()->rejectChange();
1303                 update();
1304                 break;
1305         }
1306
1307         case LFUN_UNKNOWN_ACTION:
1308                 ev.errorMessage(N_("Unknown function!"));
1309                 break;
1310
1311         default:
1312                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1313         } // end of switch
1314
1315         return true;
1316 }
1317
1318
1319 bool BufferView::Pimpl::insertInset(InsetOld * inset, string const & lout)
1320 {
1321         // if we are in a locking inset we should try to insert the
1322         // inset there otherwise this is a illegal function now
1323         if (bv_->theLockingInset()) {
1324                 if (bv_->theLockingInset()->insetAllowed(inset))
1325                         return bv_->theLockingInset()->insertInset(bv_, inset);
1326                 return false;
1327         }
1328
1329         // not quite sure if we want this...
1330         recordUndo(bv_, Undo::ATOMIC);
1331         freezeUndo();
1332
1333         beforeChange(bv_->text);
1334         if (!lout.empty()) {
1335                 bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1336
1337                 if (!bv_->text->cursor.par()->empty()) {
1338                         bv_->text->cursorLeft(bv_);
1339                         bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1340                 }
1341
1342                 string lres = lout;
1343                 LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1344                 bool hasLayout = tclass.hasLayout(lres);
1345                 string lay = tclass.defaultLayoutName();
1346
1347                 if (hasLayout != false) {
1348                         // layout found
1349                         lay = lres;
1350                 } else {
1351                         // layout not fount using default
1352                         lay = tclass.defaultLayoutName();
1353                 }
1354
1355                 bv_->text->setLayout(lay);
1356
1357                 bv_->text->setParagraph(0, 0,
1358                                    0, 0,
1359                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1360                                    Spacing(),
1361                                    LYX_ALIGN_LAYOUT,
1362                                    string(),
1363                                    0);
1364         }
1365
1366         bv_->text->insertInset(inset);
1367         update();
1368
1369         unFreezeUndo();
1370         return true;
1371 }
1372
1373
1374 void BufferView::Pimpl::updateInset()
1375 {
1376         if (!available())
1377                 return;
1378
1379         // this should not be needed, but it is...
1380         //bv_->text->redoParagraph(bv_->text->cursor.par());
1381         //bv_->text->fullRebreak();
1382
1383         update();
1384         updateScrollbar();
1385 }