]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
696f9197c14093ed3baec1bc7fa8a8cd03040c0b
[lyx.git] / src / BufferView_pimpl.C
1 /**
2  * \file BufferView_pimpl.C
3  * Copyright 2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author various
7  */
8
9 #include <config.h>
10
11 #ifdef __GNUG__
12 #pragma implementation
13 #endif
14
15 #include "BufferView_pimpl.h"
16 #include "frontends/WorkArea.h"
17 #include "frontends/screen.h"
18 #include "frontends/LyXScreenFactory.h"
19 #include "frontends/WorkAreaFactory.h"
20 #include "frontends/Dialogs.h"
21 #include "frontends/Alert.h"
22 #include "frontends/FileDialog.h"
23 #include "frontends/mouse_state.h"
24 #include "lyxtext.h"
25 #include "lyxrow.h"
26 #include "paragraph.h"
27 #include "frontends/LyXView.h"
28 #include "commandtags.h"
29 #include "lyxfunc.h"
30 #include "debug.h"
31 #include "bufferview_funcs.h"
32 #include "TextCache.h"
33 #include "bufferlist.h"
34 #include "lyxrc.h"
35 #include "intl.h"
36 // added for Dispatch functions
37 #include "lyx_cb.h"
38 #include "lyx_main.h"
39 #include "FloatList.h"
40 #include "gettext.h"
41 #include "ParagraphParameters.h"
42 #include "undo_funcs.h"
43 #include "funcrequest.h"
44
45 #include "insets/insetbib.h"
46 #include "insets/insettext.h"
47 #include "insets/inseturl.h"
48 #include "insets/insetlatexaccent.h"
49 #include "insets/insettoc.h"
50 #include "insets/insetref.h"
51 #include "insets/insetparent.h"
52 #include "insets/insetindex.h"
53 #include "insets/insetinclude.h"
54 #include "insets/insetcite.h"
55 #include "insets/insetgraphics.h"
56 #include "insets/insetmarginal.h"
57 #include "insets/insetcaption.h"
58 #include "insets/insetfloatlist.h"
59
60 #include "mathed/formulabase.h"
61
62 #include "graphics/Previews.h"
63
64 #include "support/LAssert.h"
65 #include "support/lstrings.h"
66 #include "support/filetools.h"
67
68 #include <boost/bind.hpp>
69 #include <boost/signals/connection.hpp>
70
71 #include <unistd.h>
72 #include <sys/wait.h>
73
74
75 using std::vector;
76 using std::find_if;
77 using std::find;
78 using std::pair;
79 using std::endl;
80 using std::make_pair;
81 using std::min;
82
83 using lyx::pos_type;
84
85 extern BufferList bufferlist;
86
87
88 namespace {
89
90 unsigned int const saved_positions_num = 20;
91
92 // All the below connection objects are needed because of a bug in some
93 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
94 // to these connections we avoid a segfault upon startup, and also at exit.
95 // (Lgb)
96
97 boost::signals::connection dispatchcon;
98 boost::signals::connection timecon;
99 boost::signals::connection doccon;
100 boost::signals::connection resizecon;
101 boost::signals::connection kpresscon;
102 boost::signals::connection selectioncon;
103 boost::signals::connection lostcon;
104
105
106 } // anon namespace
107
108
109 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
110              int xpos, int ypos, int width, int height)
111         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
112           using_xterm_cursor(false)
113 {
114         workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
115         screen_.reset(LyXScreenFactory::create(workarea()));
116
117         // Setup the signals
118         doccon = workarea().scrollDocView
119                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
120         resizecon = workarea().workAreaResize
121                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
122         dispatchcon = workarea().dispatch
123                 .connect(boost::bind(&BufferView::Pimpl::dispatch, this, _1));
124         kpresscon = workarea().workAreaKeyPress
125                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
126         selectioncon = workarea().selectionRequested
127                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
128         lostcon = workarea().selectionLost
129                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
130
131         timecon = cursor_timeout.timeout
132                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
133         cursor_timeout.start();
134         saved_positions.resize(saved_positions_num);
135 }
136
137
138 WorkArea & BufferView::Pimpl::workarea() const
139 {
140         return *workarea_.get();
141 }
142
143
144 LyXScreen & BufferView::Pimpl::screen() const
145 {
146         return *screen_.get();
147 }
148
149
150 Painter & BufferView::Pimpl::painter() const
151 {
152         return workarea().getPainter();
153 }
154
155
156 void BufferView::Pimpl::buffer(Buffer * b)
157 {
158         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
159                             << b << ")" << endl;
160         if (buffer_) {
161                 buffer_->delUser(bv_);
162
163                 // Put the old text into the TextCache, but
164                 // only if the buffer is still loaded.
165                 // Also set the owner of the test to 0
166                 //              bv_->text->owner(0);
167                 textcache.add(buffer_, workarea().workWidth(), bv_->text);
168                 if (lyxerr.debugging())
169                         textcache.show(lyxerr, "BufferView::buffer");
170
171                 bv_->text = 0;
172         }
173
174         // set current buffer
175         buffer_ = b;
176
177         if (bufferlist.getState() == BufferList::CLOSING) return;
178
179         // if we are closing the buffer, use the first buffer as current
180         if (!buffer_) {
181                 buffer_ = bufferlist.first();
182         }
183
184         if (buffer_) {
185                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
186                 buffer_->addUser(bv_);
187
188                 // If we don't have a text object for this, we make one
189                 if (bv_->text == 0) {
190                         resizeCurrentBuffer();
191                 }
192
193                 // FIXME: needed when ?
194                 bv_->text->first_y =
195                         screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
196
197                 // Similarly, buffer-dependent dialogs should be updated or
198                 // hidden. This should go here because some dialogs (eg ToC)
199                 // require bv_->text.
200                 owner_->getDialogs().updateBufferDependent(true);
201         } else {
202                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
203                 owner_->getDialogs().hideBufferDependent();
204
205                 // Also remove all remaining text's from the testcache.
206                 // (there should not be any!) (if there is any it is a
207                 // bug!)
208                 if (lyxerr.debugging())
209                         textcache.show(lyxerr, "buffer delete all");
210                 textcache.clear();
211         }
212
213         repaint();
214         updateScrollbar();
215         owner_->updateMenubar();
216         owner_->updateToolbar();
217         owner_->updateLayoutChoice();
218         owner_->updateWindowTitle();
219
220         if (grfx::Previews::activated() && buffer_)
221                 grfx::Previews::get().generateBufferPreviews(*buffer_);
222 }
223
224
225 bool BufferView::Pimpl::fitCursor()
226 {
227         bool ret;
228
229         if (bv_->theLockingInset()) {
230                 bv_->theLockingInset()->fitInsetCursor(bv_);
231                 ret = true;
232         } else {
233                 ret = screen().fitCursor(bv_->text, bv_);
234         }
235
236         bv_->owner()->getDialogs().updateParagraph();
237         if (ret)
238                 updateScrollbar();
239         return ret;
240 }
241
242
243 void BufferView::Pimpl::redoCurrentBuffer()
244 {
245         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
246         if (buffer_ && bv_->text) {
247                 resizeCurrentBuffer();
248                 updateScrollbar();
249                 owner_->updateLayoutChoice();
250                 repaint();
251         }
252 }
253
254
255 int BufferView::Pimpl::resizeCurrentBuffer()
256 {
257         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
258
259         Paragraph * par = 0;
260         Paragraph * selstartpar = 0;
261         Paragraph * selendpar = 0;
262         UpdatableInset * the_locking_inset = 0;
263
264         pos_type pos = 0;
265         pos_type selstartpos = 0;
266         pos_type selendpos = 0;
267         bool selection = false;
268         bool mark_set  = false;
269
270         owner_->prohibitInput();
271
272         owner_->message(_("Formatting document..."));
273
274         if (bv_->text) {
275                 par = bv_->text->cursor.par();
276                 pos = bv_->text->cursor.pos();
277                 selstartpar = bv_->text->selection.start.par();
278                 selstartpos = bv_->text->selection.start.pos();
279                 selendpar = bv_->text->selection.end.par();
280                 selendpos = bv_->text->selection.end.pos();
281                 selection = bv_->text->selection.set();
282                 mark_set = bv_->text->selection.mark();
283                 the_locking_inset = bv_->theLockingInset();
284                 buffer_->resizeInsets(bv_);
285                 // I don't think the delete and new are necessary here we just could
286                 // call only init! (Jug 20020419)
287                 delete bv_->text;
288                 bv_->text = new LyXText(bv_);
289                 bv_->text->init(bv_);
290         } else {
291                 // See if we have a text in TextCache that fits
292                 // the new buffer_ with the correct width.
293                 bv_->text = textcache.findFit(buffer_, workarea().workWidth());
294                 if (bv_->text) {
295                         if (lyxerr.debugging()) {
296                                 lyxerr << "Found a LyXText that fits:\n";
297                                 textcache.show(lyxerr, make_pair(buffer_, make_pair(workarea().workWidth(), bv_->text)));
298                         }
299                         // Set the owner of the newly found text
300                         //      bv_->text->owner(bv_);
301                         if (lyxerr.debugging())
302                                 textcache.show(lyxerr, "resizeCurrentBuffer");
303                 } else {
304                         bv_->text = new LyXText(bv_);
305                         bv_->text->init(bv_);
306                         //buffer_->resizeInsets(bv_);
307                 }
308         }
309
310         if (par) {
311                 bv_->text->selection.set(true);
312                 // At this point just to avoid the Delete-Empty-Paragraph-
313                 // Mechanism when setting the cursor.
314                 bv_->text->selection.mark(mark_set);
315                 if (selection) {
316                         bv_->text->setCursor(bv_, selstartpar, selstartpos);
317                         bv_->text->selection.cursor = bv_->text->cursor;
318                         bv_->text->setCursor(bv_, selendpar, selendpos);
319                         bv_->text->setSelection(bv_);
320                         bv_->text->setCursor(bv_, par, pos);
321                 } else {
322                         bv_->text->setCursor(bv_, par, pos);
323                         bv_->text->selection.cursor = bv_->text->cursor;
324                         bv_->text->selection.set(false);
325                 }
326                 // remake the inset locking
327                 bv_->theLockingInset(the_locking_inset);
328         }
329
330         bv_->text->first_y = screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
331
332         switchKeyMap();
333         owner_->allowInput();
334
335         updateScrollbar();
336
337         return 0;
338 }
339
340
341 void BufferView::Pimpl::repaint()
342 {
343         // Regenerate the screen.
344         screen().redraw(bv_->text, bv_);
345 }
346
347
348 void BufferView::Pimpl::updateScrollbar()
349 {
350         if (!bv_->text) {
351                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
352                 workarea().setScrollbarParams(0, 0, 0);
353                 return;
354         }
355
356         LyXText const & t = *bv_->text;
357
358         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", first_y "
359                 << t.first_y << ", default height " << t.defaultHeight() << endl;
360
361         workarea().setScrollbarParams(t.height, t.first_y, t.defaultHeight());
362 }
363
364
365 void BufferView::Pimpl::scrollDocView(int value)
366 {
367         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
368
369         if (!buffer_) return;
370
371         screen().draw(bv_->text, bv_, value);
372
373         if (!lyxrc.cursor_follows_scrollbar) {
374                 return;
375         }
376
377         LyXText * vbt = bv_->text;
378
379         int const height = vbt->defaultHeight();
380         int const first = static_cast<int>((bv_->text->first_y + height));
381         int const last = static_cast<int>((bv_->text->first_y + workarea().workHeight() - height));
382
383         if (vbt->cursor.y() < first)
384                 vbt->setCursorFromCoordinates(bv_, 0, first);
385         else if (vbt->cursor.y() > last)
386                 vbt->setCursorFromCoordinates(bv_, 0, last);
387 }
388
389
390 int BufferView::Pimpl::scroll(long time)
391 {
392         if (!buffer_)
393                 return 0;
394
395         LyXText const * t = bv_->text;
396
397         double const diff = t->defaultHeight()
398                 + double(time) * double(time) * 0.125;
399
400         scrollDocView(int(diff));
401         workarea().setScrollbarParams(t->height, t->first_y, t->defaultHeight());
402         return 0;
403 }
404
405
406 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
407                                          key_modifier::state state)
408 {
409         bv_->owner()->getLyXFunc().processKeySym(key, state);
410 }
411
412
413 void BufferView::Pimpl::selectionRequested()
414 {
415         static string sel;
416
417         if (!available())
418                 return;
419
420         LyXText * text = bv_->getLyXText();
421
422         if (text->selection.set() &&
423                 (!bv_->text->xsel_cache.set() ||
424                  text->selection.start != bv_->text->xsel_cache.start ||
425                  text->selection.end != bv_->text->xsel_cache.end))
426         {
427                 bv_->text->xsel_cache = text->selection;
428                 sel = text->selectionAsString(bv_->buffer(), false);
429         } else if (!text->selection.set()) {
430                 sel = string();
431                 bv_->text->xsel_cache.set(false);
432         }
433         if (!sel.empty()) {
434                 workarea().putClipboard(sel);
435         }
436 }
437
438
439 void BufferView::Pimpl::selectionLost()
440 {
441         if (available()) {
442                 hideCursor();
443                 toggleSelection();
444                 bv_->getLyXText()->clearSelection();
445                 showCursor();
446                 bv_->text->xsel_cache.set(false);
447         }
448 }
449
450
451 void BufferView::Pimpl::workAreaResize()
452 {
453         static int work_area_width;
454         static int work_area_height;
455
456         bool const widthChange = workarea().workWidth() != work_area_width;
457         bool const heightChange = workarea().workHeight() != work_area_height;
458
459         // update from work area
460         work_area_width = workarea().workWidth();
461         work_area_height = workarea().workHeight();
462
463         if (buffer_ != 0) {
464                 if (widthChange) {
465                         // The visible LyXView need a resize
466                         resizeCurrentBuffer();
467
468                         // Remove all texts from the textcache
469                         // This is not _really_ what we want to do. What
470                         // we really want to do is to delete in textcache
471                         // that does not have a BufferView with matching
472                         // width, but as long as we have only one BufferView
473                         // deleting all gives the same result.
474                         if (lyxerr.debugging())
475                                 textcache.show(lyxerr, "Expose delete all");
476                         textcache.clear();
477                         // FIXME: this is already done in resizeCurrentBuffer() ??
478                         buffer_->resizeInsets(bv_);
479                 } else if (heightChange) {
480                         // fitCursor() ensures we don't jump back
481                         // to the start of the document on vertical
482                         // resize
483                         fitCursor();
484                 }
485         }
486
487         if (widthChange || heightChange) {
488                 repaint();
489         }
490
491         // always make sure that the scrollbar is sane.
492         updateScrollbar();
493         owner_->updateLayoutChoice();
494         return;
495 }
496
497
498 void BufferView::Pimpl::update()
499 {
500         if (!bv_->theLockingInset() || !bv_->theLockingInset()->nodraw()) {
501                 LyXText::text_status st = bv_->text->status();
502                 screen().update(bv_->text, bv_);
503                 bool fitc = false;
504                 while (bv_->text->status() == LyXText::CHANGED_IN_DRAW) {
505                         bv_->text->fullRebreak(bv_);
506                         st = LyXText::NEED_MORE_REFRESH;
507                         bv_->text->setCursor(bv_, bv_->text->cursor.par(),
508                                              bv_->text->cursor.pos());
509                         if (bv_->text->selection.set()) {
510                                 bv_->text->setCursor(bv_, bv_->text->selection.start,
511                                                      bv_->text->selection.start.par(),
512                                                      bv_->text->selection.start.pos());
513                                 bv_->text->setCursor(bv_, bv_->text->selection.end,
514                                                      bv_->text->selection.end.par(),
515                                                      bv_->text->selection.end.pos());
516                         }
517                         fitc = true;
518                         bv_->text->status(bv_, st);
519                         screen().update(bv_->text, bv_);
520                 }
521                 // do this here instead of in the screen::update because of
522                 // the above loop!
523                 bv_->text->status(bv_, LyXText::UNCHANGED);
524                 if (fitc)
525                         fitCursor();
526         }
527 }
528
529 // Values used when calling update:
530 // -3 - update
531 // -2 - update, move sel_cursor if selection, fitcursor
532 // -1 - update, move sel_cursor if selection, fitcursor, mark dirty
533 //  0 - update, move sel_cursor if selection, fitcursor
534 //  1 - update, move sel_cursor if selection, fitcursor, mark dirty
535 //  3 - update, move sel_cursor if selection
536 //
537 // update -
538 // a simple redraw of the parts that need refresh
539 //
540 // move sel_cursor if selection -
541 // the text's sel_cursor is moved if there is selection is progress
542 //
543 // fitcursor -
544 // fitCursor() is called and the scrollbar updated
545 //
546 // mark dirty -
547 // the buffer is marked dirty.
548 //
549 // enum {
550 //       UPDATE = 0,
551 //       SELECT = 1,
552 //       FITCUR = 2,
553 //       CHANGE = 4
554 // };
555 //
556 // UPDATE_ONLY = UPDATE;
557 // UPDATE_SELECT = UPDATE | SELECT;
558 // UPDATE_SELECT_MOVE = UPDATE | SELECT | FITCUR;
559 // UPDATE_SELECT_MOVE_AFTER_CHANGE = UPDATE | SELECT | FITCUR | CHANGE;
560 //
561 // update(-3) -> update(0)         -> update(0) -> update(UPDATE)
562 // update(-2) -> update(1 + 2)     -> update(3) -> update(SELECT|FITCUR)
563 // update(-1) -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
564 // update(1)  -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
565 // update(3)  -> update(1)         -> update(1) -> update(SELECT)
566
567 void BufferView::Pimpl::update(LyXText * text, BufferView::UpdateCodes f)
568 {
569         owner_->updateLayoutChoice();
570
571         if (!text->selection.set() && (f & SELECT)) {
572                 text->selection.cursor = text->cursor;
573         }
574
575         text->fullRebreak(bv_);
576
577         if (text->inset_owner) {
578                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
579                 updateInset(text->inset_owner, false);
580         } else {
581                 update();
582         }
583
584         if ((f & FITCUR)) {
585                 fitCursor();
586         }
587
588         if ((f & CHANGE)) {
589                 buffer_->markDirty();
590         }
591 }
592
593
594 // Callback for cursor timer
595 void BufferView::Pimpl::cursorToggle()
596 {
597         if (!buffer_) {
598                 cursor_timeout.restart();
599                 return;
600         }
601
602         /* FIXME */
603         extern void reapSpellchecker(void);
604         reapSpellchecker();
605
606         if (!bv_->theLockingInset()) {
607                 screen().cursorToggle(bv_);
608         } else {
609                 bv_->theLockingInset()->toggleInsetCursor(bv_);
610         }
611
612         cursor_timeout.restart();
613 }
614
615
616 bool BufferView::Pimpl::available() const
617 {
618         if (buffer_ && bv_->text)
619                 return true;
620         return false;
621 }
622
623
624 void BufferView::Pimpl::beforeChange(LyXText * text)
625 {
626         toggleSelection();
627         text->clearSelection();
628 }
629
630
631 void BufferView::Pimpl::savePosition(unsigned int i)
632 {
633         if (i >= saved_positions_num)
634                 return;
635         saved_positions[i] = Position(buffer_->fileName(),
636                                       bv_->text->cursor.par()->id(),
637                                       bv_->text->cursor.pos());
638         if (i > 0) {
639                 ostringstream str;
640                 str << _("Saved bookmark") << ' ' << i;
641                 owner_->message(str.str().c_str());
642         }
643 }
644
645
646 void BufferView::Pimpl::restorePosition(unsigned int i)
647 {
648         if (i >= saved_positions_num)
649                 return;
650
651         string const fname = saved_positions[i].filename;
652
653         beforeChange(bv_->text);
654
655         if (fname != buffer_->fileName()) {
656                 Buffer * b = bufferlist.exists(fname) ?
657                         bufferlist.getBuffer(fname) :
658                         bufferlist.loadLyXFile(fname); // don't ask, just load it
659                 if (b != 0) buffer(b);
660         }
661
662         Paragraph * par = buffer_->getParFromID(saved_positions[i].par_id);
663         if (!par)
664                 return;
665
666         bv_->text->setCursor(bv_, par,
667                              min(par->size(), saved_positions[i].par_pos));
668
669         update(bv_->text, BufferView::SELECT | BufferView::FITCUR);
670         if (i > 0) {
671                 ostringstream str;
672                 str << _("Moved to bookmark") << ' ' << i;
673                 owner_->message(str.str().c_str());
674         }
675 }
676
677
678 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
679 {
680         if (i >= saved_positions_num)
681                 return false;
682
683         return !saved_positions[i].filename.empty();
684 }
685
686
687 void BufferView::Pimpl::switchKeyMap()
688 {
689         if (!lyxrc.rtl_support)
690                 return;
691
692         LyXText * text = bv_->getLyXText();
693         if (text->real_current_font.isRightToLeft()
694             && !(bv_->theLockingInset()
695                  && bv_->theLockingInset()->lyxCode() == Inset::ERT_CODE))
696         {
697                 if (owner_->getIntl().keymap == Intl::PRIMARY)
698                         owner_->getIntl().KeyMapSec();
699         } else {
700                 if (owner_->getIntl().keymap == Intl::SECONDARY)
701                         owner_->getIntl().KeyMapPrim();
702         }
703 }
704
705
706 void BufferView::Pimpl::insetUnlock()
707 {
708         if (bv_->theLockingInset()) {
709                 bv_->theLockingInset()->insetUnlock(bv_);
710                 bv_->theLockingInset(0);
711                 finishUndo();
712         }
713 }
714
715
716 void BufferView::Pimpl::showCursor()
717 {
718         if (bv_->theLockingInset())
719                 bv_->theLockingInset()->showInsetCursor(bv_);
720         else
721                 screen().showCursor(bv_->text, bv_);
722 }
723
724
725 void BufferView::Pimpl::hideCursor()
726 {
727         if (!bv_->theLockingInset())
728                 screen().hideCursor();
729 }
730
731
732 void BufferView::Pimpl::toggleSelection(bool b)
733 {
734         if (bv_->theLockingInset())
735                 bv_->theLockingInset()->toggleSelection(bv_, b);
736         screen().toggleSelection(bv_->text, bv_, b);
737 }
738
739
740 void BufferView::Pimpl::toggleToggle()
741 {
742         screen().toggleToggle(bv_->text, bv_);
743 }
744
745
746 void BufferView::Pimpl::center()
747 {
748         LyXText * t = bv_->text;
749
750         beforeChange(t);
751         int const half_height = workarea().workHeight() / 2;
752         int new_y = 0;
753
754         if (t->cursor.y() > half_height) {
755                 new_y = t->cursor.y() - half_height;
756         }
757
758         // FIXME: can we do this w/o calling screen directly ?
759         // This updates first_y but means the fitCursor() call
760         // from the update(FITCUR) doesn't realise that we might
761         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
762         // the scrollbar to be updated as it should, so we have
763         // to do it manually. Any operation that does a center()
764         // and also might have moved first_y must make sure to call
765         // updateScrollbar() currently. Never mind that this is a
766         // pretty obfuscated way of updating t->first_y
767         screen().draw(t, bv_, new_y);
768
769         update(t, BufferView::SELECT | BufferView::FITCUR);
770 }
771
772
773 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
774 {
775         workarea().putClipboard(stuff);
776 }
777
778
779 /*
780  * Dispatch functions for actions which can be valid for BufferView->text
781  * and/or InsetText->text!!!
782  */
783
784
785 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
786 {
787 #if 0
788         LyXCursor cursor = bv_->getLyXText()->cursor;
789         Buffer::inset_iterator it =
790                 find_if(Buffer::inset_iterator(
791                         cursor.par(), cursor.pos()),
792                         buffer_->inset_iterator_end(),
793                         lyx::compare_memfun(&Inset::lyxCode, code));
794         return it != buffer_->inset_iterator_end() ? (*it) : 0;
795 #else
796         // Ok, this is a little bit too brute force but it
797         // should work for now. Better infrastructure is comming. (Lgb)
798
799         Buffer * b = bv_->buffer();
800         LyXCursor cursor = bv_->getLyXText()->cursor;
801
802         Buffer::inset_iterator beg = b->inset_iterator_begin();
803         Buffer::inset_iterator end = b->inset_iterator_end();
804
805         bool cursor_par_seen = false;
806
807         for (; beg != end; ++beg) {
808                 if (beg.getPar() == cursor.par()) {
809                         cursor_par_seen = true;
810                 }
811                 if (cursor_par_seen) {
812                         if (beg.getPar() == cursor.par()
813                             && beg.getPos() >= cursor.pos()) {
814                                 break;
815                         } else if (beg.getPar() != cursor.par()) {
816                                 break;
817                         }
818                 }
819
820         }
821         if (beg != end) {
822                 // Now find the first inset that matches code.
823                 for (; beg != end; ++beg) {
824                         if (beg->lyxCode() == code) {
825                                 return &(*beg);
826                         }
827                 }
828         }
829         return 0;
830 #endif
831 }
832
833
834 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
835 {
836         string filename = filen;
837
838         if (filename.empty()) {
839                 // Launch a file browser
840                 string initpath = lyxrc.document_path;
841
842                 if (available()) {
843                         string const trypath = owner_->buffer()->filePath();
844                         // If directory is writeable, use this as default.
845                         if (IsDirWriteable(trypath))
846                                 initpath = trypath;
847                 }
848
849                 FileDialog fileDlg(bv_->owner(),
850                                    _("Select LyX document to insert"),
851                         LFUN_FILE_INSERT,
852                         make_pair(string(_("Documents|#o#O")),
853                                   string(lyxrc.document_path)),
854                         make_pair(string(_("Examples|#E#e")),
855                                   string(AddPath(system_lyxdir, "examples"))));
856
857                 FileDialog::Result result =
858                         fileDlg.Select(initpath,
859                                        _("*.lyx| LyX Documents (*.lyx)"));
860
861                 if (result.first == FileDialog::Later)
862                         return;
863
864                 filename = result.second;
865
866                 // check selected filename
867                 if (filename.empty()) {
868                         owner_->message(_("Canceled."));
869                         return;
870                 }
871         }
872
873         // get absolute path of file and add ".lyx" to the filename if
874         // necessary
875         filename = FileSearch(string(), filename, "lyx");
876
877         string const disp_fn(MakeDisplayPath(filename));
878
879         ostringstream s1;
880         s1 << _("Inserting document") << ' '
881            << disp_fn << " ...";
882         owner_->message(s1.str().c_str());
883         bool const res = bv_->insertLyXFile(filename);
884         if (res) {
885                 ostringstream str;
886                 str << _("Document") << ' ' << disp_fn
887                     << ' ' << _("inserted.");
888                 owner_->message(str.str().c_str());
889         } else {
890                 ostringstream str;
891                 str << _("Could not insert document") << ' '
892                     << disp_fn;
893                 owner_->message(str.str().c_str());
894         }
895 }
896
897
898 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
899 {
900         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
901                 << " action[" << ev.action <<"]"
902                 << " arg[" << ev.argument << "]"
903                 << " x[" << ev.x << "]"
904                 << " y[" << ev.y << "]"
905                 << " button[" << ev.button() << "]"
906                 << endl;
907
908         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
909
910         switch (ev.action) {
911
912         case LFUN_TOC_INSERT:
913         {
914                 InsetCommandParams p;
915                 p.setCmdName("tableofcontents");
916                 Inset * inset = new InsetTOC(p);
917                 if (!insertInset(inset, tclass.defaultLayoutName()))
918                         delete inset;
919                 break;
920         }
921
922         case LFUN_SCROLL_INSET:
923                 // this is not handled here as this function is only active
924                 // if we have a locking_inset and that one is (or contains)
925                 // a tabular-inset
926                 break;
927
928         case LFUN_INSET_GRAPHICS:
929         {
930                 Inset * new_inset = new InsetGraphics;
931                 if (!insertInset(new_inset)) {
932                         delete new_inset;
933                 } else {
934                         // this is need because you don't use a inset->Edit()
935                         updateInset(new_inset, true);
936                         new_inset->edit(bv_);
937                 }
938                 break;
939         }
940
941         case LFUN_LAYOUT_COPY:
942                 bv_->copyEnvironment();
943                 break;
944
945         case LFUN_LAYOUT_PASTE:
946                 bv_->pasteEnvironment();
947                 switchKeyMap();
948                 break;
949
950         case LFUN_DEPTH_MIN:
951                 changeDepth(bv_, bv_->getLyXText(), -1);
952                 break;
953
954         case LFUN_DEPTH_PLUS:
955                 changeDepth(bv_, bv_->getLyXText(), 1);
956                 break;
957
958         case LFUN_FREE:
959                 owner_->getDialogs().setUserFreeFont();
960                 break;
961
962         case LFUN_FILE_INSERT:
963                 MenuInsertLyXFile(ev.argument);
964                 break;
965
966         case LFUN_FILE_INSERT_ASCII_PARA:
967                 InsertAsciiFile(bv_, ev.argument, true);
968                 break;
969
970         case LFUN_FILE_INSERT_ASCII:
971                 InsertAsciiFile(bv_, ev.argument, false);
972                 break;
973
974         case LFUN_LANGUAGE:
975                 lang(bv_, ev.argument);
976                 switchKeyMap();
977                 owner_->view_state_changed();
978                 break;
979
980         case LFUN_EMPH:
981                 emph(bv_);
982                 owner_->view_state_changed();
983                 break;
984
985         case LFUN_BOLD:
986                 bold(bv_);
987                 owner_->view_state_changed();
988                 break;
989
990         case LFUN_NOUN:
991                 noun(bv_);
992                 owner_->view_state_changed();
993                 break;
994
995         case LFUN_CODE:
996                 code(bv_);
997                 owner_->view_state_changed();
998                 break;
999
1000         case LFUN_SANS:
1001                 sans(bv_);
1002                 owner_->view_state_changed();
1003                 break;
1004
1005         case LFUN_ROMAN:
1006                 roman(bv_);
1007                 owner_->view_state_changed();
1008                 break;
1009
1010         case LFUN_DEFAULT:
1011                 styleReset(bv_);
1012                 owner_->view_state_changed();
1013                 break;
1014
1015         case LFUN_UNDERLINE:
1016                 underline(bv_);
1017                 owner_->view_state_changed();
1018                 break;
1019
1020         case LFUN_FONT_SIZE:
1021                 fontSize(bv_, ev.argument);
1022                 owner_->view_state_changed();
1023                 break;
1024
1025         case LFUN_FONT_STATE:
1026                 owner_->getLyXFunc().setMessage(currentState(bv_));
1027                 break;
1028
1029         case LFUN_INSERT_LABEL:
1030                 MenuInsertLabel(bv_, ev.argument);
1031                 break;
1032
1033         case LFUN_REF_INSERT:
1034                 if (ev.argument.empty()) {
1035                         InsetCommandParams p("ref");
1036                         owner_->getDialogs().createRef(p.getAsString());
1037                 } else {
1038                         InsetCommandParams p;
1039                         p.setFromString(ev.argument);
1040
1041                         InsetRef * inset = new InsetRef(p, *buffer_);
1042                         if (!insertInset(inset))
1043                                 delete inset;
1044                         else
1045                                 updateInset(inset, true);
1046                 }
1047                 break;
1048
1049         case LFUN_BOOKMARK_SAVE:
1050                 savePosition(strToUnsignedInt(ev.argument));
1051                 break;
1052
1053         case LFUN_BOOKMARK_GOTO:
1054                 restorePosition(strToUnsignedInt(ev.argument));
1055                 break;
1056
1057         case LFUN_REF_GOTO:
1058         {
1059                 string label = ev.argument;
1060                 if (label.empty()) {
1061                         InsetRef * inset =
1062                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1063                         if (inset) {
1064                                 label = inset->getContents();
1065                                 savePosition(0);
1066                         }
1067                 }
1068
1069                 if (!label.empty()) {
1070                         //bv_->savePosition(0);
1071                         if (!bv_->gotoLabel(label))
1072                                 Alert::alert(_("Error"),
1073                                            _("Couldn't find this label"),
1074                                            _("in current document."));
1075                 }
1076         }
1077         break;
1078
1079         case LFUN_HTMLURL:
1080         case LFUN_URL:
1081         {
1082                 InsetCommandParams p;
1083                 if (ev.action == LFUN_HTMLURL)
1084                         p.setCmdName("htmlurl");
1085                 else
1086                         p.setCmdName("url");
1087                 owner_->getDialogs().createUrl(p.getAsString());
1088         }
1089         break;
1090
1091         case LFUN_INSERT_URL:
1092         {
1093                 InsetCommandParams p;
1094                 p.setFromString(ev.argument);
1095
1096                 InsetUrl * inset = new InsetUrl(p);
1097                 if (!insertInset(inset))
1098                         delete inset;
1099                 else
1100                         updateInset(inset, true);
1101         }
1102         break;
1103
1104         case LFUN_INSET_CAPTION:
1105         {
1106                 // Do we have a locking inset...
1107                 if (bv_->theLockingInset()) {
1108                         lyxerr << "Locking inset code: "
1109                                << static_cast<int>(bv_->theLockingInset()->lyxCode());
1110                         InsetCaption * new_inset =
1111                                 new InsetCaption(buffer_->params);
1112                         new_inset->setOwner(bv_->theLockingInset());
1113                         new_inset->setAutoBreakRows(true);
1114                         new_inset->setDrawFrame(0, InsetText::LOCKED);
1115                         new_inset->setFrameColor(0, LColor::captionframe);
1116                         if (insertInset(new_inset))
1117                                 new_inset->edit(bv_);
1118                         else
1119                                 delete new_inset;
1120                 }
1121         }
1122         break;
1123
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(bv_->getLyXText(),
1152                                BufferView::SELECT
1153                                | BufferView::FITCUR
1154                                | BufferView::CHANGE);
1155                 }
1156                 break;
1157
1158         case LFUN_MATH_MACRO:
1159         case LFUN_MATH_DELIM:
1160         case LFUN_INSERT_MATRIX:
1161         case LFUN_INSERT_MATH:
1162         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1163         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1164         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1165         case LFUN_GREEK:                 // Insert a single greek letter
1166                 mathDispatch(FuncRequest(bv_, ev.action, ev.argument));
1167                 break;
1168
1169         case LFUN_CITATION_INSERT:
1170         {
1171                 InsetCommandParams p;
1172                 p.setFromString(ev.argument);
1173
1174                 InsetCitation * inset = new InsetCitation(p);
1175                 if (!insertInset(inset))
1176                         delete inset;
1177                 else
1178                         updateInset(inset, true);
1179         }
1180         break;
1181
1182         case LFUN_INSERT_BIBTEX:
1183         {
1184                 // ale970405+lasgoutt970425
1185                 // The argument can be up to two tokens separated
1186                 // by a space. The first one is the bibstyle.
1187                 string const db = token(ev.argument, ' ', 0);
1188                 string bibstyle = token(ev.argument, ' ', 1);
1189                 if (bibstyle.empty())
1190                         bibstyle = "plain";
1191
1192                 InsetCommandParams p("BibTeX", db, bibstyle);
1193                 InsetBibtex * inset = new InsetBibtex(p);
1194
1195                 if (insertInset(inset)) {
1196                         if (ev.argument.empty())
1197                                 inset->edit(bv_);
1198                 } else
1199                         delete inset;
1200         }
1201         break;
1202
1203         // BibTeX data bases
1204         case LFUN_BIBDB_ADD:
1205         {
1206                 InsetBibtex * inset =
1207                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1208                 if (inset) {
1209                         inset->addDatabase(ev.argument);
1210                 }
1211         }
1212         break;
1213
1214         case LFUN_BIBDB_DEL:
1215         {
1216                 InsetBibtex * inset =
1217                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1218                 if (inset) {
1219                         inset->delDatabase(ev.argument);
1220                 }
1221         }
1222         break;
1223
1224         case LFUN_BIBTEX_STYLE:
1225         {
1226                 InsetBibtex * inset =
1227                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1228                 if (inset) {
1229                         inset->setOptions(ev.argument);
1230                 }
1231         }
1232         break;
1233
1234         case LFUN_INDEX_INSERT:
1235         {
1236                 string entry = ev.argument;
1237                 if (entry.empty())
1238                         entry = bv_->getLyXText()->getStringToIndex(bv_);
1239
1240                 if (entry.empty()) {
1241                         owner_->getDialogs().createIndex();
1242                         break;
1243                 }
1244
1245                 InsetIndex * inset = new InsetIndex(InsetCommandParams("index", entry));
1246
1247                 if (!insertInset(inset)) {
1248                         delete inset;
1249                 } else {
1250                         updateInset(inset, true);
1251                 }
1252         }
1253         break;
1254
1255         case LFUN_INDEX_PRINT:
1256         {
1257                 InsetCommandParams p("printindex");
1258                 Inset * inset = new InsetPrintIndex(p);
1259                 if (!insertInset(inset, tclass.defaultLayoutName()))
1260                         delete inset;
1261         }
1262         break;
1263
1264         case LFUN_PARENTINSERT:
1265         {
1266                 InsetCommandParams p("lyxparent", ev.argument);
1267                 Inset * inset = new InsetParent(p, *buffer_);
1268                 if (!insertInset(inset, tclass.defaultLayoutName()))
1269                         delete inset;
1270         }
1271
1272         break;
1273
1274         case LFUN_CHILD_INSERT:
1275         {
1276                 InsetInclude::Params p;
1277                 p.cparams.setFromString(ev.argument);
1278                 p.masterFilename_ = buffer_->fileName();
1279
1280                 InsetInclude * inset = new InsetInclude(p);
1281                 if (!insertInset(inset))
1282                         delete inset;
1283                 else {
1284                         updateInset(inset, true);
1285                         bv_->owner()->getDialogs().showInclude(inset);
1286                 }
1287         }
1288         break;
1289
1290         case LFUN_FLOAT_LIST:
1291                 if (tclass.floats().typeExist(ev.argument)) {
1292                         Inset * inset = new InsetFloatList(ev.argument);
1293                         if (!insertInset(inset, tclass.defaultLayoutName()))
1294                                 delete inset;
1295                 } else {
1296                         lyxerr << "Non-existent float type: "
1297                                << ev.argument << endl;
1298                 }
1299                 break;
1300
1301         case LFUN_THESAURUS_ENTRY:
1302         {
1303                 string arg = ev.argument;
1304
1305                 if (arg.empty()) {
1306                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1307                                                                    false);
1308
1309                         // FIXME
1310                         if (arg.size() > 100 || arg.empty()) {
1311                                 // Get word or selection
1312                                 bv_->getLyXText()->selectWordWhenUnderCursor(bv_, LyXText::WHOLE_WORD);
1313                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1314                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1315                         }
1316                 }
1317
1318                 bv_->owner()->getDialogs().showThesaurus(arg);
1319         }
1320                 break;
1321
1322         case LFUN_UNKNOWN_ACTION:
1323                 ev.errorMessage(N_("Unknown function!"));
1324                 break;
1325
1326         default:
1327                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1328         } // end of switch
1329
1330         return true;
1331 }
1332
1333
1334 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1335 {
1336         // if we are in a locking inset we should try to insert the
1337         // inset there otherwise this is a illegal function now
1338         if (bv_->theLockingInset()) {
1339                 if (bv_->theLockingInset()->insetAllowed(inset))
1340                     return bv_->theLockingInset()->insertInset(bv_, inset);
1341                 return false;
1342         }
1343
1344         // not quite sure if we want this...
1345         setCursorParUndo(bv_);
1346         freezeUndo();
1347
1348         beforeChange(bv_->text);
1349         if (!lout.empty()) {
1350                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1351                 bv_->text->breakParagraph(bv_);
1352                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1353
1354                 if (!bv_->text->cursor.par()->empty()) {
1355                         bv_->text->cursorLeft(bv_);
1356
1357                         bv_->text->breakParagraph(bv_);
1358                         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1359                 }
1360
1361                 string lres = lout;
1362                 LyXTextClass const & tclass =
1363                         buffer_->params.getLyXTextClass();
1364                 bool hasLayout = tclass.hasLayout(lres);
1365                 string lay = tclass.defaultLayoutName();
1366
1367                 if (hasLayout != false) {
1368                         // layout found
1369                         lay = lres;
1370                 } else {
1371                         // layout not fount using default
1372                         lay = tclass.defaultLayoutName();
1373                 }
1374
1375                 bv_->text->setLayout(bv_, lay);
1376
1377                 bv_->text->setParagraph(bv_, 0, 0,
1378                                    0, 0,
1379                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1380                                    Spacing(),
1381                                    LYX_ALIGN_LAYOUT,
1382                                    string(),
1383                                    0);
1384                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1385         }
1386
1387         bv_->text->insertInset(bv_, inset);
1388         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1389
1390         unFreezeUndo();
1391         return true;
1392 }
1393
1394
1395 void BufferView::Pimpl::updateInset(Inset * inset, bool mark_dirty)
1396 {
1397         if (!inset || !available())
1398                 return;
1399
1400         // first check for locking insets
1401         if (bv_->theLockingInset()) {
1402                 if (bv_->theLockingInset() == inset) {
1403                         if (bv_->text->updateInset(bv_, inset)) {
1404                                 update();
1405                                 if (mark_dirty) {
1406                                         buffer_->markDirty();
1407                                 }
1408                                 updateScrollbar();
1409                                 return;
1410                         }
1411                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1412                         if (bv_->text->updateInset(bv_,  bv_->theLockingInset())) {
1413                                 update();
1414                                 if (mark_dirty) {
1415                                         buffer_->markDirty();
1416                                 }
1417                                 updateScrollbar();
1418                                 return;
1419                         }
1420                 }
1421         }
1422
1423         // then check if the inset is a top_level inset (has no owner)
1424         // if yes do the update as always otherwise we have to update the
1425         // toplevel inset where this inset is inside
1426         Inset * tl_inset = inset;
1427         while (tl_inset->owner())
1428                 tl_inset = tl_inset->owner();
1429         hideCursor();
1430         if (tl_inset == inset) {
1431                 update(bv_->text, BufferView::UPDATE);
1432                 if (bv_->text->updateInset(bv_, inset)) {
1433                         if (mark_dirty) {
1434                                 update(bv_->text,
1435                                        BufferView::SELECT
1436                                        | BufferView::FITCUR
1437                                        | BufferView::CHANGE);
1438                         } else {
1439                                 update(bv_->text, SELECT);
1440                         }
1441                         return;
1442                 }
1443         } else if (static_cast<UpdatableInset *>(tl_inset)
1444                            ->updateInsetInInset(bv_, inset))
1445         {
1446                 if (bv_->text->updateInset(bv_, tl_inset)) {
1447                         update();
1448                         updateScrollbar();
1449                 }
1450         }
1451 }