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