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