]> git.lyx.org Git - lyx.git/blob - src/text2.C
remove the focusInEvent() and focusOutEvent() that had so many bad side effect...
[lyx.git] / src / text2.C
1 /**
2  * \file text2.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 Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Dekel Tsur
15  * \author Jürgen Vigna
16  *
17  * Full author contact details are available in file CREDITS.
18  */
19
20 #include <config.h>
21
22 #include "lyxtext.h"
23
24 #include "buffer.h"
25 #include "buffer_funcs.h"
26 #include "bufferlist.h"
27 #include "bufferparams.h"
28 #include "BufferView.h"
29 #include "Bullet.h"
30 #include "coordcache.h"
31 #include "cursor.h"
32 #include "CutAndPaste.h"
33 #include "debug.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
36 #include "funcrequest.h"
37 #include "gettext.h"
38 #include "language.h"
39 #include "LColor.h"
40 #include "lyxfunc.h"
41 #include "lyxrc.h"
42 #include "lyxrow.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "pariterator.h"
48 #include "lyxserver.h"
49 #include "lyxsocket.h"
50 #include "undo.h"
51 #include "vspace.h"
52
53 #include "frontends/Application.h"
54 #include "frontends/FontLoader.h"
55 #include "frontends/FontMetrics.h"
56
57 #include "insets/insetenv.h"
58
59 #include "mathed/InsetMathHull.h"
60
61 #include "support/textutils.h"
62
63 #include <boost/current_function.hpp>
64
65 #include <sstream>
66
67 using lyx::docstring;
68 using lyx::pit_type;
69 using lyx::pos_type;
70
71 using std::endl;
72 using std::ostringstream;
73 using std::string;
74 using std::max;
75 using std::min;
76
77
78 LyXText::LyXText(BufferView * bv)
79         : maxwidth_(bv ? bv->workWidth() : 100),
80           current_font(LyXFont::ALL_INHERIT),
81           background_color_(LColor::background),
82           bv_owner(bv),
83           autoBreakRows_(false)
84 {}
85
86
87 void LyXText::init(BufferView * bv)
88 {
89         BOOST_ASSERT(bv);
90         bv_owner = bv;
91         maxwidth_ = bv->workWidth();
92         dim_.wid = maxwidth_;
93         dim_.asc = 10;
94         dim_.des = 10;
95
96         pit_type const end = paragraphs().size();
97         for (pit_type pit = 0; pit != end; ++pit)
98                 pars_[pit].rows().clear();
99
100         updateLabels(*bv->buffer());
101 }
102
103
104 bool LyXText::isMainText() const
105 {
106         return &bv()->buffer()->text() == this;
107 }
108
109
110 //takes screen x,y coordinates
111 InsetBase * LyXText::checkInsetHit(int x, int y) const
112 {
113         pit_type pit = getPitNearY(y);
114         BOOST_ASSERT(pit != -1);
115
116         Paragraph const & par = pars_[pit];
117
118         lyxerr[Debug::DEBUG]
119                 << BOOST_CURRENT_FUNCTION
120                 << ": x: " << x
121                 << " y: " << y
122                 << "  pit: " << pit
123                 << endl;
124         InsetList::const_iterator iit = par.insetlist.begin();
125         InsetList::const_iterator iend = par.insetlist.end();
126         for (; iit != iend; ++iit) {
127                 InsetBase * inset = iit->inset;
128 #if 1
129                 lyxerr[Debug::DEBUG]
130                         << BOOST_CURRENT_FUNCTION
131                         << ": examining inset " << inset << endl;
132
133                 if (theCoords.getInsets().has(inset))
134                         lyxerr[Debug::DEBUG]
135                                 << BOOST_CURRENT_FUNCTION
136                                 << ": xo: " << inset->xo() << "..."
137                                 << inset->xo() + inset->width()
138                                 << " yo: " << inset->yo() - inset->ascent()
139                                 << "..."
140                                 << inset->yo() + inset->descent()
141                                 << endl;
142                 else
143                         lyxerr[Debug::DEBUG]
144                                 << BOOST_CURRENT_FUNCTION
145                                 << ": inset has no cached position" << endl;
146 #endif
147                 if (inset->covers(x, y)) {
148                         lyxerr[Debug::DEBUG]
149                                 << BOOST_CURRENT_FUNCTION
150                                 << ": Hit inset: " << inset << endl;
151                         return inset;
152                 }
153         }
154         lyxerr[Debug::DEBUG]
155                 << BOOST_CURRENT_FUNCTION
156                 << ": No inset hit. " << endl;
157         return 0;
158 }
159
160
161
162 // Gets the fully instantiated font at a given position in a paragraph
163 // Basically the same routine as Paragraph::getFont() in paragraph.C.
164 // The difference is that this one is used for displaying, and thus we
165 // are allowed to make cosmetic improvements. For instance make footnotes
166 // smaller. (Asger)
167 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
168 {
169         BOOST_ASSERT(pos >= 0);
170
171         LyXLayout_ptr const & layout = par.layout();
172 #ifdef WITH_WARNINGS
173 #warning broken?
174 #endif
175         BufferParams const & params = bv()->buffer()->params();
176         pos_type const body_pos = par.beginOfBody();
177
178         // We specialize the 95% common case:
179         if (!par.getDepth()) {
180                 LyXFont f = par.getFontSettings(params, pos);
181                 if (!isMainText())
182                         applyOuterFont(f);
183                 LyXFont lf;
184                 LyXFont rlf;
185                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
186                         lf = layout->labelfont;
187                         rlf = layout->reslabelfont;
188                 } else {
189                         lf = layout->font;
190                         rlf = layout->resfont;
191                 }
192                 // In case the default family has been customized
193                 if (lf.family() == LyXFont::INHERIT_FAMILY)
194                         rlf.setFamily(params.getFont().family());
195                 return f.realize(rlf);
196         }
197
198         // The uncommon case need not be optimized as much
199         LyXFont layoutfont;
200         if (pos < body_pos)
201                 layoutfont = layout->labelfont;
202         else
203                 layoutfont = layout->font;
204
205         LyXFont font = par.getFontSettings(params, pos);
206         font.realize(layoutfont);
207
208         if (!isMainText())
209                 applyOuterFont(font);
210
211         // Find the pit value belonging to paragraph. This will not break
212         // even if pars_ would not be a vector anymore.
213         // Performance appears acceptable.
214
215         pit_type pit = pars_.size();
216         for (pit_type it = 0; it < pit; ++it)
217                 if (&pars_[it] == &par) {
218                         pit = it;
219                         break;
220                 }
221         // Realize against environment font information
222         // NOTE: the cast to pit_type should be removed when pit_type
223         // changes to a unsigned integer.
224         if (pit < pit_type(pars_.size()))
225                 font.realize(outerFont(pit, pars_));
226
227         // Realize with the fonts of lesser depth.
228         font.realize(params.getFont());
229
230         return font;
231 }
232
233 // There are currently two font mechanisms in LyX:
234 // 1. The font attributes in a lyxtext, and
235 // 2. The inset-specific font properties, defined in an inset's
236 // metrics() and draw() methods and handed down the inset chain through
237 // the pi/mi parameters, and stored locally in a lyxtext in font_.
238 // This is where the two are integrated in the final fully realized
239 // font.
240 void LyXText::applyOuterFont(LyXFont & font) const {
241         LyXFont lf(font_);
242         lf.reduce(bv()->buffer()->params().getFont());
243         lf.realize(font);
244         lf.setLanguage(font.language());
245         font = lf;
246 }
247
248
249 LyXFont LyXText::getLayoutFont(pit_type const pit) const
250 {
251         LyXLayout_ptr const & layout = pars_[pit].layout();
252
253         if (!pars_[pit].getDepth())  {
254                 LyXFont lf = layout->resfont;
255                 // In case the default family has been customized
256                 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
257                         lf.setFamily(bv()->buffer()->params().getFont().family());
258                 return lf;
259         }
260
261         LyXFont font = layout->font;
262         // Realize with the fonts of lesser depth.
263         //font.realize(outerFont(pit, paragraphs()));
264         font.realize(bv()->buffer()->params().getFont());
265
266         return font;
267 }
268
269
270 LyXFont LyXText::getLabelFont(Paragraph const & par) const
271 {
272         LyXLayout_ptr const & layout = par.layout();
273
274         if (!par.getDepth()) {
275                 LyXFont lf = layout->reslabelfont;
276                 // In case the default family has been customized
277                 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
278                         lf.setFamily(bv()->buffer()->params().getFont().family());
279                 return lf;
280         }
281
282         LyXFont font = layout->labelfont;
283         // Realize with the fonts of lesser depth.
284         font.realize(bv()->buffer()->params().getFont());
285
286         return font;
287 }
288
289
290 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
291 {
292         LyXFont font = fnt;
293         LyXLayout_ptr const & layout = pars_[pit].layout();
294
295         // Get concrete layout font to reduce against
296         LyXFont layoutfont;
297
298         if (pos < pars_[pit].beginOfBody())
299                 layoutfont = layout->labelfont;
300         else
301                 layoutfont = layout->font;
302
303         // Realize against environment font information
304         if (pars_[pit].getDepth()) {
305                 pit_type tp = pit;
306                 while (!layoutfont.resolved() &&
307                        tp != pit_type(paragraphs().size()) &&
308                        pars_[tp].getDepth()) {
309                         tp = outerHook(tp, paragraphs());
310                         if (tp != pit_type(paragraphs().size()))
311                                 layoutfont.realize(pars_[tp].layout()->font);
312                 }
313         }
314
315         // Inside inset, apply the inset's font attributes if any
316         // (charstyle!)
317         if (!isMainText())
318                 layoutfont.realize(font_);
319
320         layoutfont.realize(bv()->buffer()->params().getFont());
321
322         // Now, reduce font against full layout font
323         font.reduce(layoutfont);
324
325         pars_[pit].setFont(pos, font);
326 }
327
328
329 // return past-the-last paragraph influenced by a layout change on pit
330 pit_type LyXText::undoSpan(pit_type pit)
331 {
332         pit_type end = paragraphs().size();
333         pit_type nextpit = pit + 1;
334         if (nextpit == end)
335                 return nextpit;
336         //because of parindents
337         if (!pars_[pit].getDepth())
338                 return boost::next(nextpit);
339         //because of depth constrains
340         for (; nextpit != end; ++pit, ++nextpit) {
341                 if (!pars_[pit].getDepth())
342                         break;
343         }
344         return nextpit;
345 }
346
347
348 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
349 {
350         BOOST_ASSERT(start != end);
351
352         BufferParams const & bufparams = bv()->buffer()->params();
353         LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
354
355         for (pit_type pit = start; pit != end; ++pit) {
356                 pars_[pit].applyLayout(lyxlayout);
357                 if (lyxlayout->margintype == MARGIN_MANUAL)
358                         pars_[pit].setLabelWidthString(lyxlayout->labelstring());
359         }
360 }
361
362
363 // set layout over selection and make a total rebreak of those paragraphs
364 void LyXText::setLayout(LCursor & cur, string const & layout)
365 {
366         BOOST_ASSERT(this == cur.text());
367         // special handling of new environment insets
368         BufferView & bv = cur.bv();
369         BufferParams const & params = bv.buffer()->params();
370         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
371         if (lyxlayout->is_environment) {
372                 // move everything in a new environment inset
373                 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
374                 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_BEGIN));
375                 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_END_SELECT));
376                 theApp->lyxFunc().dispatch(FuncRequest(LFUN_CUT));
377                 InsetBase * inset = new InsetEnvironment(params, layout);
378                 insertInset(cur, inset);
379                 //inset->edit(cur, true);
380                 //theApp->lyxFunc().dispatch(FuncRequest(LFUN_PASTE));
381                 return;
382         }
383
384         pit_type start = cur.selBegin().pit();
385         pit_type end = cur.selEnd().pit() + 1;
386         pit_type undopit = undoSpan(end - 1);
387         recUndo(start, undopit - 1);
388         setLayout(start, end, layout);
389         updateLabels(cur.buffer());
390 }
391
392
393 namespace {
394
395
396 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
397                         Paragraph const & par, int max_depth)
398 {
399         if (par.layout()->labeltype == LABEL_BIBLIO)
400                 return false;
401         int const depth = par.params().depth();
402         if (type == LyXText::INC_DEPTH && depth < max_depth)
403                 return true;
404         if (type == LyXText::DEC_DEPTH && depth > 0)
405                 return true;
406         return false;
407 }
408
409
410 }
411
412
413 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
414 {
415         BOOST_ASSERT(this == cur.text());
416         // this happens when selecting several cells in tabular (bug 2630)
417         if (cur.selBegin().idx() != cur.selEnd().idx())
418                 return false;
419
420         pit_type const beg = cur.selBegin().pit();
421         pit_type const end = cur.selEnd().pit() + 1;
422         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
423
424         for (pit_type pit = beg; pit != end; ++pit) {
425                 if (::changeDepthAllowed(type, pars_[pit], max_depth))
426                         return true;
427                 max_depth = pars_[pit].getMaxDepthAfter();
428         }
429         return false;
430 }
431
432
433 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
434 {
435         BOOST_ASSERT(this == cur.text());
436         pit_type const beg = cur.selBegin().pit();
437         pit_type const end = cur.selEnd().pit() + 1;
438         recordUndoSelection(cur);
439         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
440
441         for (pit_type pit = beg; pit != end; ++pit) {
442                 Paragraph & par = pars_[pit];
443                 if (::changeDepthAllowed(type, par, max_depth)) {
444                         int const depth = par.params().depth();
445                         if (type == INC_DEPTH)
446                                 par.params().depth(depth + 1);
447                         else
448                                 par.params().depth(depth - 1);
449                 }
450                 max_depth = par.getMaxDepthAfter();
451         }
452         // this handles the counter labels, and also fixes up
453         // depth values for follow-on (child) paragraphs
454         updateLabels(cur.buffer());
455 }
456
457
458 // set font over selection
459 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
460 {
461         BOOST_ASSERT(this == cur.text());
462         // if there is no selection just set the current_font
463         if (!cur.selection()) {
464                 // Determine basis font
465                 LyXFont layoutfont;
466                 pit_type pit = cur.pit();
467                 if (cur.pos() < pars_[pit].beginOfBody())
468                         layoutfont = getLabelFont(pars_[pit]);
469                 else
470                         layoutfont = getLayoutFont(pit);
471
472                 // Update current font
473                 real_current_font.update(font,
474                                          cur.buffer().params().language,
475                                          toggleall);
476
477                 // Reduce to implicit settings
478                 current_font = real_current_font;
479                 current_font.reduce(layoutfont);
480                 // And resolve it completely
481                 real_current_font.realize(layoutfont);
482
483                 return;
484         }
485
486         // Ok, we have a selection.
487         recordUndoSelection(cur);
488
489         DocIterator dit = cur.selectionBegin();
490         DocIterator ditend = cur.selectionEnd();
491
492         BufferParams const & params = cur.buffer().params();
493
494         // Don't use forwardChar here as ditend might have
495         // pos() == lastpos() and forwardChar would miss it.
496         // Can't use forwardPos either as this descends into
497         // nested insets.
498         for (; dit != ditend; dit.forwardPosNoDescend()) {
499                 if (dit.pos() != dit.lastpos()) {
500                         LyXFont f = getFont(dit.paragraph(), dit.pos());
501                         f.update(font, params.language, toggleall);
502                         setCharFont(dit.pit(), dit.pos(), f);
503                 }
504         }
505 }
506
507
508 // the cursor set functions have a special mechanism. When they
509 // realize you left an empty paragraph, they will delete it.
510
511 bool LyXText::cursorHome(LCursor & cur)
512 {
513         BOOST_ASSERT(this == cur.text());
514         Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
515
516         return setCursor(cur, cur.pit(), row.pos());
517 }
518
519
520 bool LyXText::cursorEnd(LCursor & cur)
521 {
522         BOOST_ASSERT(this == cur.text());
523         // if not on the last row of the par, put the cursor before
524         // the final space exept if I have a spanning inset or one string
525         // is so long that we force a break.
526         pos_type end = cur.textRow().endpos();
527         if (end == 0)
528                 // empty text, end-1 is no valid position
529                 return false;
530         bool boundary = false;
531         if (end != cur.lastpos()) {
532                 if (!cur.paragraph().isLineSeparator(end-1)
533                     && !cur.paragraph().isNewline(end-1))
534                         boundary = true;
535                 else
536                         --end;
537         }
538         return setCursor(cur, cur.pit(), end, true, boundary);
539 }
540
541
542 bool LyXText::cursorTop(LCursor & cur)
543 {
544         BOOST_ASSERT(this == cur.text());
545         return setCursor(cur, 0, 0);
546 }
547
548
549 bool LyXText::cursorBottom(LCursor & cur)
550 {
551         BOOST_ASSERT(this == cur.text());
552         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
553 }
554
555
556 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
557 {
558         BOOST_ASSERT(this == cur.text());
559         // If the mask is completely neutral, tell user
560         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
561                 // Could only happen with user style
562                 cur.message(_("No font change defined. "
563                                            "Use Character under the Layout menu to define font change."));
564                 return;
565         }
566
567         // Try implicit word selection
568         // If there is a change in the language the implicit word selection
569         // is disabled.
570         CursorSlice resetCursor = cur.top();
571         bool implicitSelection =
572                 font.language() == ignore_language
573                 && font.number() == LyXFont::IGNORE
574                 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
575
576         // Set font
577         setFont(cur, font, toggleall);
578
579         // Implicit selections are cleared afterwards
580         // and cursor is set to the original position.
581         if (implicitSelection) {
582                 cur.clearSelection();
583                 cur.top() = resetCursor;
584                 cur.resetAnchor();
585         }
586 }
587
588
589 string LyXText::getStringToIndex(LCursor const & cur)
590 {
591         BOOST_ASSERT(this == cur.text());
592
593         docstring idxstring;
594         if (cur.selection())
595                 idxstring = cur.selectionAsString(false);
596         else {
597                 // Try implicit word selection. If there is a change
598                 // in the language the implicit word selection is
599                 // disabled.
600                 LCursor tmpcur = cur;
601                 selectWord(tmpcur, lyx::PREVIOUS_WORD);
602
603                 if (!tmpcur.selection())
604                         cur.message(_("Nothing to index!"));
605                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
606                         cur.message(_("Cannot index more than one paragraph!"));
607                 else
608                         idxstring = tmpcur.selectionAsString(false);
609         }
610
611         return lyx::to_utf8(idxstring);
612 }
613
614
615 void LyXText::setParagraph(LCursor & cur,
616                            Spacing const & spacing, LyXAlignment align,
617                            string const & labelwidthstring, bool noindent)
618 {
619         BOOST_ASSERT(cur.text());
620         // make sure that the depth behind the selection are restored, too
621         pit_type undopit = undoSpan(cur.selEnd().pit());
622         recUndo(cur.selBegin().pit(), undopit - 1);
623
624         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
625              pit <= end; ++pit) {
626                 Paragraph & par = pars_[pit];
627                 ParagraphParameters & params = par.params();
628                 params.spacing(spacing);
629
630                 // does the layout allow the new alignment?
631                 LyXLayout_ptr const & layout = par.layout();
632
633                 if (align == LYX_ALIGN_LAYOUT)
634                         align = layout->align;
635                 if (align & layout->alignpossible) {
636                         if (align == layout->align)
637                                 params.align(LYX_ALIGN_LAYOUT);
638                         else
639                                 params.align(align);
640                 }
641                 par.setLabelWidthString(labelwidthstring);
642                 params.noindent(noindent);
643         }
644 }
645
646
647 // this really should just insert the inset and not move the cursor.
648 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
649 {
650         BOOST_ASSERT(this == cur.text());
651         BOOST_ASSERT(inset);
652         cur.paragraph().insertInset(cur.pos(), inset);
653 }
654
655
656 // needed to insert the selection
657 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
658 {
659         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
660                                          current_font, str, autoBreakRows_);
661 }
662
663
664 // turn double CR to single CR, others are converted into one
665 // blank. Then insertStringAsLines is called
666 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
667 {
668         docstring linestr = str;
669         bool newline_inserted = false;
670
671         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
672                 if (linestr[i] == '\n') {
673                         if (newline_inserted) {
674                                 // we know that \r will be ignored by
675                                 // insertStringAsLines. Of course, it is a dirty
676                                 // trick, but it works...
677                                 linestr[i - 1] = '\r';
678                                 linestr[i] = '\n';
679                         } else {
680                                 linestr[i] = ' ';
681                                 newline_inserted = true;
682                         }
683                 } else if (isPrintable(linestr[i])) {
684                         newline_inserted = false;
685                 }
686         }
687         insertStringAsLines(cur, linestr);
688 }
689
690
691 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
692                         bool setfont, bool boundary)
693 {
694         LCursor old = cur;
695         setCursorIntern(cur, par, pos, setfont, boundary);
696         return deleteEmptyParagraphMechanism(cur, old);
697 }
698
699
700 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
701 {
702         BOOST_ASSERT(par != int(paragraphs().size()));
703         cur.pit() = par;
704         cur.pos() = pos;
705
706         // now some strict checking
707         Paragraph & para = getPar(par);
708
709         // None of these should happen, but we're scaredy-cats
710         if (pos < 0) {
711                 lyxerr << "dont like -1" << endl;
712                 BOOST_ASSERT(false);
713         }
714
715         if (pos > para.size()) {
716                 lyxerr << "dont like 1, pos: " << pos
717                        << " size: " << para.size()
718                        << " par: " << par << endl;
719                 BOOST_ASSERT(false);
720         }
721 }
722
723
724 void LyXText::setCursorIntern(LCursor & cur,
725                               pit_type par, pos_type pos, bool setfont, bool boundary)
726 {
727         BOOST_ASSERT(this == cur.text());
728         cur.boundary(boundary);
729         setCursor(cur.top(), par, pos);
730         cur.setTargetX();
731         if (setfont)
732                 setCurrentFont(cur);
733 }
734
735
736 void LyXText::setCurrentFont(LCursor & cur)
737 {
738         BOOST_ASSERT(this == cur.text());
739         pos_type pos = cur.pos();
740         Paragraph & par = cur.paragraph();
741
742         if (cur.boundary() && pos > 0)
743                 --pos;
744
745         if (pos > 0) {
746                 if (pos == cur.lastpos())
747                         --pos;
748                 else // potentional bug... BUG (Lgb)
749                         if (par.isSeparator(pos)) {
750                                 if (pos > cur.textRow().pos() &&
751                                     bidi.level(pos) % 2 ==
752                                     bidi.level(pos - 1) % 2)
753                                         --pos;
754                                 else if (pos + 1 < cur.lastpos())
755                                         ++pos;
756                         }
757         }
758
759         BufferParams const & bufparams = cur.buffer().params();
760         current_font = par.getFontSettings(bufparams, pos);
761         real_current_font = getFont(par, pos);
762
763         if (cur.pos() == cur.lastpos()
764             && bidi.isBoundary(cur.buffer(), par, cur.pos())
765             && !cur.boundary()) {
766                 Language const * lang = par.getParLanguage(bufparams);
767                 current_font.setLanguage(lang);
768                 current_font.setNumber(LyXFont::OFF);
769                 real_current_font.setLanguage(lang);
770                 real_current_font.setNumber(LyXFont::OFF);
771         }
772 }
773
774
775 // x is an absolute screen coord
776 // returns the column near the specified x-coordinate of the row
777 // x is set to the real beginning of this column
778 pos_type LyXText::getColumnNearX(pit_type const pit,
779                                  Row const & row, int & x, bool & boundary) const
780 {
781         int const xo = theCoords.get(this, pit).x_;
782         x -= xo;
783         RowMetrics const r = computeRowMetrics(pit, row);
784         Paragraph const & par = pars_[pit];
785
786         pos_type vc = row.pos();
787         pos_type end = row.endpos();
788         pos_type c = 0;
789         LyXLayout_ptr const & layout = par.layout();
790
791         bool left_side = false;
792
793         pos_type body_pos = par.beginOfBody();
794
795         double tmpx = r.x;
796         double last_tmpx = tmpx;
797
798         if (body_pos > 0 &&
799             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
800                 body_pos = 0;
801
802         // check for empty row
803         if (vc == end) {
804                 x = int(tmpx) + xo;
805                 return 0;
806         }
807
808         lyx::frontend::FontMetrics const & fm = theApp->fontLoader().metrics(
809                 getLabelFont(par));
810
811         while (vc < end && tmpx <= x) {
812                 c = bidi.vis2log(vc);
813                 last_tmpx = tmpx;
814                 if (body_pos > 0 && c == body_pos - 1) {
815                         string lsep = layout->labelsep;
816                         docstring dlsep(lsep.begin(), lsep.end());
817                         tmpx += r.label_hfill + fm.width(dlsep);
818                         if (par.isLineSeparator(body_pos - 1))
819                                 tmpx -= singleWidth(par, body_pos - 1);
820                 }
821
822                 if (hfillExpansion(par, row, c)) {
823                         tmpx += singleWidth(par, c);
824                         if (c >= body_pos)
825                                 tmpx += r.hfill;
826                         else
827                                 tmpx += r.label_hfill;
828                 } else if (par.isSeparator(c)) {
829                         tmpx += singleWidth(par, c);
830                         if (c >= body_pos)
831                                 tmpx += r.separator;
832                 } else {
833                         tmpx += singleWidth(par, c);
834                 }
835                 ++vc;
836         }
837
838         if ((tmpx + last_tmpx) / 2 > x) {
839                 tmpx = last_tmpx;
840                 left_side = true;
841         }
842
843         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
844
845         boundary = false;
846         // This (rtl_support test) is not needed, but gives
847         // some speedup if rtl_support == false
848         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
849
850         // If lastrow is false, we don't need to compute
851         // the value of rtl.
852         bool const rtl = lastrow ? isRTL(par) : false;
853         if (lastrow &&
854             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
855              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
856                 c = end;
857         else if (vc == row.pos()) {
858                 c = bidi.vis2log(vc);
859                 if (bidi.level(c) % 2 == 1)
860                         ++c;
861         } else {
862                 c = bidi.vis2log(vc - 1);
863                 bool const rtl = (bidi.level(c) % 2 == 1);
864                 if (left_side == rtl) {
865                         ++c;
866                         boundary = bidi.isBoundary(*bv()->buffer(), par, c);
867                 }
868         }
869
870 // I believe this code is not needed anymore (Jug 20050717)
871 #if 0
872         // The following code is necessary because the cursor position past
873         // the last char in a row is logically equivalent to that before
874         // the first char in the next row. That's why insets causing row
875         // divisions -- Newline and display-style insets -- must be treated
876         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
877         // Newline inset, air gap below:
878         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
879                 if (bidi.level(end -1) % 2 == 0)
880                         tmpx -= singleWidth(par, end - 1);
881                 else
882                         tmpx += singleWidth(par, end - 1);
883                 c = end - 1;
884         }
885
886         // Air gap above display inset:
887         if (row.pos() < end && c >= end && end < par.size()
888             && par.isInset(end) && par.getInset(end)->display()) {
889                 c = end - 1;
890         }
891         // Air gap below display inset:
892         if (row.pos() < end && c >= end && par.isInset(end - 1)
893             && par.getInset(end - 1)->display()) {
894                 c = end - 1;
895         }
896 #endif
897
898         x = int(tmpx) + xo;
899         pos_type const col = c - row.pos();
900
901         if (!c || end == par.size())
902                 return col;
903
904         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
905                 boundary = true;
906                 return col;
907         }
908
909         return min(col, end - 1 - row.pos());
910 }
911
912
913 // y is screen coordinate
914 pit_type LyXText::getPitNearY(int y) const
915 {
916         BOOST_ASSERT(!paragraphs().empty());
917         BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
918         CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
919         lyxerr[Debug::DEBUG]
920                 << BOOST_CURRENT_FUNCTION
921                 << ": y: " << y << " cache size: " << cc.size()
922                 << endl;
923
924         // look for highest numbered paragraph with y coordinate less than given y
925         pit_type pit = 0;
926         int yy = -1;
927         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
928         CoordCache::InnerParPosCache::const_iterator et = cc.end();
929         for (; it != et; ++it) {
930                 lyxerr[Debug::DEBUG]
931                         << BOOST_CURRENT_FUNCTION
932                         << "  examining: pit: " << it->first
933                         << " y: " << it->second.y_
934                         << endl;
935
936                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
937                         pit = it->first;
938                         yy = it->second.y_;
939                 }
940         }
941
942         lyxerr[Debug::DEBUG]
943                 << BOOST_CURRENT_FUNCTION
944                 << ": found best y: " << yy << " for pit: " << pit
945                 << endl;
946
947         return pit;
948 }
949
950
951 Row const & LyXText::getRowNearY(int y, pit_type pit) const
952 {
953         Paragraph const & par = pars_[pit];
954         int yy = theCoords.get(this, pit).y_ - par.ascent();
955         BOOST_ASSERT(!par.rows().empty());
956         RowList::const_iterator rit = par.rows().begin();
957         RowList::const_iterator const rlast = boost::prior(par.rows().end());
958         for (; rit != rlast; yy += rit->height(), ++rit)
959                 if (yy + rit->height() > y)
960                         break;
961         return *rit;
962 }
963
964
965 // x,y are absolute screen coordinates
966 // sets cursor recursively descending into nested editable insets
967 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
968 {
969         pit_type pit = getPitNearY(y);
970         BOOST_ASSERT(pit != -1);
971         Row const & row = getRowNearY(y, pit);
972         bool bound = false;
973
974         int xx = x; // is modified by getColumnNearX
975         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
976         cur.pit() = pit;
977         cur.pos() = pos;
978         cur.boundary(bound);
979         cur.x_target() = x;
980
981         // try to descend into nested insets
982         InsetBase * inset = checkInsetHit(x, y);
983         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
984         if (!inset) {
985                 // Either we deconst editXY or better we move current_font
986                 // and real_current_font to LCursor
987                 setCurrentFont(cur);
988                 return 0;
989         }
990
991         // This should be just before or just behind the
992         // cursor position set above.
993         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
994                      || inset == pars_[pit].getInset(pos));
995         // Make sure the cursor points to the position before
996         // this inset.
997         if (inset == pars_[pit].getInset(pos - 1))
998                 --cur.pos();
999         inset = inset->editXY(cur, x, y);
1000         if (cur.top().text() == this)
1001                 setCurrentFont(cur);
1002         return inset;
1003 }
1004
1005
1006 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1007 {
1008         if (cur.selection())
1009                 return false;
1010         if (cur.pos() == cur.lastpos())
1011                 return false;
1012         InsetBase * inset = cur.nextInset();
1013         if (!isHighlyEditableInset(inset))
1014                 return false;
1015         inset->edit(cur, front);
1016         return true;
1017 }
1018
1019
1020 bool LyXText::cursorLeft(LCursor & cur)
1021 {
1022         if (!cur.boundary() && cur.pos() > 0 &&
1023             cur.textRow().pos() == cur.pos() &&
1024             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1025             !cur.paragraph().isNewline(cur.pos()-1)) {
1026                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1027         }
1028         if (cur.pos() != 0) {
1029                 bool boundary = cur.boundary();
1030                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1031                 if (!checkAndActivateInset(cur, false)) {
1032                         if (false && !boundary &&
1033                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1034                                 updateNeeded |=
1035                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1036                 }
1037                 return updateNeeded;
1038         }
1039
1040         if (cur.pit() != 0) {
1041                 // Steps into the paragraph above
1042                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1043         }
1044         return false;
1045 }
1046
1047
1048 bool LyXText::cursorRight(LCursor & cur)
1049 {
1050         if (cur.pos() != cur.lastpos()) {
1051                 if (cur.boundary())
1052                         return setCursor(cur, cur.pit(), cur.pos(),
1053                                          true, false);
1054
1055                 bool updateNeeded = false;
1056                 if (!checkAndActivateInset(cur, true)) {
1057                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1058                             cur.textRow().endpos() != cur.lastpos() &&
1059                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1060                             !cur.paragraph().isNewline(cur.pos())) {
1061                                 cur.boundary(true);
1062                         }
1063                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1064                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1065                                                      cur.pos()))
1066                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1067                 }
1068                 return updateNeeded;
1069         }
1070
1071         if (cur.pit() != cur.lastpit())
1072                 return setCursor(cur, cur.pit() + 1, 0);
1073         return false;
1074 }
1075
1076
1077 bool LyXText::cursorUp(LCursor & cur)
1078 {
1079         Paragraph const & par = cur.paragraph();
1080         int row;
1081         int const x = cur.targetX();
1082
1083         if (cur.pos() && cur.boundary())
1084                 row = par.pos2row(cur.pos()-1);
1085         else
1086                 row = par.pos2row(cur.pos());
1087
1088         if (!cur.selection()) {
1089                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1090                 LCursor old = cur;
1091                 // Go to middle of previous row. 16 found to work OK;
1092                 // 12 = top/bottom margin of display math
1093                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1094                 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1095                 cur.clearSelection();
1096
1097                 // This happens when you move out of an inset.
1098                 // And to give the DEPM the possibility of doing
1099                 // something we must provide it with two different
1100                 // cursors. (Lgb)
1101                 LCursor dummy = cur;
1102                 if (dummy == old)
1103                         ++dummy.pos();
1104
1105                 return deleteEmptyParagraphMechanism(dummy, old);
1106         }
1107
1108         bool updateNeeded = false;
1109
1110         if (row > 0) {
1111                 updateNeeded |= setCursor(cur, cur.pit(),
1112                                           x2pos(cur.pit(), row - 1, x));
1113         } else if (cur.pit() > 0) {
1114                 --cur.pit();
1115                 //cannot use 'par' now
1116                 updateNeeded |= setCursor(cur, cur.pit(),
1117                                           x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1118         }
1119
1120         cur.x_target() = x;
1121
1122         return updateNeeded;
1123 }
1124
1125
1126 bool LyXText::cursorDown(LCursor & cur)
1127 {
1128         Paragraph const & par = cur.paragraph();
1129         int row;
1130         int const x = cur.targetX();
1131
1132         if (cur.pos() && cur.boundary())
1133                 row = par.pos2row(cur.pos()-1);
1134         else
1135                 row = par.pos2row(cur.pos());
1136
1137         if (!cur.selection()) {
1138                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1139                 LCursor old = cur;
1140                 // To middle of next row
1141                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1142                 editXY(cur, x, y + par.rows()[row].descent() + margin);
1143                 cur.clearSelection();
1144
1145                 // This happens when you move out of an inset.
1146                 // And to give the DEPM the possibility of doing
1147                 // something we must provide it with two different
1148                 // cursors. (Lgb)
1149                 LCursor dummy = cur;
1150                 if (dummy == old)
1151                         ++dummy.pos();
1152
1153                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1154
1155                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1156                 if (changed)
1157                         cur = dummy;
1158
1159                 return changed;
1160         }
1161
1162         bool updateNeeded = false;
1163
1164         if (row + 1 < int(par.rows().size())) {
1165                 updateNeeded |= setCursor(cur, cur.pit(),
1166                                           x2pos(cur.pit(), row + 1, x));
1167         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1168                 ++cur.pit();
1169                 updateNeeded |= setCursor(cur, cur.pit(),
1170                                           x2pos(cur.pit(), 0, x));
1171         }
1172
1173         cur.x_target() = x;
1174
1175         return updateNeeded;
1176 }
1177
1178
1179 bool LyXText::cursorUpParagraph(LCursor & cur)
1180 {
1181         bool updated = false;
1182         if (cur.pos() > 0)
1183                 updated = setCursor(cur, cur.pit(), 0);
1184         else if (cur.pit() != 0)
1185                 updated = setCursor(cur, cur.pit() - 1, 0);
1186         return updated;
1187 }
1188
1189
1190 bool LyXText::cursorDownParagraph(LCursor & cur)
1191 {
1192         bool updated = false;
1193         if (cur.pit() != cur.lastpit())
1194                 updated = setCursor(cur, cur.pit() + 1, 0);
1195         else
1196                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1197         return updated;
1198 }
1199
1200
1201 // fix the cursor `cur' after a characters has been deleted at `where'
1202 // position. Called by deleteEmptyParagraphMechanism
1203 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1204 {
1205         // Do nothing if cursor is not in the paragraph where the
1206         // deletion occured,
1207         if (cur.pit() != where.pit())
1208                 return;
1209
1210         // If cursor position is after the deletion place update it
1211         if (cur.pos() > where.pos())
1212                 --cur.pos();
1213
1214         // Check also if we don't want to set the cursor on a spot behind the
1215         // pagragraph because we erased the last character.
1216         if (cur.pos() > cur.lastpos())
1217                 cur.pos() = cur.lastpos();
1218 }
1219
1220
1221 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1222 {
1223         // Would be wrong to delete anything if we have a selection.
1224         if (cur.selection())
1225                 return false;
1226
1227         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1228         // old should point to us
1229         BOOST_ASSERT(old.text() == this);
1230
1231         Paragraph & oldpar = old.paragraph();
1232
1233         // We allow all kinds of "mumbo-jumbo" when freespacing.
1234         if (oldpar.isFreeSpacing())
1235                 return false;
1236
1237         /* Ok I'll put some comments here about what is missing.
1238            There are still some small problems that can lead to
1239            double spaces stored in the document file or space at
1240            the beginning of paragraphs(). This happens if you have
1241            the cursor between to spaces and then save. Or if you
1242            cut and paste and the selection have a space at the
1243            beginning and then save right after the paste. (Lgb)
1244         */
1245
1246         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1247         // delete the LineSeparator.
1248         // MISSING
1249
1250         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1251         // delete the LineSeparator.
1252         // MISSING
1253
1254         bool const same_inset = &old.inset() == &cur.inset();
1255         bool const same_par = same_inset && old.pit() == cur.pit();
1256         bool const same_par_pos = same_par && old.pos() == cur.pos();
1257
1258         // If the chars around the old cursor were spaces, delete one of them.
1259         if (!same_par_pos) {
1260                 // Only if the cursor has really moved.
1261                 if (old.pos() > 0
1262                     && old.pos() < oldpar.size()
1263                     && oldpar.isLineSeparator(old.pos())
1264                     && oldpar.isLineSeparator(old.pos() - 1)
1265                     && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1266                         // We need to set the text to Change::INSERTED to
1267                         // get it erased properly
1268                         oldpar.setChange(old.pos() -1, Change::INSERTED);
1269                         oldpar.erase(old.pos() - 1);
1270 #ifdef WITH_WARNINGS
1271 #warning This will not work anymore when we have multiple views of the same buffer
1272 // In this case, we will have to correct also the cursors held by
1273 // other bufferviews. It will probably be easier to do that in a more
1274 // automated way in CursorSlice code. (JMarc 26/09/2001)
1275 #endif
1276                         // correct all cursor parts
1277                         if (same_par) {
1278                                 fixCursorAfterDelete(cur.top(), old.top());
1279                                 cur.resetAnchor();
1280                         }
1281                         return true;
1282                 }
1283         }
1284
1285         // only do our magic if we changed paragraph
1286         if (same_par)
1287                 return false;
1288
1289         // don't delete anything if this is the ONLY paragraph!
1290         if (old.lastpit() == 0)
1291                 return false;
1292
1293         // Do not delete empty paragraphs with keepempty set.
1294         if (oldpar.allowEmpty())
1295                 return false;
1296
1297         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1298                 // Delete old par.
1299                 recordUndo(old, Undo::ATOMIC,
1300                            max(old.pit() - 1, pit_type(0)),
1301                            min(old.pit() + 1, old.lastpit()));
1302                 ParagraphList & plist = old.text()->paragraphs();
1303                 plist.erase(boost::next(plist.begin(), old.pit()));
1304
1305                 // see #warning above
1306                 if (cur.depth() >= old.depth()) {
1307                         CursorSlice & curslice = cur[old.depth() - 1];
1308                         if (&curslice.inset() == &old.inset()
1309                             && curslice.pit() > old.pit()) {
1310                                 --curslice.pit();
1311                                 // since a paragraph has been deleted, all the
1312                                 // insets after `old' have been copied and
1313                                 // their address has changed. Therefore we
1314                                 // need to `regenerate' cur. (JMarc)
1315                                 cur.updateInsets(&(cur.bottom().inset()));
1316                                 cur.resetAnchor();
1317                         }
1318                 }
1319                 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1320                 //ParIterator par_it(old);
1321                 //updateLabels(old.buffer(), par_it);
1322                 // So for now we do the full update:
1323                 updateLabels(old.buffer());
1324                 return true;
1325         }
1326
1327         if (oldpar.stripLeadingSpaces())
1328                 cur.resetAnchor();
1329
1330         return false;
1331 }
1332
1333
1334 void LyXText::recUndo(pit_type first, pit_type last) const
1335 {
1336         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1337 }
1338
1339
1340 void LyXText::recUndo(pit_type par) const
1341 {
1342         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1343 }
1344
1345
1346 int defaultRowHeight()
1347 {
1348         return int(theApp->fontLoader().metrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() *  1.2);
1349 }