]> git.lyx.org Git - lyx.git/blob - src/insets/InsetBibitem.cpp
3da71d51d183c0824172b93fd65ef16f9316a961
[lyx.git] / src / insets / InsetBibitem.cpp
1 /**
2  * \file InsetBibitem.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12 #include <algorithm>
13
14 #include "InsetBibitem.h"
15
16 #include "BiblioInfo.h"
17 #include "Buffer.h"
18 #include "Cursor.h"
19 #include "buffer_funcs.h"
20 #include "BufferParams.h"
21 #include "BufferView.h"
22 #include "Counters.h"
23 #include "DispatchResult.h"
24 #include "Encoding.h"
25 #include "FuncRequest.h"
26 #include "InsetIterator.h"
27 #include "InsetList.h"
28 #include "Language.h"
29 #include "Lexer.h"
30 #include "output_xhtml.h"
31 #include "OutputParams.h"
32 #include "Paragraph.h"
33 #include "ParagraphList.h"
34 #include "ParIterator.h"
35 #include "TextClass.h"
36
37 #include "frontends/alert.h"
38
39 #include "support/convert.h"
40 #include "support/debug.h"
41 #include "support/docstream.h"
42 #include "support/gettext.h"
43 #include "support/lstrings.h"
44
45 using namespace std;
46 using namespace lyx::support;
47
48 namespace lyx {
49
50
51 // FIXME THREAD
52 int InsetBibitem::key_counter = 0;
53 docstring const key_prefix = from_ascii("key-");
54
55
56 InsetBibitem::InsetBibitem(Buffer * buf, InsetCommandParams const & p)
57         : InsetCommand(buf, p)
58 {
59         buffer().invalidateBibinfoCache();
60         if (getParam("key").empty())
61                 setParam("key", key_prefix + convert<docstring>(++key_counter));
62 }
63
64
65 InsetBibitem::~InsetBibitem()
66 {
67         if (isBufferLoaded())
68                 buffer().invalidateBibinfoCache();
69 }
70
71
72 void InsetBibitem::initView()
73 {
74         updateCommand(getParam("key"));
75 }
76
77
78 void InsetBibitem::updateCommand(docstring const & new_key, bool)
79 {
80         docstring key = new_key;
81
82         vector<docstring> bibkeys = buffer().masterBibInfo().getKeys();
83
84         int i = 1;
85
86         if (find(bibkeys.begin(), bibkeys.end(), key) != bibkeys.end()) {
87                 // generate unique label
88                 key = new_key + '-' + convert<docstring>(i);
89                 while (find(bibkeys.begin(), bibkeys.end(), key) != bibkeys.end()) {
90                         ++i;
91                         key = new_key + '-' + convert<docstring>(i);
92                 }
93                 frontend::Alert::warning(_("Keys must be unique!"),
94                         bformat(_("The key %1$s already exists,\n"
95                         "it will be changed to %2$s."), new_key, key));
96         }
97         setParam("key", key);
98         buffer().invalidateBibinfoCache();
99 }
100
101
102 ParamInfo const & InsetBibitem::findInfo(string const & /* cmdName */)
103 {
104         static ParamInfo param_info_;
105         if (param_info_.empty()) {
106                 param_info_.add("label", ParamInfo::LATEX_OPTIONAL,
107                                 ParamInfo::HANDLING_LATEXIFY);
108                 param_info_.add("key", ParamInfo::LATEX_REQUIRED,
109                                 ParamInfo::HANDLING_ESCAPE);
110         }
111         return param_info_;
112 }
113
114
115 void InsetBibitem::doDispatch(Cursor & cur, FuncRequest & cmd)
116 {
117         switch (cmd.action()) {
118
119         case LFUN_INSET_MODIFY: {
120                 InsetCommandParams p(BIBITEM_CODE);
121                 InsetCommand::string2params(to_utf8(cmd.argument()), p);
122                 if (p.getCmdName().empty()) {
123                         cur.noScreenUpdate();
124                         break;
125                 }
126
127                 cur.recordUndo();
128
129                 docstring const & old_key = params()["key"];
130                 docstring const & old_label = params()["label"];
131                 docstring label = p["label"];
132
133                 // definitions for escaping
134                 int previous;
135                 static docstring const backslash = from_ascii("\\");
136                 static docstring const lbrace = from_ascii("{");
137                 static docstring const rbrace = from_ascii("}");
138                 static char_type const chars_escape[6] = {
139                         '&', '_', '$', '%', '#', '^'};
140                 static char_type const brackets_escape[2] = {'[', ']'};
141
142                 if (!label.empty()) {
143                         // The characters in chars_name[] need to be changed to a command when
144                         // they are in the name field.
145                         for (int k = 0; k < 6; k++)
146                                 for (size_t i = 0, pos;
147                                         (pos = label.find(chars_escape[k], i)) != string::npos;
148                                         i = pos + 2) {
149                                                 if (pos == 0)
150                                                         previous = 0;
151                                                 else
152                                                         previous = pos - 1;
153                                                 // only if not already escaped
154                                                 if (label[previous] != '\\')
155                                                         label.replace(pos, 1, backslash + chars_escape[k] + lbrace + rbrace);
156                                 }
157                         // The characters '[' and ']' need to be put into braces
158                         for (int k = 0; k < 2; k++)
159                                 for (size_t i = 0, pos;
160                                         (pos = label.find(brackets_escape[k], i)) != string::npos;
161                                         i = pos + 2) {
162                                                 if (pos == 0)
163                                                         previous = 0;
164                                                 else
165                                                         previous = pos - 1;
166                                                 // only if not already escaped
167                                                 if (label[previous] != '{')
168                                                         label.replace(pos, 1, lbrace + brackets_escape[k] + rbrace);
169                                 }
170                 }
171
172                 if (old_label != label) {
173                         p["label"] = label;
174                         cur.forceBufferUpdate();
175                         buffer().invalidateBibinfoCache();
176                 }
177
178                 setParam("label", p["label"]);
179                 if (p["key"] != old_key) {
180                         updateCommand(p["key"]);
181                         cur.bv().buffer().changeRefsIfUnique(old_key, params()["key"]);
182                         cur.forceBufferUpdate();
183                         buffer().invalidateBibinfoCache();
184                 }
185                 break;
186         }
187
188         default:
189                 InsetCommand::doDispatch(cur, cmd);
190                 break;
191         }
192 }
193
194
195 void InsetBibitem::read(Lexer & lex)
196 {
197         InsetCommand::read(lex);
198
199         if (prefixIs(getParam("key"), key_prefix)) {
200                 int const key = convert<int>(getParam("key").substr(key_prefix.length()));
201                 key_counter = max(key_counter, key);
202         }
203 }
204
205
206 docstring InsetBibitem::bibLabel() const
207 {
208         BufferParams const & bp = buffer().masterBuffer()->params();
209         if (bp.citeEngineType() == ENGINE_TYPE_NUMERICAL)
210                 return autolabel_;
211         docstring const & label = getParam("label");
212         return label.empty() ? autolabel_ : label;
213 }
214
215
216 docstring InsetBibitem::screenLabel() const
217 {
218         return getParam("key") + " [" + bibLabel() + ']';
219 }
220
221
222 int InsetBibitem::plaintext(odocstringstream & os,
223         OutputParams const &, size_t) const
224 {
225         odocstringstream oss;
226         oss << '[' << bibLabel() << "] ";
227
228         docstring const str = oss.str();
229         os << str;
230
231         return str.size();
232 }
233
234
235 // ale070405
236 docstring bibitemWidest(Buffer const & buffer, OutputParams const & runparams)
237 {
238         BufferParams const & bp = buffer.masterBuffer()->params();
239         if (bp.citeEngineType() == ENGINE_TYPE_NUMERICAL)
240                 return from_ascii("99");
241
242         int w = 0;
243
244         InsetBibitem const * bitem = 0;
245
246         // FIXME: this font is used unitialized for now but should  be set to
247         // a proportional font. Here is what Georg Baum has to say about it:
248         /*
249         bibitemWidest() is supposed to find the bibitem with the widest label in the
250         output, because that is needed as an argument of the bibliography
251         environment to determine the correct indentation. To be 100% correct we
252         would need the metrics of the font that is used in the output, but usually
253         we don't have access to these.
254         In practice, any proportional font is probably good enough, since we don't
255         need to know the final with, we only need to know the which label is the
256         widest.
257         Unless there is an easy way to get the metrics of the output font I suggest
258         to use a hardcoded font like "Times" or so.
259
260         It is very important that the result of this function is the same both with
261         and without GUI. After thinking about this it is clear that no Font
262         metrics should be used here, since these come from the gui. If we can't
263         easily get the LaTeX font metrics we should make our own poor mans font
264         metrics replacement, e.g. by hardcoding the metrics of the standard TeX
265         font.
266         */
267
268         docstring lbl;
269
270         ParagraphList::const_iterator it = buffer.paragraphs().begin();
271         ParagraphList::const_iterator end = buffer.paragraphs().end();
272
273         for (; it != end; ++it) {
274                 if (it->insetList().empty())
275                         continue;
276                 Inset * inset = it->insetList().begin()->inset;
277                 if (inset->lyxCode() != BIBITEM_CODE)
278                         continue;
279
280                 bitem = static_cast<InsetBibitem const *>(inset);
281                 docstring const label = bitem->bibLabel();
282
283                 // FIXME: we can't be sure using the following that the GUI
284                 // version and the command-line version will give the same
285                 // result.
286                 //
287                 //int const wx = use_gui?
288                 //      theFontMetrics(font).width(label): label.size();
289                 //
290                 // So for now we just use the label size in order to be sure
291                 // that GUI and no-GUI gives the same bibitem (even if that is
292                 // potentially the wrong one.
293                 int const wx = label.size();
294
295                 if (wx > w) {
296                         w = wx;
297                         lbl = label;
298                 }
299         }
300
301         if (!lbl.empty()) {
302                 pair<docstring, docstring> latex_lbl =
303                         runparams.encoding->latexString(lbl, runparams.dryrun);
304                 return latex_lbl.first;
305         }
306
307         return from_ascii("99");
308 }
309
310
311 void InsetBibitem::collectBibKeys(InsetIterator const & it) const
312 {
313         docstring const key = getParam("key");
314         docstring const label = getParam("label");
315         BibTeXInfo keyvalmap(false);
316         keyvalmap.key(key);
317         keyvalmap.label(label);
318
319         BufferParams const & bp = buffer().masterBuffer()->params();
320         Counters & counters = bp.documentClass().counters();
321         docstring const bibitem = from_ascii("bibitem");
322         if (bp.citeEngineType() == ENGINE_TYPE_NUMERICAL || getParam("label").empty()) {
323                 if (counters.hasCounter(bibitem))
324                         counters.step(bibitem, InternalUpdate);
325                 string const & lang = it.paragraph().getParLanguage(bp)->code();
326                 keyvalmap.setCiteNumber(counters.theCounter(bibitem, lang));
327         }
328
329         DocIterator doc_it(it);
330         doc_it.forwardPos();
331         keyvalmap[from_ascii("ref")] = doc_it.paragraph().asString(
332                 AS_STR_INSETS | AS_STR_SKIPDELETE);
333         buffer().addBibTeXInfo(key, keyvalmap);
334 }
335
336
337 // Update the counters of this inset and of its contents
338 void InsetBibitem::updateBuffer(ParIterator const & it, UpdateType utype)
339 {
340         BufferParams const & bp = buffer().masterBuffer()->params();
341         Counters & counters = bp.documentClass().counters();
342         docstring const bibitem = from_ascii("bibitem");
343         if (bp.citeEngineType() == ENGINE_TYPE_NUMERICAL || getParam("label").empty()) {
344                 if (counters.hasCounter(bibitem))
345                         counters.step(bibitem, utype);
346                 string const & lang = it.paragraph().getParLanguage(bp)->code();
347                 autolabel_ = counters.theCounter(bibitem, lang);
348         } else {
349                 autolabel_ = from_ascii("??");
350         }
351 }
352
353
354 docstring InsetBibitem::xhtml(XHTMLStream & xs, OutputParams const &) const
355 {
356         // FIXME XHTML
357         // XHTML 1.1 doesn't have the "name" attribute for <a>, so we have to use
358         // the "id" atttribute to get the document to validate. Probably, we will
359         // need to use "name" anyway, eventually, because some browsers do not
360         // handle jumping to ids. If we don't do that, though, we can just put the
361         // id into the span tag.
362         string const attrs =
363                 "id='LyXCite-" + to_utf8(html::cleanAttr(getParam("key"))) + "'";
364         xs << html::CompTag("a", attrs);
365         xs << html::StartTag("span", "class='bibitemlabel'");
366         xs << bibLabel();
367         xs << html::EndTag("span");
368         return docstring();
369 }
370
371
372 } // namespace lyx