]> git.lyx.org Git - lyx.git/blob - src/vspace.C
John's Layout Tabular UI improvements and Martins fixes to clearing the
[lyx.git] / src / vspace.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *      
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "vspace.h"
18 #include "lengthcommon.h"
19 #include "lyx_main.h"
20 #include "buffer.h"
21 #include "lyxrc.h"
22 #include "lyxtext.h"
23 #include "BufferView.h"
24
25 #include "support/lstrings.h"
26
27 #include <cstdio>
28
29
30 namespace {
31
32 double           number[4] = { 0, 0, 0, 0 };
33 LyXLength::UNIT unit[4]   = { LyXLength::UNIT_NONE,
34                               LyXLength::UNIT_NONE,
35                               LyXLength::UNIT_NONE,
36                               LyXLength::UNIT_NONE };
37
38 int number_index;
39 int unit_index;
40
41 inline
42 void lyx_advance(string & data, string::size_type n)
43 {
44         data.erase(0, n);
45 }
46
47
48 inline
49 bool isEndOfData(string const & data)
50 {
51         return frontStrip(data).empty();
52 }
53
54
55 char nextToken(string & data)
56 {
57         data = frontStrip(data);
58         if (data.empty())
59                 return '\0';
60         else if (data[0] == '+') {
61                 lyx_advance(data, 1);
62                 return '+';
63         } else if (prefixIs(data, "plus")) {
64                 lyx_advance(data, 4);
65                 return '+';
66         } else if (data[0] == '-') {
67                 lyx_advance(data, 1);
68                 return '-';
69         } else if (prefixIs(data, "minus")) {
70                 lyx_advance(data, 5);
71                 return '-';
72         } else {
73                 string::size_type i = data.find_first_not_of("0123456789.");
74
75                 if (i != 0) {
76                         if (number_index > 3) return 'E';
77
78                         string buffer;
79                 
80                         // we have found some number
81                         if (i == string::npos) {
82                                 buffer = data;
83                                 i = data.size() + 1;
84                         } else
85                                 buffer = data.substr(0, i);
86
87                         lyx_advance(data, i);
88
89                         if (isStrDbl(buffer)) {
90                                 number[number_index] = strToDbl(buffer);
91                                 ++number_index;
92                                 return 'n';
93                         } else return 'E';
94                 }
95                 
96                 i = data.find_first_not_of("abcdefghijklmnopqrstuvwxyz%");
97                 if (i != 0) {
98                         if (unit_index > 3) return 'E';
99
100                         string buffer;
101                 
102                         // we have found some alphabetical string
103                         if (i == string::npos) {
104                                 buffer = data;
105                                 i = data.size() + 1;
106                         } else
107                                 buffer = data.substr(0, i);
108
109                         // possibly we have "mmplus" string or similar
110                         if (buffer.size() > 5 && (buffer.substr(2,4) == string("plus") || buffer.substr(2,5) == string("minus"))) {
111                                 lyx_advance(data, 2);
112                                 unit[unit_index] = unitFromString(buffer.substr(0, 2));
113                         } else {
114                                 lyx_advance(data, i);
115                                 unit[unit_index] = unitFromString(buffer);
116                         }
117
118                         if (unit[unit_index] != LyXLength::UNIT_NONE) {
119                                 ++unit_index;
120                                 return 'u';
121                         } else return 'E';  // Error
122                 }
123                 return 'E';  // Error
124         }
125 }
126
127
128 struct LaTeXLength {
129         char const * pattern;
130         int  plus_val_index;
131         int  minus_val_index;
132         int  plus_uni_index;
133         int  minus_uni_index;
134 };
135
136
137 LaTeXLength table[] = {
138         { "nu",       0, 0, 0, 0 },
139         { "nu+nu",    2, 0, 2, 0 },
140         { "nu+nu-nu", 2, 3, 2, 3 },
141         { "nu+-nu",   2, 2, 2, 2 },
142         { "nu-nu",    0, 2, 0, 2 },
143         { "nu-nu+nu", 3, 2, 3, 2 },
144         { "nu-+nu",   2, 2, 2, 2 },
145         { "n+nu",     2, 0, 1, 0 },
146         { "n+n-nu",   2, 3, 1, 1 },
147         { "n+-nu",    2, 2, 1, 1 },
148         { "n-nu",     0, 2, 0, 1 },
149         { "n-n+nu",   3, 2, 1, 1 },
150         { "n-+nu",    2, 2, 1, 1 },
151         { "",         0, 0, 0, 0 }   // sentinel, must be empty
152 };
153
154
155 } // namespace anon
156
157 const char * stringFromUnit(int unit)
158 {
159         if (unit < 0 || unit >= num_units)
160                 return 0;
161         return unit_name[unit];
162 }
163
164
165 bool isValidGlueLength(string const & data, LyXGlueLength * result)
166 {
167         // This parser is table-driven.  First, it constructs a "pattern"
168         // that describes the sequence of tokens in "data".  For example,
169         // "n-nu" means: number, minus sign, number, unit.  As we go along,
170         // numbers and units are stored into static arrays.  Then, "pattern"
171         // is searched in the "table".  If it is found, the associated
172         // table entries tell us which number and unit should go where
173         // in the LyXLength structure.  Example: if "data" has the "pattern"
174         // "nu+nu-nu", the associated table entries are "2, 3, 2, 3".
175         // That means, "plus_val" is the second number that was seen
176         // in the input, "minus_val" is the third number, and "plus_uni"
177         // and "minus_uni" are the second and third units, respectively.
178         // ("val" and "uni" are always the first items seen in "data".)
179         // This is the most elegant solution I could find -- a straight-
180         // forward approach leads to very long, tedious code that would be
181         // much harder to understand and maintain. (AS)
182
183         if (data.empty())
184                 return true;
185         string buffer = frontStrip(data);
186
187         // To make isValidGlueLength recognize negative values as
188         // the first number this little hack is needed:
189         int val_sign = 1; // positive as default
190         switch (buffer[0]) {
191         case '-':
192                 lyx_advance(buffer, 1);
193                 val_sign = -1;
194                 break;
195         case '+':
196                 lyx_advance(buffer, 1);
197                 // fall through
198         default:
199                 // no action
200                 break;
201         }
202         // end of hack
203         
204         int  pattern_index = 0;
205         int  table_index = 0;
206         char pattern[20];
207
208         number_index = 1;
209         unit_index = 1;  // entries at index 0 are sentinels
210
211         // construct "pattern" from "data"
212         while (!isEndOfData (buffer)) {
213                 if (pattern_index > 20) return false;
214                 pattern[pattern_index] = nextToken (buffer);
215                 if (pattern[pattern_index] == 'E') return false;
216                 ++pattern_index;
217         }
218         pattern[pattern_index] = '\0';
219
220         // search "pattern" in "table"
221         table_index = 0;
222         while (compare(pattern, table[table_index].pattern)) {
223                 ++table_index;
224                 if (!*table[table_index].pattern)
225                         return false;
226         }
227         
228         // Get the values from the appropriate places.  If an index
229         // is zero, the corresponding array value is zero or UNIT_NONE,
230         // so we needn't check this.
231         if (result) {
232                 result->len_.value  (number[1] * val_sign);
233                 result->len_.unit   (unit[1]);
234                 result->plus_.value (number[table[table_index].plus_val_index]);
235                 result->plus_.unit  (unit  [table[table_index].plus_uni_index]);
236                 result->minus_.value(number[table[table_index].minus_val_index]);
237                 result->minus_.unit (unit  [table[table_index].minus_uni_index]);
238         }
239         return true;
240 }
241
242
243 bool isValidLength(string const & data, LyXLength * result)
244 {
245         /// This is a trimmed down version of isValidGlueLength.
246         /// The parser may seem overkill for lengths without
247         /// glue, but since we already have it, using it is
248         /// easier than writing something from scratch.
249         if (data.empty())
250                 return true;
251
252         string   buffer = data;
253         int      pattern_index = 0;
254         char     pattern[3];
255
256         // To make isValidLength recognize negative values
257         // this little hack is needed:
258         int val_sign = 1; // positive as default
259         switch (buffer[0]) {
260         case '-':
261                 lyx_advance(buffer, 1);
262                 val_sign = -1;
263                 break;
264         case '+':
265                 lyx_advance(buffer, 1);
266                 // fall through
267         default:
268                 // no action
269                 break;
270         }
271         // end of hack
272         
273         number_index = unit_index = 1;  // entries at index 0 are sentinels
274
275         // construct "pattern" from "data"
276         while (!isEndOfData (buffer)) {
277                 if (pattern_index > 2)
278                         return false;
279                 pattern[pattern_index] = nextToken (buffer);
280                 if (pattern[pattern_index] == 'E')
281                         return false;
282                 ++pattern_index;
283         }
284         pattern[pattern_index] = '\0';
285
286         // only the most basic pattern is accepted here
287         if (compare(pattern, "nu") != 0) return false;          
288         
289         // It _was_ a correct length string.
290         // Store away the values we found.
291         if (result) {
292                 result->val_  = number[1] * val_sign;
293                 result->unit_ = unit[1];
294         }
295         return true;
296 }
297
298
299 //
300 //  VSpace class
301 //
302
303 VSpace::VSpace()
304         : kind_(NONE), len_(), keep_(false)
305 {}
306
307
308 VSpace::VSpace(vspace_kind k)
309         : kind_(k), len_(), keep_(false)
310 {}
311
312
313 VSpace::VSpace(LyXLength const & l)
314         : kind_(LENGTH), len_(l), keep_(false)
315 {}
316
317
318 VSpace::VSpace(LyXGlueLength const & l)
319         : kind_(LENGTH), len_(l), keep_(false)
320 {}
321
322
323 VSpace::VSpace(string const & data)
324         : kind_(NONE), len_(), keep_(false)
325 {
326         if (data.empty())
327                 return;
328         double value;
329         string input  = strip(data);
330
331         string::size_type const length = input.length();
332
333         if (length > 1 && input[length-1] == '*') {
334                 keep_ = true;
335                 input.erase(length - 1);
336         }
337
338         if      (prefixIs (input, "defskip"))    kind_ = DEFSKIP;
339         else if (prefixIs (input, "smallskip"))  kind_ = SMALLSKIP;
340         else if (prefixIs (input, "medskip"))    kind_ = MEDSKIP;
341         else if (prefixIs (input, "bigskip"))    kind_ = BIGSKIP;
342         else if (prefixIs (input, "vfill"))      kind_ = VFILL;
343         else if (isValidGlueLength(input, &len_)) kind_ = LENGTH;
344         else if (sscanf(input.c_str(), "%lf", &value) == 1) {
345                 // This last one is for reading old .lyx files
346                 // without units in added_space_top/bottom.
347                 // Let unit default to centimeters here.
348                 kind_ = LENGTH;
349                 len_  = LyXGlueLength(LyXLength(value, LyXLength::CM));
350         }
351 }
352
353
354 VSpace::vspace_kind VSpace::kind() const
355 {
356         return kind_;
357 }
358
359
360 LyXGlueLength VSpace::length() const
361 {
362         return len_;
363 }
364
365
366 bool VSpace::keep() const
367 {
368         return keep_;
369 }
370
371
372 void VSpace::setKeep(bool val)
373 {
374         keep_ = val;
375 }
376
377
378 bool VSpace::operator==(VSpace const & other) const
379 {
380         if (kind_ != other.kind_)
381                 return false;
382
383         if (kind_ != LENGTH)
384                 return this->keep_ == other.keep_;
385
386         if (len_ != other.len_)
387                 return false;
388
389         return keep_ == other.keep_;
390 }
391
392
393 string const VSpace::asLyXCommand() const
394 {
395         string result;
396         switch (kind_) {
397         case NONE:      break;
398         case DEFSKIP:   result = "defskip";      break;
399         case SMALLSKIP: result = "smallskip";    break;
400         case MEDSKIP:   result = "medskip";      break;
401         case BIGSKIP:   result = "bigskip";      break;
402         case VFILL:     result = "vfill";        break;
403         case LENGTH:    result = len_.asString(); break;
404         }
405         if (keep_ && kind_ != NONE && kind_ != DEFSKIP)
406                 result += '*';
407         return result;
408 }
409
410
411 string const VSpace::asLatexCommand(BufferParams const & params) const
412 {
413         switch (kind_) {
414         case NONE:      return string();
415         case DEFSKIP:
416                 return params.getDefSkip().asLatexCommand(params);
417         case SMALLSKIP: return keep_ ? "\\vspace*{\\smallskipamount}"
418                                 : "\\smallskip{}";
419         case MEDSKIP:   return keep_ ? "\\vspace*{\\medskipamount}"
420                                 : "\\medskip{}";
421         case BIGSKIP:   return keep_ ? "\\vspace*{\\bigskipamount}"
422                                 : "\\bigskip{}";
423         case VFILL:     return keep_ ? "\\vspace*{\\fill}"
424                                 : "\\vfill{}";
425         case LENGTH:    return keep_ ? "\\vspace*{" + len_.asLatexString() + '}'
426                                 : "\\vspace{" + len_.asLatexString() + '}';
427         }
428         return string();  // should never be reached
429 }
430
431
432 int VSpace::inPixels(BufferView * bv) const
433 {
434         // Height of a normal line in pixels (zoom factor considered)
435         int default_height = bv->text->defaultHeight(); // [pixels]
436         int default_skip   = 0;
437         int default_width  = bv->workWidth();
438
439         if (kind_ == DEFSKIP)
440                 default_skip = bv->buffer()->params.getDefSkip().inPixels(bv);
441
442         // Height of a normal line in pixels (zoom factor considered)
443         int height = default_height; // [pixels]
444         
445         // Zoom factor specified by user in percent
446         double const zoom = lyxrc.zoom / 100.0; // [percent]
447
448         // DPI setting for monitor: pixels/inch
449         double const dpi = lyxrc.dpi; // screen resolution [pixels/inch]
450
451         // We want the result in pixels
452         double result;
453         double value;
454
455         switch (kind_) {
456         case NONE:
457                 return 0;
458
459         case DEFSKIP:
460                 return default_skip;
461
462                 // This is how the skips are normally defined by
463                 // LateX.  But there should be some way to change
464                 // this per document.
465         case SMALLSKIP: return height / 4;
466         case MEDSKIP:   return height / 2;
467         case BIGSKIP:   return height;
468         case VFILL:     return 3 * height;
469                 // leave space for the vfill symbol
470         case LENGTH:
471                 // Pixel values are scaled so that the ratio
472                 // between lengths and font sizes on the screen
473                 // is the same as on paper.
474
475                 // we don't care about sign of value, we
476                 // display negative space with text too
477                 result = 0.0;
478                 value  = len_.len().value();
479                 int val_sign = value < 0.0 ? -1 : 1;
480                 
481                 switch (len_.len().unit()) {
482                 case LyXLength::SP:
483                         // Scaled point: sp = 1/65536 pt
484                         result = zoom * dpi * value
485                                 / (72.27 * 65536); // 4736286.7
486                         break;
487                 case LyXLength::PT:
488                         // Point: 1 pt = 1/72.27 inch
489                         result = zoom * dpi * value
490                                 / 72.27; // 72.27
491                         break;
492                 case LyXLength::BP:
493                         // Big point: 1 bp = 1/72 inch
494                         result = zoom * dpi * value
495                                 / 72; // 72
496                         break;
497                 case LyXLength::DD:
498                         // Didot: 1157dd = 1238 pt?
499                         result = zoom * dpi * value
500                                 / (72.27 / (0.376 * 2.845)); // 67.559735
501                         break;
502                 case LyXLength::MM:
503                         // Millimeter: 1 mm = 1/25.4 inch
504                         result = zoom * dpi * value
505                                 / 25.4; // 25.4
506                         break;
507                 case LyXLength::PC:
508                         // Pica: 1 pc = 12 pt
509                         result = zoom * dpi * value
510                                 / (72.27 / 12); // 6.0225
511                         break;
512                 case LyXLength::CC:
513                         // Cicero: 1 cc = 12 dd
514                         result = zoom * dpi * value
515                                 / (72.27 / (12 * 0.376 * 2.845)); // 5.6299779
516                         break;
517                 case LyXLength::CM:
518                         // Centimeter: 1 cm = 1/2.54 inch
519                         result = zoom * dpi * value
520                                 / 2.54; // 2.54
521                         break;
522                 case LyXLength::IN:
523                         // Inch
524                         result = zoom * dpi * value;
525                         break;
526                 case LyXLength::EX:
527                         // Ex: The height of an "x"
528                         result = zoom * value * height / 2; // what to / width?
529                         break;
530                 case LyXLength::EM: // what to / width?
531                         // Em: The width of an "m"
532                         result = zoom * value * height / 2; // Why 2?
533                         break;
534                 case LyXLength::MU: // This is probably only allowed in
535                         // math mode
536                         result = zoom * value * height;
537                         break;
538                 case LyXLength::PW: // Always % of workarea
539                 case LyXLength::PE:
540                 case LyXLength::PP:
541                 case LyXLength::PL:
542                         result = value * default_width / 100;
543                         break;
544                 case LyXLength::UNIT_NONE:
545                         result = 0;  // this cannot happen
546                         break;
547                 }
548                 return static_cast<int>(result * val_sign + 0.5);
549         }
550         return 0; // never reached
551 }