]> git.lyx.org Git - lyx.git/blob - src/paragraph_funcs.cpp
Move Inset::Code to InsetCode.h
[lyx.git] / src / paragraph_funcs.cpp
1 /**
2  * \file paragraph_funcs.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "paragraph_funcs.h"
14
15 #include "BufferParams.h"
16 #include "debug.h"
17 #include "Layout.h"
18 #include "Text.h"
19 #include "Paragraph.h"
20 #include "ParagraphParameters.h"
21
22 #include <boost/next_prior.hpp>
23
24
25 namespace lyx {
26
27 using std::endl;
28
29
30 static bool moveItem(Paragraph & fromPar, pos_type fromPos,
31         Paragraph & toPar, pos_type toPos, BufferParams const & params)
32 {
33         // Note: moveItem() does not honour change tracking!
34         // Therefore, it should only be used for breaking and merging paragraphs
35
36         Paragraph::value_type const tmpChar = fromPar.getChar(fromPos);
37         Font const tmpFont = fromPar.getFontSettings(params, fromPos);
38         Change const tmpChange = fromPar.lookupChange(fromPos);
39
40         if (tmpChar == Paragraph::META_INSET) {
41                 Inset * tmpInset = 0;
42                 if (fromPar.getInset(fromPos)) {
43                         // the inset is not in the paragraph any more
44                         tmpInset = fromPar.insetlist.release(fromPos);
45                 }
46
47                 fromPar.eraseChar(fromPos, false);
48
49                 if (!toPar.insetAllowed(tmpInset->lyxCode())) {
50                         delete tmpInset;
51                         return false;
52                 }
53
54                 toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange);
55         } else {
56                 fromPar.eraseChar(fromPos, false);
57                 toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
58         }
59
60         return true;
61 }
62
63
64 void breakParagraph(BufferParams const & bparams,
65                     ParagraphList & pars, pit_type par_offset, pos_type pos, 
66                     bool keep_layout)
67 {
68         // create a new paragraph, and insert into the list
69         ParagraphList::iterator tmp =
70                 pars.insert(boost::next(pars.begin(), par_offset + 1),
71                             Paragraph());
72
73         Paragraph & par = pars[par_offset];
74
75         // without doing that we get a crash when typing <Return> at the
76         // end of a paragraph
77         tmp->layout(bparams.getTextClass().defaultLayout());
78         // remember to set the inset_owner
79         tmp->setInsetOwner(par.inInset());
80
81         // layout stays the same with latex-environments
82         if (keep_layout) {
83                 tmp->layout(par.layout());
84                 tmp->setLabelWidthString(par.params().labelWidthString());
85                 tmp->params().depth(par.params().depth());
86         } else if (par.params().depth() > 0) {
87                 Paragraph const & hook = pars[outerHook(par_offset, pars)];
88                 tmp->layout(hook.layout());
89                 // not sure the line below is useful
90                 tmp->setLabelWidthString(par.params().labelWidthString());
91                 tmp->params().depth(hook.params().depth());
92         }
93
94         bool const isempty = (par.allowEmpty() && par.empty());
95
96         if (!isempty && (par.size() > pos || par.empty())) {
97                 tmp->layout(par.layout());
98                 tmp->params().align(par.params().align());
99                 tmp->setLabelWidthString(par.params().labelWidthString());
100
101                 tmp->params().depth(par.params().depth());
102                 tmp->params().noindent(par.params().noindent());
103
104                 // move everything behind the break position
105                 // to the new paragraph
106
107                 /* Note: if !keepempty, empty() == true, then we reach
108                  * here with size() == 0. So pos_end becomes - 1. This
109                  * doesn't cause problems because both loops below
110                  * enforce pos <= pos_end and 0 <= pos
111                  */
112                 pos_type pos_end = par.size() - 1;
113
114                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
115                         if (moveItem(par, pos, *tmp, j, bparams)) {
116                                 ++j;
117                         }
118                 }
119         }
120
121         // Move over the end-of-par change information
122         tmp->setChange(tmp->size(), par.lookupChange(par.size()));
123         par.setChange(par.size(), Change(bparams.trackChanges ?
124                                            Change::INSERTED : Change::UNCHANGED));
125
126         if (pos) {
127                 // Make sure that we keep the language when
128                 // breaking paragraph.
129                 if (tmp->empty()) {
130                         Font changed = tmp->getFirstFontSettings(bparams);
131                         Font old = par.getFontSettings(bparams, par.size());
132                         changed.setLanguage(old.language());
133                         tmp->setFont(0, changed);
134                 }
135
136                 return;
137         }
138
139         if (!isempty) {
140                 bool const soa = par.params().startOfAppendix();
141                 par.params().clear();
142                 // do not lose start of appendix marker (bug 4212)
143                 par.params().startOfAppendix(soa);
144                 par.layout(bparams.getTextClass().defaultLayout());
145         }
146
147         // layout stays the same with latex-environments
148         if (keep_layout) {
149                 par.layout(tmp->layout());
150                 par.setLabelWidthString(tmp->params().labelWidthString());
151                 par.params().depth(tmp->params().depth());
152         }
153 }
154
155
156 void breakParagraphConservative(BufferParams const & bparams,
157         ParagraphList & pars, pit_type par_offset, pos_type pos)
158 {
159         // create a new paragraph
160         Paragraph & tmp = *pars.insert(boost::next(pars.begin(), par_offset + 1),
161                                        Paragraph());
162         Paragraph & par = pars[par_offset];
163
164         tmp.makeSameLayout(par);
165
166         BOOST_ASSERT(pos <= par.size());
167
168         if (pos < par.size()) {
169                 // move everything behind the break position to the new paragraph
170                 pos_type pos_end = par.size() - 1;
171
172                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
173                         if (moveItem(par, pos, tmp, j, bparams)) {
174                                 ++j;
175                         }
176                 }
177                 // Move over the end-of-par change information
178                 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
179                 par.setChange(par.size(), Change(bparams.trackChanges ?
180                                            Change::INSERTED : Change::UNCHANGED));
181         }
182 }
183
184
185 void mergeParagraph(BufferParams const & bparams,
186         ParagraphList & pars, pit_type par_offset)
187 {
188         Paragraph & next = pars[par_offset + 1];
189         Paragraph & par = pars[par_offset];
190
191         pos_type pos_end = next.size() - 1;
192         pos_type pos_insert = par.size();
193
194         // the imaginary end-of-paragraph character (at par.size()) has to be
195         // marked as unmodified. Otherwise, its change is adopted by the first
196         // character of the next paragraph.
197         if (par.lookupChange(par.size()).type != Change::UNCHANGED) {
198                 LYXERR(Debug::CHANGES) <<
199                    "merging par with inserted/deleted end-of-par character" << endl;
200                 par.setChange(par.size(), Change(Change::UNCHANGED));
201         }
202
203         Change change = next.lookupChange(next.size());
204
205         // move the content of the second paragraph to the end of the first one
206         for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
207                 if (moveItem(next, 0, par, j, bparams)) {
208                         ++j;
209                 }
210         }
211
212         // move the change of the end-of-paragraph character
213         par.setChange(par.size(), change);
214
215         pars.erase(boost::next(pars.begin(), par_offset + 1));
216 }
217
218
219 pit_type depthHook(pit_type pit, ParagraphList const & pars, depth_type depth)
220 {
221         pit_type newpit = pit;
222
223         if (newpit != 0)
224                 --newpit;
225
226         while (newpit != 0 && pars[newpit].getDepth() > depth)
227                 --newpit;
228
229         if (pars[newpit].getDepth() > depth)
230                 return pit;
231
232         return newpit;
233 }
234
235
236 pit_type outerHook(pit_type par_offset, ParagraphList const & pars)
237 {
238         Paragraph const & par = pars[par_offset];
239
240         if (par.getDepth() == 0)
241                 return pars.size();
242         return depthHook(par_offset, pars, depth_type(par.getDepth() - 1));
243 }
244
245
246 bool isFirstInSequence(pit_type par_offset, ParagraphList const & pars)
247 {
248         Paragraph const & par = pars[par_offset];
249
250         pit_type dhook_offset = depthHook(par_offset, pars, par.getDepth());
251
252         if (dhook_offset == par_offset)
253                 return true;
254
255         Paragraph const & dhook = pars[dhook_offset];
256
257         return dhook.layout() != par.layout()
258                 || dhook.getDepth() != par.getDepth();
259 }
260
261
262 int getEndLabel(pit_type p, ParagraphList const & pars)
263 {
264         pit_type pit = p;
265         depth_type par_depth = pars[p].getDepth();
266         while (pit != pit_type(pars.size())) {
267                 LayoutPtr const & layout = pars[pit].layout();
268                 int const endlabeltype = layout->endlabeltype;
269
270                 if (endlabeltype != END_LABEL_NO_LABEL) {
271                         if (p + 1 == pit_type(pars.size()))
272                                 return endlabeltype;
273
274                         depth_type const next_depth =
275                                 pars[p + 1].getDepth();
276                         if (par_depth > next_depth ||
277                             (par_depth == next_depth && layout != pars[p + 1].layout()))
278                                 return endlabeltype;
279                         break;
280                 }
281                 if (par_depth == 0)
282                         break;
283                 pit = outerHook(pit, pars);
284                 if (pit != pit_type(pars.size()))
285                         par_depth = pars[pit].getDepth();
286         }
287         return END_LABEL_NO_LABEL;
288 }
289
290
291 Font const outerFont(pit_type par_offset, ParagraphList const & pars)
292 {
293         depth_type par_depth = pars[par_offset].getDepth();
294         Font tmpfont(Font::ALL_INHERIT);
295
296         // Resolve against environment font information
297         while (par_offset != pit_type(pars.size())
298                && par_depth
299                && !tmpfont.resolved()) {
300                 par_offset = outerHook(par_offset, pars);
301                 if (par_offset != pit_type(pars.size())) {
302                         tmpfont.realize(pars[par_offset].layout()->font);
303                         par_depth = pars[par_offset].getDepth();
304                 }
305         }
306
307         return tmpfont;
308 }
309
310
311 /// return the number of InsetOptArg in a paragraph
312 int numberOfOptArgs(Paragraph const & par)
313 {
314         int num = 0;
315
316         InsetList::const_iterator it = par.insetlist.begin();
317         InsetList::const_iterator end = par.insetlist.end();
318         for (; it != end ; ++it) {
319                 if (it->inset->lyxCode() == OPTARG_CODE)
320                         ++num;
321         }
322         return num;
323 }
324
325
326 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
327 {
328         pit_type pars_size = static_cast<pit_type>(pars.size());
329
330         // first, accept changes within each individual paragraph
331         // (do not consider end-of-par)
332         for (pit_type pit = 0; pit < pars_size; ++pit) {
333                 if (!pars[pit].empty())   // prevent assertion failure
334                         pars[pit].acceptChanges(bparams, 0, pars[pit].size());
335         }
336
337         // next, accept imaginary end-of-par characters
338         for (pit_type pit = 0; pit < pars_size; ++pit) {
339                 pos_type pos = pars[pit].size();
340
341                 if (pars[pit].isInserted(pos)) {
342                         pars[pit].setChange(pos, Change(Change::UNCHANGED));
343                 } else if (pars[pit].isDeleted(pos)) {
344                         if (pit == pars_size - 1) {
345                                 // we cannot remove a par break at the end of the last
346                                 // paragraph; instead, we mark it unchanged
347                                 pars[pit].setChange(pos, Change(Change::UNCHANGED));
348                         } else {
349                                 mergeParagraph(bparams, pars, pit);
350                                 --pit;
351                                 --pars_size;
352                         }
353                 }
354         }
355 }
356
357
358 } // namespace lyx