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