]> git.lyx.org Git - lyx.git/blob - src/insets/InsetBibitem.cpp
Update a bibitem label also when it is emptied.
[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,
181                                 params()["key"], CITE_CODE);
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         docstring const & label = getParam("label");
209         return label.empty() ? autolabel_ : label;
210 }
211
212
213 docstring InsetBibitem::screenLabel() const
214 {
215         return getParam("key") + " [" + bibLabel() + ']';
216 }
217
218
219 int InsetBibitem::plaintext(odocstream & os, OutputParams const &) const
220 {
221         odocstringstream oss;
222         oss << '[' << bibLabel() << "] ";
223
224         docstring const str = oss.str();
225         os << str;
226
227         return str.size();
228 }
229
230
231 // ale070405
232 docstring bibitemWidest(Buffer const & buffer, OutputParams const & runparams)
233 {
234         int w = 0;
235
236         InsetBibitem const * bitem = 0;
237
238         // FIXME: this font is used unitialized for now but should  be set to
239         // a proportional font. Here is what Georg Baum has to say about it:
240         /*
241         bibitemWidest() is supposed to find the bibitem with the widest label in the
242         output, because that is needed as an argument of the bibliography
243         environment to determine the correct indentation. To be 100% correct we
244         would need the metrics of the font that is used in the output, but usually
245         we don't have access to these.
246         In practice, any proportional font is probably good enough, since we don't
247         need to know the final with, we only need to know the which label is the
248         widest.
249         Unless there is an easy way to get the metrics of the output font I suggest
250         to use a hardcoded font like "Times" or so.
251
252         It is very important that the result of this function is the same both with
253         and without GUI. After thinking about this it is clear that no Font
254         metrics should be used here, since these come from the gui. If we can't
255         easily get the LaTeX font metrics we should make our own poor mans font
256         metrics replacement, e.g. by hardcoding the metrics of the standard TeX
257         font.
258         */
259
260         docstring lbl;
261
262         ParagraphList::const_iterator it = buffer.paragraphs().begin();
263         ParagraphList::const_iterator end = buffer.paragraphs().end();
264
265         for (; it != end; ++it) {
266                 if (it->insetList().empty())
267                         continue;
268                 Inset * inset = it->insetList().begin()->inset;
269                 if (inset->lyxCode() != BIBITEM_CODE)
270                         continue;
271
272                 bitem = static_cast<InsetBibitem const *>(inset);
273                 docstring const label = bitem->bibLabel();
274
275                 // FIXME: we can't be sure using the following that the GUI
276                 // version and the command-line version will give the same
277                 // result.
278                 //
279                 //int const wx = use_gui?
280                 //      theFontMetrics(font).width(label): label.size();
281                 //
282                 // So for now we just use the label size in order to be sure
283                 // that GUI and no-GUI gives the same bibitem (even if that is
284                 // potentially the wrong one.
285                 int const wx = label.size();
286
287                 if (wx > w) {
288                         w = wx;
289                         lbl = label;
290                 }
291         }
292
293         if (!lbl.empty()) {
294                 docstring latex_lbl;
295                 for (size_t n = 0; n < lbl.size(); ++n) {
296                         try {
297                                 latex_lbl += runparams.encoding->latexChar(lbl[n]);
298                         } catch (EncodingException & /* e */) {
299                                 if (runparams.dryrun) {
300                                         latex_lbl += "<" + _("LyX Warning: ")
301                                                   + _("uncodable character") + " '";
302                                         latex_lbl += docstring(1, lbl[n]);
303                                         latex_lbl += "'>";
304                                 }
305                         }
306                 }
307                 return latex_lbl;
308         }
309
310         return from_ascii("99");
311 }
312
313
314 void InsetBibitem::collectBibKeys(InsetIterator const & it) const
315 {
316         docstring const key = getParam("key");
317         BibTeXInfo keyvalmap(false);
318         keyvalmap.label(bibLabel());
319         DocIterator doc_it(it);
320         doc_it.forwardPos();
321         keyvalmap[from_ascii("ref")] = doc_it.paragraph().asString();
322         buffer().addBibTeXInfo(key, keyvalmap);
323 }
324
325
326 // Update the counters of this inset and of its contents
327 void InsetBibitem::updateBuffer(ParIterator const & it, UpdateType utype)
328 {
329         BufferParams const & bp = buffer().masterBuffer()->params();
330         Counters & counters = bp.documentClass().counters();
331         docstring const bibitem = from_ascii("bibitem");
332         if (getParam("label").empty()) {
333                 if (counters.hasCounter(bibitem))
334                         counters.step(bibitem, utype);
335                 string const & lang = it.paragraph().getParLanguage(bp)->code();
336                 autolabel_ = counters.theCounter(bibitem, lang);
337         } else {
338                 autolabel_ = from_ascii("??");
339         }
340 }
341
342
343 docstring InsetBibitem::xhtml(XHTMLStream & xs, OutputParams const &) const
344 {
345         // FIXME XHTML
346         // XHTML 1.1 doesn't have the "name" attribute for <a>, so we have to use
347         // the "id" atttribute to get the document to validate. Probably, we will
348         // need to use "name" anyway, eventually, because some browsers do not
349         // handle jumping to ids. If we don't do that, though, we can just put the
350         // id into the span tag.
351         string const attrs = "id='" + to_utf8(getParam("label")) + "'";
352         xs << html::CompTag("a", attrs);
353         xs << html::StartTag("span", "class='bibitemlabel'");
354         xs << bibLabel();
355         xs << html::EndTag("span");
356         return docstring();
357 }
358
359
360 } // namespace lyx