]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
(Rob Lahaye) Remove redundant "LyX:" from title.
[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().draw();
503
504         if (!lyxrc.cursor_follows_scrollbar)
505                 return;
506
507         LyXText * vbt = bv_->text;
508
509         int const height = defaultRowHeight();
510         int const first = static_cast<int>((bv_->text->top_y() + height));
511         int const last = static_cast<int>((bv_->text->top_y() + workarea().workHeight() - height));
512
513         if (vbt->cursor.y() < first)
514                 vbt->setCursorFromCoordinates(0, first);
515         else if (vbt->cursor.y() > last)
516                 vbt->setCursorFromCoordinates(0, last);
517
518         owner_->updateLayoutChoice();
519 }
520
521
522 void BufferView::Pimpl::scroll(int lines)
523 {
524         if (!buffer_) {
525                 return;
526         }
527
528         LyXText const * t = bv_->text;
529         int const line_height = defaultRowHeight();
530
531         // The new absolute coordinate
532         int new_top_y = t->top_y() + lines * line_height;
533
534         // Restrict to a valid value
535         new_top_y = std::min(t->height - 4 * line_height, new_top_y);
536         new_top_y = std::max(0, new_top_y);
537
538         scrollDocView(new_top_y);
539
540         // Update the scrollbar.
541         workarea().setScrollbarParams(t->height, t->top_y(), defaultRowHeight());
542 }
543
544
545 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
546                                          key_modifier::state state)
547 {
548         bv_->owner()->getLyXFunc().processKeySym(key, state);
549
550         /* This is perhaps a bit of a hack. When we move
551          * around, or type, it's nice to be able to see
552          * the cursor immediately after the keypress. So
553          * we reset the toggle timeout and force the visibility
554          * of the cursor. Note we cannot do this inside
555          * dispatch() itself, because that's called recursively.
556          */
557         if (available()) {
558                 cursor_timeout.restart();
559                 screen().showCursor(*bv_);
560         }
561 }
562
563
564 void BufferView::Pimpl::selectionRequested()
565 {
566         static string sel;
567
568         if (!available())
569                 return;
570
571         LyXText * text = bv_->getLyXText();
572
573         if (text->selection.set() &&
574                 (!bv_->text->xsel_cache.set() ||
575                  text->selection.start != bv_->text->xsel_cache.start ||
576                  text->selection.end != bv_->text->xsel_cache.end))
577         {
578                 bv_->text->xsel_cache = text->selection;
579                 sel = text->selectionAsString(bv_->buffer(), false);
580         } else if (!text->selection.set()) {
581                 sel = string();
582                 bv_->text->xsel_cache.set(false);
583         }
584         if (!sel.empty()) {
585                 workarea().putClipboard(sel);
586         }
587 }
588
589
590 void BufferView::Pimpl::selectionLost()
591 {
592         if (available()) {
593                 screen().hideCursor();
594                 bv_->getLyXText()->clearSelection();
595                 bv_->text->xsel_cache.set(false);
596         }
597 }
598
599
600 void BufferView::Pimpl::workAreaResize()
601 {
602         static int work_area_width;
603         static int work_area_height;
604
605         bool const widthChange = workarea().workWidth() != work_area_width;
606         bool const heightChange = workarea().workHeight() != work_area_height;
607
608         // update from work area
609         work_area_width = workarea().workWidth();
610         work_area_height = workarea().workHeight();
611
612         if (buffer_ != 0) {
613                 if (widthChange) {
614                         // The visible LyXView need a resize
615                         resizeCurrentBuffer();
616
617                         // Remove all texts from the textcache
618                         // This is not _really_ what we want to do. What
619                         // we really want to do is to delete in textcache
620                         // that does not have a BufferView with matching
621                         // width, but as long as we have only one BufferView
622                         // deleting all gives the same result.
623                         if (lyxerr.debugging())
624                                 textcache.show(lyxerr, "Expose delete all");
625                         textcache.clear();
626                 }
627         }
628
629         if (widthChange || heightChange)
630                 update();
631
632         // always make sure that the scrollbar is sane.
633         updateScrollbar();
634         owner_->updateLayoutChoice();
635 }
636
637
638 void BufferView::Pimpl::update()
639 {
640         lyxerr << "BufferView::update()" << endl;
641         // fix cursor coordinate cache in case something went wrong
642         if (bv_->getLyXText()) {
643                 // check needed to survive LyX startup
644                 bv_->getLyXText()->redoCursor();
645                 fitCursor();
646         }
647         screen().redraw(*bv_);
648 }
649
650
651 // Callback for cursor timer
652 void BufferView::Pimpl::cursorToggle()
653 {
654         if (!buffer_) {
655                 cursor_timeout.restart();
656                 return;
657         }
658
659         screen().toggleCursor(*bv_);
660
661         cursor_timeout.restart();
662 }
663
664
665 bool BufferView::Pimpl::available() const
666 {
667         if (buffer_ && bv_->text)
668                 return true;
669         return false;
670 }
671
672
673 Change const BufferView::Pimpl::getCurrentChange()
674 {
675         if (!bv_->buffer()->params.tracking_changes)
676                 return Change(Change::UNCHANGED);
677
678         LyXText * text = bv_->getLyXText();
679
680         if (!text->selection.set())
681                 return Change(Change::UNCHANGED);
682
683         LyXCursor const & cur = text->selection.start;
684         return cur.par()->lookupChangeFull(cur.pos());
685 }
686
687
688 void BufferView::Pimpl::beforeChange(LyXText * text)
689 {
690         text->clearSelection();
691 }
692
693
694 void BufferView::Pimpl::savePosition(unsigned int i)
695 {
696         if (i >= saved_positions_num)
697                 return;
698         saved_positions[i] = Position(buffer_->fileName(),
699                                       bv_->text->cursor.par()->id(),
700                                       bv_->text->cursor.pos());
701         if (i > 0)
702                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
703 }
704
705
706 void BufferView::Pimpl::restorePosition(unsigned int i)
707 {
708         if (i >= saved_positions_num)
709                 return;
710
711         string const fname = saved_positions[i].filename;
712
713         beforeChange(bv_->text);
714
715         if (fname != buffer_->fileName()) {
716                 Buffer * b;
717                 if (bufferlist.exists(fname))
718                         b = bufferlist.getBuffer(fname);
719                 else {
720                         b = bufferlist.newBuffer(fname);
721                         ::loadLyXFile(b, fname); // don't ask, just load it
722                 }
723                 if (b != 0)
724                         buffer(b);
725         }
726
727         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
728         if (par == buffer_->par_iterator_end())
729                 return;
730
731         bv_->text->setCursor(par.pit(),
732                              min(par->size(), saved_positions[i].par_pos));
733
734         update();
735         if (i > 0)
736                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
737 }
738
739
740 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
741 {
742         if (i >= saved_positions_num)
743                 return false;
744
745         return !saved_positions[i].filename.empty();
746 }
747
748
749 void BufferView::Pimpl::switchKeyMap()
750 {
751         if (!lyxrc.rtl_support)
752                 return;
753
754         LyXText * text = bv_->getLyXText();
755         if (text->real_current_font.isRightToLeft()
756             && !(bv_->theLockingInset()
757                  && bv_->theLockingInset()->lyxCode() == InsetOld::ERT_CODE))
758         {
759                 if (owner_->getIntl().keymap == Intl::PRIMARY)
760                         owner_->getIntl().KeyMapSec();
761         } else {
762                 if (owner_->getIntl().keymap == Intl::SECONDARY)
763                         owner_->getIntl().KeyMapPrim();
764         }
765 }
766
767
768 void BufferView::Pimpl::insetUnlock()
769 {
770         if (bv_->theLockingInset()) {
771                 bv_->theLockingInset()->insetUnlock(bv_);
772                 bv_->theLockingInset(0);
773                 finishUndo();
774         }
775 }
776
777
778 void BufferView::Pimpl::center()
779 {
780         LyXText * text = bv_->text;
781
782         beforeChange(text);
783         int const half_height = workarea().workHeight() / 2;
784         int new_y = 0;
785
786         if (text->cursor.y() > half_height)
787                 new_y = text->cursor.y() - half_height;
788
789         // FIXME: look at this comment again ...
790
791         // FIXME: can we do this w/o calling screen directly ?
792         // This updates top_y() but means the fitCursor() call
793         // from the update(FITCUR) doesn't realise that we might
794         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
795         // the scrollbar to be updated as it should, so we have
796         // to do it manually. Any operation that does a center()
797         // and also might have moved top_y() must make sure to call
798         // updateScrollbar() currently. Never mind that this is a
799         // pretty obfuscated way of updating t->top_y()
800         text->top_y(new_y);
801         //screen().draw();
802         update();
803 }
804
805
806 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
807 {
808         workarea().putClipboard(stuff);
809 }
810
811
812 /*
813  * Dispatch functions for actions which can be valid for BufferView->text
814  * and/or InsetText->text!!!
815  */
816
817
818 InsetOld * BufferView::Pimpl::getInsetByCode(InsetOld::Code code)
819 {
820 #if 0
821         LyXCursor cursor = bv_->getLyXText()->cursor;
822         Buffer::inset_iterator it =
823                 find_if(Buffer::inset_iterator(
824                         cursor.par(), cursor.pos()),
825                         buffer_->inset_iterator_end(),
826                         lyx::compare_memfun(&Inset::lyxCode, code));
827         return it != buffer_->inset_iterator_end() ? (*it) : 0;
828 #else
829         // Ok, this is a little bit too brute force but it
830         // should work for now. Better infrastructure is coming. (Lgb)
831
832         Buffer * b = bv_->buffer();
833         LyXCursor cursor = bv_->getLyXText()->cursor;
834
835         Buffer::inset_iterator beg = b->inset_iterator_begin();
836         Buffer::inset_iterator end = b->inset_iterator_end();
837
838         bool cursor_par_seen = false;
839
840         for (; beg != end; ++beg) {
841                 if (beg.getPar() == cursor.par()) {
842                         cursor_par_seen = true;
843                 }
844                 if (cursor_par_seen) {
845                         if (beg.getPar() == cursor.par()
846                             && beg.getPos() >= cursor.pos()) {
847                                 break;
848                         } else if (beg.getPar() != cursor.par()) {
849                                 break;
850                         }
851                 }
852
853         }
854         if (beg != end) {
855                 // Now find the first inset that matches code.
856                 for (; beg != end; ++beg) {
857                         if (beg->lyxCode() == code) {
858                                 return &(*beg);
859                         }
860                 }
861         }
862         return 0;
863 #endif
864 }
865
866
867 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
868 {
869         string filename = filen;
870
871         if (filename.empty()) {
872                 // Launch a file browser
873                 string initpath = lyxrc.document_path;
874
875                 if (available()) {
876                         string const trypath = owner_->buffer()->filePath();
877                         // If directory is writeable, use this as default.
878                         if (IsDirWriteable(trypath))
879                                 initpath = trypath;
880                 }
881
882                 FileDialog fileDlg(_("Select LyX document to insert"),
883                         LFUN_FILE_INSERT,
884                         make_pair(string(_("Documents|#o#O")),
885                                   string(lyxrc.document_path)),
886                         make_pair(string(_("Examples|#E#e")),
887                                   string(AddPath(system_lyxdir(), "examples"))));
888
889                 FileDialog::Result result =
890                         fileDlg.open(initpath,
891                                        _("*.lyx| LyX Documents (*.lyx)"));
892
893                 if (result.first == FileDialog::Later)
894                         return;
895
896                 filename = result.second;
897
898                 // check selected filename
899                 if (filename.empty()) {
900                         owner_->message(_("Canceled."));
901                         return;
902                 }
903         }
904
905         // get absolute path of file and add ".lyx" to the filename if
906         // necessary
907         filename = FileSearch(string(), filename, "lyx");
908
909         string const disp_fn = MakeDisplayPath(filename);
910         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
911         if (bv_->insertLyXFile(filename))
912                 owner_->message(bformat(_("Document %1$s inserted."),
913                                         disp_fn));
914         else
915                 owner_->message(bformat(_("Could not insert document %1$s"),
916                                         disp_fn));
917 }
918
919
920 void BufferView::Pimpl::trackChanges()
921 {
922         Buffer * buf(bv_->buffer());
923         bool const tracking(buf->params.tracking_changes);
924
925         if (!tracking) {
926                 ParIterator const end = buf->par_iterator_end();
927                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
928                         it->trackChanges();
929                 buf->params.tracking_changes = true;
930
931                 // we cannot allow undos beyond the freeze point
932                 buf->undostack.clear();
933         } else {
934                 update();
935                 bv_->text->setCursor(buf->paragraphs.begin(), 0);
936 #warning changes FIXME
937                 //moveCursorUpdate(false);
938
939                 bool found = lyx::find::findNextChange(bv_);
940                 if (found) {
941                         owner_->getDialogs().show("changes");
942                         return;
943                 }
944
945                 ParIterator const end = buf->par_iterator_end();
946                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
947                         it->untrackChanges();
948                 buf->params.tracking_changes = false;
949         }
950
951         buf->redostack.clear();
952 }
953
954
955 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & ev)
956 {
957         switch (ev.action) {
958         case LFUN_MOUSE_PRESS:
959         case LFUN_MOUSE_MOTION:
960         case LFUN_MOUSE_RELEASE:
961         case LFUN_MOUSE_DOUBLE:
962         case LFUN_MOUSE_TRIPLE:
963         {
964                 // We pass those directly to the Bufferview, since
965                 // otherwise selection handling breaks down
966
967                 // Doesn't go through lyxfunc, so we need to update
968                 // the layout choice etc. ourselves
969
970                 // e.g. Qt mouse press when no buffer
971                 if (!available())
972                         return false;
973
974                 screen().hideCursor();
975
976                 bool const res = dispatch(ev);
977                 
978                 // see workAreaKeyPress
979                 cursor_timeout.restart();
980                 screen().showCursor(*bv_);
981
982                 // FIXME: we should skip these when selecting
983                 owner_->updateLayoutChoice();
984                 owner_->updateToolbar();
985                 fitCursor();
986
987                 // slight hack: this is only called currently when we
988                 // clicked somewhere, so we force through the display
989                 // of the new status here.
990                 owner_->clearMessage();
991
992                 return res;
993         }
994         default:
995                 owner_->dispatch(ev);
996                 return true;
997         }
998 }
999
1000
1001 bool BufferView::Pimpl::dispatch(FuncRequest const & ev_in)
1002 {
1003         // Make sure that the cached BufferView is correct.
1004         FuncRequest ev = ev_in;
1005         ev.setView(bv_);
1006
1007         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
1008                 << " action[" << ev.action << ']'
1009                 << " arg[" << ev.argument << ']'
1010                 << " x[" << ev.x << ']'
1011                 << " y[" << ev.y << ']'
1012                 << " button[" << ev.button() << ']'
1013                 << endl;
1014
1015         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1016
1017         switch (ev.action) {
1018
1019         case LFUN_SCROLL_INSET:
1020                 // this is not handled here as this function is only active
1021                 // if we have a locking_inset and that one is (or contains)
1022                 // a tabular-inset
1023                 break;
1024
1025         case LFUN_FILE_INSERT:
1026                 MenuInsertLyXFile(ev.argument);
1027                 break;
1028
1029         case LFUN_FILE_INSERT_ASCII_PARA:
1030                 InsertAsciiFile(bv_, ev.argument, true);
1031                 break;
1032
1033         case LFUN_FILE_INSERT_ASCII:
1034                 InsertAsciiFile(bv_, ev.argument, false);
1035                 break;
1036
1037         case LFUN_LANGUAGE:
1038                 lang(bv_, ev.argument);
1039                 switchKeyMap();
1040                 owner_->view_state_changed();
1041                 break;
1042
1043         case LFUN_EMPH:
1044                 emph(bv_);
1045                 owner_->view_state_changed();
1046                 break;
1047
1048         case LFUN_BOLD:
1049                 bold(bv_);
1050                 owner_->view_state_changed();
1051                 break;
1052
1053         case LFUN_NOUN:
1054                 noun(bv_);
1055                 owner_->view_state_changed();
1056                 break;
1057
1058         case LFUN_CODE:
1059                 code(bv_);
1060                 owner_->view_state_changed();
1061                 break;
1062
1063         case LFUN_SANS:
1064                 sans(bv_);
1065                 owner_->view_state_changed();
1066                 break;
1067
1068         case LFUN_ROMAN:
1069                 roman(bv_);
1070                 owner_->view_state_changed();
1071                 break;
1072
1073         case LFUN_DEFAULT:
1074                 styleReset(bv_);
1075                 owner_->view_state_changed();
1076                 break;
1077
1078         case LFUN_UNDERLINE:
1079                 underline(bv_);
1080                 owner_->view_state_changed();
1081                 break;
1082
1083         case LFUN_FONT_SIZE:
1084                 fontSize(bv_, ev.argument);
1085                 owner_->view_state_changed();
1086                 break;
1087
1088         case LFUN_FONT_STATE:
1089                 owner_->getLyXFunc().setMessage(currentState(bv_));
1090                 break;
1091
1092         case LFUN_INSERT_LABEL: {
1093                 // Try and generate a valid label
1094                 string const contents = ev.argument.empty() ?
1095                         getPossibleLabel(*bv_) : ev.argument;
1096                 InsetCommandParams icp("label", contents);
1097                 string data = InsetCommandMailer::params2string("label", icp);
1098                 owner_->getDialogs().show("label", data, 0);
1099         }
1100         break;
1101
1102         case LFUN_BOOKMARK_SAVE:
1103                 savePosition(strToUnsignedInt(ev.argument));
1104                 break;
1105
1106         case LFUN_BOOKMARK_GOTO:
1107                 restorePosition(strToUnsignedInt(ev.argument));
1108                 break;
1109
1110         case LFUN_REF_GOTO: {
1111                 string label = ev.argument;
1112                 if (label.empty()) {
1113                         InsetRef * inset =
1114                                 static_cast<InsetRef*>(getInsetByCode(InsetOld::REF_CODE));
1115                         if (inset) {
1116                                 label = inset->getContents();
1117                                 savePosition(0);
1118                         }
1119                 }
1120
1121                 if (!label.empty())
1122                         bv_->gotoLabel(label);
1123         }
1124         break;
1125
1126         // --- accented characters ---------------------------
1127
1128         case LFUN_UMLAUT:
1129         case LFUN_CIRCUMFLEX:
1130         case LFUN_GRAVE:
1131         case LFUN_ACUTE:
1132         case LFUN_TILDE:
1133         case LFUN_CEDILLA:
1134         case LFUN_MACRON:
1135         case LFUN_DOT:
1136         case LFUN_UNDERDOT:
1137         case LFUN_UNDERBAR:
1138         case LFUN_CARON:
1139         case LFUN_SPECIAL_CARON:
1140         case LFUN_BREVE:
1141         case LFUN_TIE:
1142         case LFUN_HUNG_UMLAUT:
1143         case LFUN_CIRCLE:
1144         case LFUN_OGONEK:
1145                 if (ev.argument.empty()) {
1146                         // As always...
1147                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1148                 } else {
1149                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1150                         owner_->getIntl().getTransManager()
1151                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1152                         update();
1153                 }
1154                 break;
1155
1156         case LFUN_MATH_MACRO:
1157         case LFUN_MATH_DELIM:
1158         case LFUN_INSERT_MATRIX:
1159         case LFUN_INSERT_MATH:
1160         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1161         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1162         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1163                 mathDispatch(ev);
1164                 break;
1165
1166         case LFUN_INSET_APPLY: {
1167                 string const name = ev.getArg(0);
1168
1169                 InsetBase * inset = owner_->getDialogs().getOpenInset(name);
1170                 if (inset) {
1171                         // This works both for 'original' and 'mathed' insets.
1172                         // Note that the localDispatch performs updateInset
1173                         // also.
1174                         FuncRequest fr(bv_, LFUN_INSET_MODIFY, ev.argument);
1175                         inset->localDispatch(fr);
1176                 } else {
1177                         FuncRequest fr(bv_, LFUN_INSET_INSERT, ev.argument);
1178                         dispatch(fr);
1179                 }
1180         }
1181         break;
1182
1183         case LFUN_INSET_INSERT: {
1184                 InsetOld * inset = createInset(ev);
1185                 if (inset && insertInset(inset)) {
1186                         updateInset();
1187
1188                         string const name = ev.getArg(0);
1189                         if (name == "bibitem") {
1190                                 // We need to do a redraw because the maximum
1191                                 // InsetBibitem width could have changed
1192 #warning check whether the update() is needed at all
1193                                 bv_->update();
1194                         }
1195                 } else {
1196                         delete inset;
1197                 }
1198         }
1199         break;
1200
1201         case LFUN_FLOAT_LIST:
1202                 if (tclass.floats().typeExist(ev.argument)) {
1203                         InsetOld * inset = new InsetFloatList(ev.argument);
1204                         if (!insertInset(inset, tclass.defaultLayoutName()))
1205                                 delete inset;
1206                 } else {
1207                         lyxerr << "Non-existent float type: "
1208                                << ev.argument << endl;
1209                 }
1210                 break;
1211
1212         case LFUN_LAYOUT_PARAGRAPH: {
1213                 string data;
1214                 params2string(*bv_->getLyXText()->cursor.par(), data);
1215
1216                 data = "show\n" + data;
1217                 bv_->owner()->getDialogs().show("paragraph", data);
1218                 break;
1219         }
1220
1221         case LFUN_PARAGRAPH_UPDATE: {
1222                 if (!bv_->owner()->getDialogs().visible("paragraph"))
1223                         break;
1224                 Paragraph const & par = *bv_->getLyXText()->cursor.par();
1225
1226                 string data;
1227                 params2string(par, data);
1228
1229                 // Will the paragraph accept changes from the dialog?
1230                 InsetOld * const inset = par.inInset();
1231                 bool const accept =
1232                         !(inset && inset->forceDefaultParagraphs(inset));
1233
1234                 data = "update " + tostr(accept) + '\n' + data;
1235                 bv_->owner()->getDialogs().update("paragraph", data);
1236                 break;
1237         }
1238
1239         case LFUN_PARAGRAPH_APPLY:
1240                 setParagraphParams(*bv_, ev.argument);
1241                 break;
1242
1243         case LFUN_THESAURUS_ENTRY:
1244         {
1245                 string arg = ev.argument;
1246
1247                 if (arg.empty()) {
1248                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1249                                                                    false);
1250
1251                         // FIXME
1252                         if (arg.size() > 100 || arg.empty()) {
1253                                 // Get word or selection
1254                                 bv_->getLyXText()->selectWordWhenUnderCursor(lyx::WHOLE_WORD);
1255                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1256                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1257                         }
1258                 }
1259
1260                 bv_->owner()->getDialogs().show("thesaurus", arg);
1261         }
1262                 break;
1263
1264         case LFUN_TRACK_CHANGES:
1265                 trackChanges();
1266                 break;
1267
1268         case LFUN_MERGE_CHANGES:
1269                 owner_->getDialogs().show("changes");
1270                 break;
1271
1272         case LFUN_ACCEPT_ALL_CHANGES: {
1273                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1274 #warning FIXME changes
1275                 //moveCursorUpdate(false);
1276
1277                 while (lyx::find::findNextChange(bv_))
1278                         bv_->getLyXText()->acceptChange();
1279
1280                 update();
1281                 break;
1282         }
1283
1284         case LFUN_REJECT_ALL_CHANGES: {
1285                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1286 #warning FIXME changes
1287                 //moveCursorUpdate(false);
1288
1289                 while (lyx::find::findNextChange(bv_))
1290                         bv_->getLyXText()->rejectChange();
1291
1292                 update();
1293                 break;
1294         }
1295
1296         case LFUN_ACCEPT_CHANGE: {
1297                 bv_->getLyXText()->acceptChange();
1298                 update();
1299                 break;
1300         }
1301
1302         case LFUN_REJECT_CHANGE: {
1303                 bv_->getLyXText()->rejectChange();
1304                 update();
1305                 break;
1306         }
1307
1308         case LFUN_UNKNOWN_ACTION:
1309                 ev.errorMessage(N_("Unknown function!"));
1310                 break;
1311
1312         default:
1313                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1314         } // end of switch
1315
1316         return true;
1317 }
1318
1319
1320 bool BufferView::Pimpl::insertInset(InsetOld * inset, string const & lout)
1321 {
1322         // if we are in a locking inset we should try to insert the
1323         // inset there otherwise this is a illegal function now
1324         if (bv_->theLockingInset()) {
1325                 if (bv_->theLockingInset()->insetAllowed(inset))
1326                         return bv_->theLockingInset()->insertInset(bv_, inset);
1327                 return false;
1328         }
1329
1330         // not quite sure if we want this...
1331         recordUndo(bv_, Undo::ATOMIC);
1332         freezeUndo();
1333
1334         beforeChange(bv_->text);
1335         if (!lout.empty()) {
1336                 bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1337
1338                 if (!bv_->text->cursor.par()->empty()) {
1339                         bv_->text->cursorLeft(bv_);
1340                         bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1341                 }
1342
1343                 string lres = lout;
1344                 LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1345                 bool hasLayout = tclass.hasLayout(lres);
1346                 string lay = tclass.defaultLayoutName();
1347
1348                 if (hasLayout != false) {
1349                         // layout found
1350                         lay = lres;
1351                 } else {
1352                         // layout not fount using default
1353                         lay = tclass.defaultLayoutName();
1354                 }
1355
1356                 bv_->text->setLayout(lay);
1357
1358                 bv_->text->setParagraph(0, 0,
1359                                    0, 0,
1360                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1361                                    Spacing(),
1362                                    LYX_ALIGN_LAYOUT,
1363                                    string(),
1364                                    0);
1365         }
1366
1367         bv_->text->insertInset(inset);
1368         update();
1369
1370         unFreezeUndo();
1371         return true;
1372 }
1373
1374
1375 void BufferView::Pimpl::updateInset()
1376 {
1377         if (!available())
1378                 return;
1379
1380         // this should not be needed, but it is...
1381         bv_->text->redoParagraph(bv_->text->cursor.par());
1382
1383         update();
1384         updateScrollbar();
1385 }