]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/xforms_helpers.C
Refactor xforms' colour handling code.
[lyx.git] / src / frontends / xforms / xforms_helpers.C
1 /**
2  * \file xforms_helpers.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS
9  */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "xforms_helpers.h"
18
19 #include "lyxlex.h"
20 #include "gettext.h"
21 #include "lyxlength.h"
22 #include "lyxgluelength.h"
23
24 #include "support/LAssert.h"
25 #include "support/FileInfo.h"
26 #include "support/filetools.h"
27 #include "support/lstrings.h" // frontStrip, strip
28
29 #include <algorithm>
30 #include <fstream>
31 #include <vector>
32
33 #include FORMS_H_LOCATION
34
35 using std::ofstream;
36 using std::pair;
37 using std::vector;
38
39 bool isActive(FL_OBJECT * ob)
40 {
41         return ob && ob->active > 0;
42 }
43
44
45 // A wrapper for the xforms routine, but this one accepts uint args
46 unsigned long fl_getmcolor(int i,
47                            unsigned int * r, unsigned int * g, unsigned int * b)
48 {
49         int r2, g2, b2;
50         unsigned long ret_val = ::fl_getmcolor(i, &r2, &g2, &b2);
51         *r = r2;
52         *g = g2;
53         *b = b2;
54         return ret_val;
55 }
56
57
58 // Set an FL_OBJECT to activated or deactivated
59 void setEnabled(FL_OBJECT * ob, bool enable)
60 {
61         if (enable) {
62                 fl_activate_object(ob);
63                 fl_set_object_lcol(ob, FL_LCOL);
64         } else {
65                 fl_deactivate_object(ob);
66                 fl_set_object_lcol(ob, FL_INACTIVE);
67         }
68 }
69
70
71 // Given an fl_choice or an fl_browser, create a vector of its entries
72 vector<string> const getVector(FL_OBJECT * ob)
73 {
74         vector <string> vec;
75
76         switch (ob->objclass) {
77         case FL_CHOICE:
78                 for(int i = 0; i < fl_get_choice_maxitems(ob); ++i) {
79                         string const text = fl_get_choice_item_text(ob, i+1);
80                         vec.push_back(trim(text));
81                 }
82                 break;
83         case FL_BROWSER:
84                 for(int i = 0; i < fl_get_browser_maxline(ob); ++i) {
85                         string const text = fl_get_browser_line(ob, i+1);
86                         vec.push_back(trim(text));
87                 }
88                 break;
89         default:
90                 lyx::Assert(0);
91         }
92
93         return vec;
94 }
95
96
97 ///
98 string const getString(FL_OBJECT * ob, int line)
99 {
100         // Negative line value does not make sense.
101         lyx::Assert(line >= 0);
102
103         char const * tmp = 0;
104         switch (ob->objclass) {
105         case FL_INPUT:
106                 tmp = fl_get_input(ob);
107                 break;
108
109         case FL_BROWSER:
110                 if (line == 0)
111                         line = fl_get_browser(ob);
112
113                 if (line >= 1 && line <= fl_get_browser_maxline(ob))
114                         tmp = fl_get_browser_line(ob, line);
115                 break;
116
117         case FL_CHOICE:
118                 if (line == 0)
119                         line = fl_get_choice(ob);
120
121                 if (line >= 1 && line <= fl_get_choice_maxitems(ob))
122                         tmp = fl_get_choice_item_text(ob, line);
123                 break;
124
125         default:
126                 lyx::Assert(0);
127         }
128
129         return tmp ? trim(tmp) : string();
130 }
131
132 string getLengthFromWidgets(FL_OBJECT * input, FL_OBJECT * choice)
133 {
134         // Paranoia check
135         lyx::Assert(input  && input->objclass  == FL_INPUT &&
136                     choice && choice->objclass == FL_CHOICE);
137
138         string const length = trim(fl_get_input(input));
139         if (length.empty())
140                 return string();
141
142         // don't return unit-from-choice if the input(field) contains a unit
143         if (isValidGlueLength(length))
144                 return length;
145
146         string unit = trim(fl_get_choice_text(choice));
147         unit = subst(unit, "%%", "%");
148
149         return length + unit;
150 }
151
152
153 void updateWidgetsFromLengthString(FL_OBJECT * input, FL_OBJECT * choice,
154                                    string const & str,
155                                    string const & default_unit)
156 {
157         // use input field only for gluelengths
158         if (!isValidLength(str) && !isStrDbl(str)) {
159                 fl_set_input(input, str.c_str());
160                 // we assume that "default_unit" is in the choice as "we"
161                 // have control over that!
162                 // No need to check for its presence in the choice, therefore.
163                 fl_set_choice_text(choice, default_unit.c_str());
164         } else {
165                 updateWidgetsFromLength(input, choice,
166                                 LyXLength(str), default_unit);
167         }
168 }
169
170
171 void updateWidgetsFromLength(FL_OBJECT * input, FL_OBJECT * choice,
172                              LyXLength const & len,
173                              string const & default_unit)
174 {
175         // Paranoia check
176         lyx::Assert(input  && input->objclass  == FL_INPUT &&
177                     choice && choice->objclass == FL_CHOICE);
178
179         if (len.empty()) {
180                 fl_set_input(input, "");
181                 fl_set_choice_text(choice, default_unit.c_str());
182         } else {
183                 ostringstream buffer;
184                 buffer << len.value();
185                 fl_set_input(input, buffer.str().c_str());
186
187                 // Set the choice to the desired unit, if present in the choice.
188                 // Else set the choice to the default unit.
189                 string const unit = subst(stringFromUnit(len.unit()),"%","%%");
190
191                 vector<string> const vec = getVector(choice);
192                 vector<string>::const_iterator it =
193                         std::find(vec.begin(), vec.end(), unit);
194                 if (it != vec.end()) {
195                         fl_set_choice_text(choice, unit.c_str());
196                 } else {
197                         fl_set_choice_text(choice, default_unit.c_str());
198                 }
199         }
200 }
201
202
203 // Take a string and add breaks so that it fits into a desired label width, w
204 string formatted(string const & sin, int w, int size, int style)
205 {
206         string sout;
207         if (sin.empty()) return sout;
208
209         string::size_type curpos = 0;
210         string line;
211         for(;;) {
212                 string::size_type const nxtpos1 = sin.find(' ',  curpos);
213                 string::size_type const nxtpos2 = sin.find('\n', curpos);
214                 string::size_type const nxtpos = std::min(nxtpos1, nxtpos2);
215
216                 string const word = nxtpos == string::npos ?
217                         sin.substr(curpos) : sin.substr(curpos, nxtpos-curpos);
218
219                 bool const newline = (nxtpos2 != string::npos &&
220                                       nxtpos2 < nxtpos1);
221
222                 string const line_plus_word =
223                         line.empty() ? word : line + ' ' + word;
224
225                 int const length =
226                         fl_get_string_width(style, size,
227                                             line_plus_word.c_str(),
228                                             int(line_plus_word.length()));
229
230                 if (length >= w) {
231                         sout += line + '\n';
232                         if (newline) {
233                                 sout += word + '\n';
234                                 line.erase();
235                         } else {
236                                 line = word;
237                         }
238
239                 } else if (newline) {
240                         sout += line_plus_word + '\n';
241                         line.erase();
242
243                 } else {
244                         if (!line.empty())
245                                 line += ' ';
246                         line += word;
247                 }
248
249                 if (nxtpos == string::npos) {
250                         if (!line.empty())
251                                 sout += line;
252                         break;
253                 }
254
255                 curpos = nxtpos+1;
256         }
257
258         return sout;
259 }
260
261
262 void setCursorColor(int color)
263 {
264         fl_set_cursor_color(FL_DEFAULT_CURSOR, color, FL_WHITE);
265         fl_set_cursor_color(XC_xterm,          color, FL_WHITE);
266         fl_set_cursor_color(XC_watch,          color, FL_WHITE);
267         fl_set_cursor_color(XC_sb_right_arrow, color, FL_WHITE);
268 }
269
270
271 namespace {
272
273 // sorted by hand to prevent LyXLex from complaining on read().
274 keyword_item xformTags[] = {
275         { "\\gui_background",   FL_COL1 },
276         { "\\gui_buttonbottom", FL_BOTTOM_BCOL },
277         { "\\gui_buttonleft",   FL_LEFT_BCOL },
278         { "\\gui_buttonright",  FL_RIGHT_BCOL },
279         { "\\gui_buttontop",    FL_TOP_BCOL },
280         { "\\gui_inactive",     FL_INACTIVE },
281         { "\\gui_pointer",      FL_FREE_COL16 },
282         { "\\gui_push_button",  FL_YELLOW },
283         { "\\gui_selected",     FL_MCOL },
284         { "\\gui_text",         FL_BLACK }
285 };
286
287
288 const int xformCount = sizeof(xformTags) / sizeof(keyword_item);
289
290 } // namespace anon
291
292
293 bool XformsColor::read(string const & filename)
294 {
295         LyXLex lexrc(xformTags, xformCount);
296         if (!lexrc.setFile(filename))
297                 return false;
298
299         while (lexrc.isOK()) {
300                 int const le = lexrc.lex();
301
302                 switch (le) {
303                 case LyXLex::LEX_UNDEF:
304                         lexrc.printError("Unknown tag `$$Token'");
305                         continue;
306                 case LyXLex::LEX_FEOF:
307                         continue;
308                 default: break;
309                 }
310
311                 string const tag = lexrc.getString();
312
313                 RGBColor col;
314
315                 if (!lexrc.next()) break;
316                 col.r = lexrc.getInteger();
317
318                 if (!lexrc.next()) break;
319                 col.g = lexrc.getInteger();
320
321                 if (!lexrc.next()) break;
322                 col.b = lexrc.getInteger();
323
324                 fl_mapcolor(le, col.r, col.g, col.b);
325
326                 if (tag == "\\gui_pointer") {
327                         setCursorColor(FL_FREE_COL16);
328                 }
329         }
330
331         return true;
332 }
333
334
335 bool XformsColor::write(string const & filename)
336 {
337         ofstream os(filename.c_str());
338         if (!os)
339                 return false;
340
341         os << "###"
342            << "### file " << filename << "\n\n"
343            << "### This file is written by LyX, if you want to make your own\n"
344            << "### modifications you should do them from inside LyX and save\n"
345            << '\n';
346
347         for (int i = 0; i < xformCount; ++i) {
348                 string const tag  = xformTags[i].tag;
349                 int const colorID = xformTags[i].code;
350                 RGBColor color;
351
352                 fl_getmcolor(colorID, &color.r, &color.g, &color.b);
353
354                 os << tag << ' '
355                    << color.r << ' ' << color.g << ' ' << color.b << '\n';
356         }
357
358         return true;
359 }
360
361
362 string  RWInfo::error_message;
363
364 bool RWInfo::WriteableDir(string const & name)
365 {
366         error_message.erase();
367
368         if (!AbsolutePath(name)) {
369                 error_message = _("The absolute path is required.");
370                 return false;
371         }
372
373         FileInfo const tp(name);
374         if (!tp.isOK() || !tp.isDir()) {
375                 error_message = _("Directory does not exist.");
376                 return false;
377         }
378
379         if (!tp.writable()) {
380                 error_message = _("Cannot write to this directory.");
381                 return false;
382         }
383
384         return true;
385 }
386
387
388 bool RWInfo::ReadableDir(string const & name)
389 {
390         error_message.erase();
391
392         if (!AbsolutePath(name)) {
393                 error_message = _("The absolute path is required.");
394                 return false;
395         }
396
397         FileInfo const tp(name);
398         if (!tp.isOK() || !tp.isDir()) {
399                 error_message = _("Directory does not exist.");
400                 return false;
401         }
402
403         if (!tp.readable()) {
404                 error_message = _("Cannot read this directory.");
405                 return false;
406         }
407
408         return true;
409 }
410
411
412 bool RWInfo::WriteableFile(string const & name)
413 {
414         // A writeable file is either:
415         // * An existing file to which we have write access, or
416         // * A file that doesn't yet exist but that would exist in a writeable
417         //   directory.
418
419         error_message.erase();
420
421         if (name.empty()) {
422                 error_message = _("No file input.");
423                 return false;
424         }
425
426         string const dir = OnlyPath(name);
427         if (!AbsolutePath(dir)) {
428                 error_message = _("The absolute path is required.");
429                 return false;
430         }
431
432         FileInfo d(name);
433
434         if (!d.isOK() || !d.isDir()) {
435                 d.newFile(dir);
436         }
437
438         if (!d.isOK() || !d.isDir()) {
439                 error_message = _("Directory does not exist.");
440                 return false;
441         }
442
443         if (!d.writable()) {
444                 error_message = _("Cannot write to this directory.");
445                 return false;
446         }
447
448         FileInfo f(name);
449         if (dir == name || (f.isOK() && f.isDir())) {
450                 error_message = _("A file is required, not a directory.");
451                 return false;
452         }
453
454         if (f.isOK() && f.exist() && !f.writable()) {
455                 error_message = _("Cannot write to this file.");
456                 return false;
457         }
458
459         return true;
460 }
461
462
463 bool RWInfo::ReadableFile(string const & name)
464 {
465         error_message.erase();
466
467         if (name.empty()) {
468                 error_message = _("No file input.");
469                 return false;
470         }
471
472         string const dir = OnlyPath(name);
473         if (!AbsolutePath(dir)) {
474                 error_message = _("The absolute path is required.");
475                 return false;
476         }
477
478         FileInfo d(name);
479
480         if (!d.isOK() && !d.isDir()) {
481                 d.newFile(dir);
482         }
483
484         if (!d.isOK() || !d.isDir()) {
485                 error_message = _("Directory does not exist.");
486                 return false;
487         }
488
489         if (!d.readable()) {
490                 error_message = _("Cannot read from this directory.");
491                 return false;
492         }
493
494         FileInfo f(name);
495         if (dir == name || (f.isOK() && f.isDir())) {
496                 error_message = _("A file is required, not a directory.");
497                 return false;
498         }
499
500         if (!f.exist()) {
501                 error_message = _("File does not exist.");
502                 return false;
503         }
504
505         if (!f.readable()) {
506                 error_message = _("Cannot read from this file.");
507                 return false;
508         }
509
510         return true;
511 }