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