]> git.lyx.org Git - lyx.git/blob - src/LayoutFile.cpp
Enable OK/Apply buttons when resetting to class defaults.
[lyx.git] / src / LayoutFile.cpp
1 /**
2  * \file LayoutFileList.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  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "LayoutFile.h"
15 #include "Counters.h"
16 #include "Floating.h"
17 #include "FloatList.h"
18 #include "Lexer.h"
19 #include "TextClass.h"
20
21 #include "frontends/alert.h"
22
23 #include "support/debug.h"
24 #include "support/FileName.h"
25 #include "support/filetools.h"
26 #include "support/gettext.h"
27 #include "support/lassert.h"
28 #include "support/lstrings.h"
29
30 #include "support/bind.h"
31 #include "support/regex.h"
32 #include "support/TempFile.h"
33
34 #include <fstream>
35
36 using namespace std;
37 using namespace lyx::support;
38
39 namespace lyx {
40
41
42
43 LayoutFile::LayoutFile(string const & fn, string const & cln,
44                        string const & desc, string const & prereq,
45                        string const & category, bool texclassavail) 
46 {
47         name_ = onlyFileName(fn);
48         path_ = fn.rfind('/') == string::npos ? string() : onlyPath(fn);
49         latexname_ = cln;
50         description_ = desc;
51         prerequisites_ = prereq;
52         category_ = category;
53         tex_class_avail_ = texclassavail;
54 }
55
56
57 LayoutFileList::~LayoutFileList()
58 {
59         ClassMap::const_iterator it = classmap_.begin();
60         ClassMap::const_iterator en = classmap_.end();
61         for (; it != en; ++it) {
62                 delete it->second;
63         }
64 }
65
66
67 LayoutFileList & LayoutFileList::get() 
68 {
69         static LayoutFileList baseclasslist;
70         return baseclasslist;
71 }
72
73
74 bool LayoutFileList::haveClass(string const & classname) const
75 {
76         ClassMap::const_iterator it = classmap_.begin();
77         ClassMap::const_iterator en = classmap_.end();
78         for (; it != en; ++it) {
79                 if (it->first == classname)
80                         return true;
81         }
82         return false;
83 }
84
85
86 LayoutFile const & LayoutFileList::operator[](string const & classname) const
87 {
88         LATTEST(haveClass(classname));
89         // safe to continue, since we will make an empty LayoutFile
90         return *classmap_[classname];
91 }
92
93
94 LayoutFile & LayoutFileList::operator[](string const & classname)
95 {
96         LATTEST(haveClass(classname));
97         // safe to continue, since we will make an empty LayoutFile
98         return *classmap_[classname];
99 }
100
101
102 // Reads LyX textclass definitions according to textclass config file
103 bool LayoutFileList::read()
104 {
105         bool success = false;
106         Lexer lex;
107         FileName const real_file = libFileSearch("", "textclass.lst");
108         LYXERR(Debug::TCLASS, "Reading textclasses from `" << real_file << "'.");
109
110         if (real_file.empty()) {
111                 LYXERR0("LayoutFileList::Read: unable to find textclass file  "
112                     << "`textclass.lst'.");
113         } else if (!lex.setFile(real_file)) {
114                 LYXERR0("LayoutFileList::Read: lyxlex was not able to set file: "
115                        << real_file << '.');
116         } else if (!lex.isOK()) {
117                 LYXERR0("LayoutFileList::Read: unable to open textclass file  `"
118                        << makeDisplayPath(real_file.absFileName(), 1000)
119                        << "'\nCheck your installation.");
120         } else {
121                 // we have a file we can read.
122                 bool finished = false;
123                 LYXERR(Debug::TCLASS, "Starting parsing of textclass.lst");
124                 while (lex.isOK() && !finished) {
125                         LYXERR(Debug::TCLASS, "\tline by line");
126                         switch (lex.lex()) {
127                         case Lexer::LEX_FEOF:
128                                 finished = true;
129                                 break;
130                         default:
131                                 string const fname = lex.getString();
132                                 LYXERR(Debug::TCLASS, "Fname: " << fname);
133                                 if (!lex.next()) 
134                                         break;
135                                 string const clname = lex.getString();
136                                 LYXERR(Debug::TCLASS, "Clname: " << clname);
137                                 if (!lex.next()) 
138                                         break;
139                                 string const desc = lex.getString();
140                                 LYXERR(Debug::TCLASS, "Desc: " << desc);
141                                 if (!lex.next()) 
142                                         break;
143                                 bool avail = lex.getBool();
144                                 LYXERR(Debug::TCLASS, "Avail: " << avail);
145                                 if (!lex.next()) 
146                                         break;
147                                 string const prereq = lex.getString();
148                                 LYXERR(Debug::TCLASS, "Prereq: " << prereq);
149                                 if (!lex.next()) 
150                                         break;
151                                 string const category = lex.getString();
152                                 LYXERR(Debug::TCLASS, "Category: " << category);
153                                 // This code is run when we have
154                                 // fname, clname, desc, prereq, and avail
155                                 LayoutFile * tmpl = new LayoutFile(fname, clname, desc, prereq, category, avail);
156                                 if (lyxerr.debugging(Debug::TCLASS)) {
157                                         // only system layout files are loaded here so no
158                                         // buffer path is needed.
159                                         tmpl->load();
160                                 }
161                                 classmap_[fname] = tmpl;
162                         } // end of switch
163                 } // end of while loop
164                 LYXERR(Debug::TCLASS, "End parsing of textclass.lst");
165                 success = true;
166         } // end of else
167
168         // LyX will start with an empty classmap_. This is OK because
169         // (a) we will give the user a chance to reconfigure (see bug 2829) and
170         // (b) even if that fails, we can use addEmptyClass() to get some basic
171         // functionality.
172         if (classmap_.empty())
173                 LYXERR0("LayoutFileList::Read: no textclasses found!");
174         return success;
175 }
176
177
178 std::vector<LayoutFileIndex> LayoutFileList::classList() const
179 {
180         std::vector<LayoutFileIndex> cl;
181         ClassMap::const_iterator it = classmap_.begin();
182         ClassMap::const_iterator en = classmap_.end();
183         for (; it != en; ++it)
184                 cl.push_back(it->first);
185         return cl;
186 }
187
188
189 void LayoutFileList::reset(LayoutFileIndex const & classname)
190 {
191         LATTEST(haveClass(classname));
192         // safe to continue, since we will make an empty LayoutFile
193         LayoutFile * tc = classmap_[classname];
194         LayoutFile * tmpl = 
195                 new LayoutFile(tc->name(), tc->latexname(), tc->description(),
196                                tc->prerequisites(), tc->category(),
197                                tc->isTeXClassAvailable());
198         classmap_[classname] = tmpl;
199         delete tc;
200 }
201
202
203 namespace {
204
205 string layoutpost =                     
206                 "Columns      1\n"
207                 "Sides        1\n"
208                 "SecNumDepth  2\n"
209                 "TocDepth     2\n"
210                 "DefaultStyle   Standard\n\n"
211                 "Style Standard\n"
212                 "       Category              MainText\n"
213                 "       Margin                Static\n"
214                 "       LatexType             Paragraph\n"
215                 "       LatexName             dummy\n"
216                 "       ParIndent             MM\n"
217                 "       ParSkip               0.4\n"
218                 "       Align                 Block\n"
219                 "       AlignPossible         Block, Left, Right, Center\n"
220                 "       LabelType             No_Label\n"
221                 "End\n";
222         
223 }
224
225
226 LayoutFileIndex LayoutFileList::addEmptyClass(string const & textclass)
227 {
228         // FIXME This could be simplified a bit to call TextClass::read(string, ReadType).
229
230         TempFile tempfile("basicXXXXXX.layout");
231         FileName const tempLayout = tempfile.name();
232         ofstream ofs(tempLayout.toFilesystemEncoding().c_str());
233         // This writes a very basic class, but it also attempts to include 
234         // stdclass.inc. That would give us something moderately usable.
235         ofs << "# This layout is automatically generated\n"
236                "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
237                "Format " << LAYOUT_FORMAT << "\n"
238                "Input stdclass.inc\n\n"
239             << layoutpost;
240         ofs.close();
241
242         // We do not know if a LaTeX class is available for this document, but setting
243         // the last parameter to true will suppress a warning message about missing
244         // tex class.
245         LayoutFile * tc = new LayoutFile(textclass, textclass, 
246                         "Unknown text class " + textclass, textclass + ".cls", "", true);
247
248         if (!tc->load(tempLayout.absFileName())) {
249                 // The only way this happens is because the hardcoded layout file 
250                 // above is wrong or stdclass.inc cannot be found. So try again 
251                 // without stdclass.inc and without stdinsets.inc. 
252                 ofstream ofs2(tempLayout.toFilesystemEncoding().c_str());
253                 ofs2 << "# This layout is automatically generated\n"
254                         "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
255                         "Format " << LAYOUT_FORMAT << "\n"
256                         "Provides stdinsets 1\n"
257                      << layoutpost;
258                 ofs2.close();
259                 if (!tc->load(tempLayout.absFileName())) {
260                         // This can only happen if the hardcoded file above is wrong
261                         // or there is some weird filesystem error.
262                         LATTEST(false); // We will get an empty layout or something.
263                 }
264         }
265
266         classmap_[textclass] = tc;
267         return textclass;
268 }
269
270
271 LayoutFileIndex  LayoutFileList::addLocalLayout(
272         string const & textclass, string const & path, string const & oldpath)
273 {
274         // FIXME  There is a bug here: 4593
275         //
276         // only check for textclass.layout file, .cls can be anywhere in $TEXINPUTS
277         // NOTE: latex class name is defined in textclass.layout, which can be 
278         // different from textclass
279         string fullName = addName(path, textclass + ".layout");
280         
281         FileName layout_file(fullName);
282         bool moved = false;
283
284         if (!layout_file.exists()) {
285                 if (oldpath.empty())
286                         return string();
287                 // The document has been moved to a different directory.
288                 // However, oldpath always points to the right spot, unless
289                 // the user also moved the layout file.
290                 fullName = addName(oldpath, textclass + ".layout");
291                 layout_file.set(fullName);
292                 layout_file.refresh();
293                 if (!layout_file.exists())
294                         return string();
295                 moved = true;
296         }
297
298         LYXERR(Debug::TCLASS, "Adding class " << textclass << " from directory " << path);
299         // Read .layout file and get description, real latex classname etc
300         //
301         // This is a C++ version of function processLayoutFile in configure.py,
302         // which uses the following regex
303         //     \Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}
304         ifstream ifs(layout_file.toFilesystemEncoding().c_str());
305         static regex const reg("^\\s*#\\s*\\\\Declare(LaTeX|DocBook)Class\\s*"
306                 "(?:\\[([^,]*)(?:,.*)*\\])*\\s*\\{(.*)\\}\\s*");
307         static regex const catreg("^\\s*#\\s*\\\\DeclareCategory\\{(.*)\\}\\s*");
308         string line;
309         string class_name;
310         string class_prereq;
311         string category;
312         bool have_declaration = false;
313         while (getline(ifs, line)) {
314                 // look for the \DeclareXXXClass line
315                 smatch sub;
316                 if (regex_match(line, sub, reg)) {
317                         // returns: whole string, classtype (not used here), class name, description
318                         // LASSERT: Why would this fail?
319                         LASSERT(sub.size() == 4, /**/);
320                         // now, create a TextClass with description containing path information
321                         class_name = (sub.str(2) == "" ? textclass : sub.str(2));
322                         class_prereq = class_name + ".cls";
323                         have_declaration = true;
324                 }
325                 else if (regex_match(line, sub, catreg)) {
326                         category = sub.str(1);
327                 }
328                 if (have_declaration && !category.empty())
329                         break;
330         }
331
332         if (!have_declaration)
333                 return string();
334
335         LayoutFile * tmpl =
336                 new LayoutFile(addName(moved ? oldpath : path, textclass),
337                         class_name, textclass, class_prereq, category, true);
338         //FIXME: The prerequisites are available from the layout file and
339         //       can be extracted from the above regex, but for now this
340         //       field is simply set to class_name + ".cls"
341         // This textclass is added on request so it will definitely be
342         // used. Load it now because other load() calls may fail if they
343         // are called in a context without buffer path information.
344         tmpl->load(moved ? oldpath : path);
345         // There will be only one textclass with this name, even if different
346         // layout files are loaded from different directories.
347         if (haveClass(textclass)) {
348                 // Unconditionally issuing the warning may be confusing when
349                 // saving the document with a different name, as it is exactly
350                 // the same textclass that is being re-established.
351                 LYXERR(Debug::TCLASS, "Existing textclass " << textclass << " is redefined by " << fullName);
352                 delete classmap_[textclass];
353         }
354         classmap_[textclass] = tmpl;
355         return removeExtension(fullName);
356 }
357
358
359 bool LayoutFileList::load(string const & name, string const & buf_path)
360 {
361         if (!haveClass(name)) {
362                 LYXERR0("Document class \"" << name << "\" does not exist.");
363                 return false;
364         }
365
366         LayoutFile * tc = classmap_[name];
367         return tc->load(buf_path);
368 }
369
370
371 LayoutFileIndex defaultBaseclass()
372 {
373         if (LayoutFileList::get().haveClass("article"))
374                 return string("article");
375         if (LayoutFileList::get().empty())
376                 // we'll call it that, since this gives the user a chance to
377                 // have a functioning document when things improve.
378                 return string("article");
379         return LayoutFileList::get().classList().front();
380 }
381
382 } // namespace lyx