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