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