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