]> git.lyx.org Git - lyx.git/blob - src/insets/insetcite.C
Rewrite the MailInset as suggested to Andr��.
[lyx.git] / src / insets / insetcite.C
1 /**
2  * \file insetcite.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  * \author Herbert Voss
8  *
9  * Full author contact details are available in file CREDITS
10  */
11
12 #include <config.h>
13
14 #include "insetcite.h"
15 #include "buffer.h"
16 #include "BufferView.h"
17 #include "LaTeXFeatures.h"
18
19 #include "frontends/controllers/biblio.h"
20
21 #include "support/lstrings.h"
22
23 #include <map>
24
25 using std::ostream;
26 using std::vector;
27 using std::map;
28
29 namespace {
30
31 // An optimisation. We assume that until the first InsetCitation::edit is
32 // called, we're loading the buffer and that, therefore, we don't need to
33 // reload the bibkey list
34 std::map<Buffer const *, bool> loading_buffer;
35
36 string const getNatbibLabel(Buffer const * buffer,
37                             string const & citeType, string const & keyList,
38                             string const & before, string const & after,
39                             bool numerical)
40 {
41         typedef std::map<Buffer const *, biblio::InfoMap> CachedMap;
42         static CachedMap cached_keys;
43
44         // Only load the bibkeys once if we're loading up the buffer,
45         // else load them afresh each time.
46         map<Buffer const *, bool>::iterator lit = loading_buffer.find(buffer);
47         if (lit == loading_buffer.end())
48                 loading_buffer[buffer] = true;
49
50         bool loadkeys = !loading_buffer[buffer];
51         if (!loadkeys) {
52                 CachedMap::iterator kit = cached_keys.find(buffer);
53                 loadkeys = kit == cached_keys.end();
54         }
55
56         if (loadkeys) {
57                 // build the keylist
58                 typedef vector<std::pair<string, string> > InfoType;
59                 InfoType bibkeys;
60                 buffer->fillWithBibKeys(bibkeys);
61
62                 InfoType::const_iterator bit  = bibkeys.begin();
63                 InfoType::const_iterator bend = bibkeys.end();
64
65                 biblio::InfoMap infomap;
66                 for (; bit != bend; ++bit) {
67                         infomap[bit->first] = bit->second;
68                 }
69                 if (infomap.empty())
70                         return string();
71
72                 cached_keys[buffer] = infomap;
73         }
74
75         biblio::InfoMap infomap = cached_keys[buffer];
76
77         // the natbib citation-styles
78         // CITET:       author (year)
79         // CITEP:       (author,year)
80         // CITEALT:     author year
81         // CITEALP:     author, year
82         // CITEAUTHOR:  author
83         // CITEYEAR:    year
84         // CITEYEARPAR: (year)
85
86         // We don't currently use the full or forceUCase fields.
87         // bool const forceUCase = citeType[0] == 'C';
88         bool const full = citeType[citeType.size() - 1] == '*';
89
90         string const cite_type = full ?
91                 ascii_lowercase(citeType.substr(0, citeType.size() - 1)) :
92                 ascii_lowercase(citeType);
93
94         string before_str;
95         if (!before.empty()) {
96                 // In CITET and CITEALT mode, the "before" string is
97                 // attached to the label associated with each and every key.
98                 // In CITEP, CITEALP and CITEYEARPAR mode, it is attached
99                 // to the front of the whole only.
100                 // In other modes, it is not used at all.
101                 if (cite_type == "citet" ||
102                     cite_type == "citealt" ||
103                     cite_type == "citep" ||
104                     cite_type == "citealp" ||
105                     cite_type == "citeyearpar")
106                         before_str = before + ' ';
107         }
108
109         string after_str;
110         if (!after.empty()) {
111                 // The "after" key is appended only to the end of the whole.
112                 after_str = ", " + after;
113         }
114
115         // One day, these might be tunable (as they are in BibTeX).
116         char const op  = '('; // opening parenthesis.
117         char const cp  = ')'; // closing parenthesis.
118         // puctuation mark separating citation entries.
119         char const * const sep = ";";
120
121         string const op_str(' ' + string(1, op));
122         string const cp_str(string(1, cp) + ' ');
123         string const sep_str(string(sep) + ' ');
124
125         string label;
126         vector<string> keys = getVectorFromString(keyList);
127         vector<string>::const_iterator it  = keys.begin();
128         vector<string>::const_iterator end = keys.end();
129         for (; it != end; ++it) {
130                 // get the bibdata corresponding to the key
131                 string const author(biblio::getAbbreviatedAuthor(infomap, *it));
132                 string const year(biblio::getYear(infomap, *it));
133
134                 // Something isn't right. Fail safely.
135                 if (author.empty() || year.empty())
136                         return string();
137
138                 // (authors1 (<before> year);  ... ;
139                 //  authors_last (<before> year, <after>)
140                 if (cite_type == "citet") {
141                         string const tmp = numerical ? '#' + *it : year;
142                         label += author + op_str + before_str + tmp +
143                                 cp + sep_str;
144
145                 // author, year; author, year; ...
146                 } else if (cite_type == "citep" ||
147                            cite_type == "citealp") {
148                         if (numerical) {
149                                 label += *it + sep_str;
150                         } else {
151                                 label += author + ", " + year + sep_str;
152                         }
153
154                 // (authors1 <before> year;
155                 //  authors_last <before> year, <after>)
156                 } else if (cite_type == "citealt") {
157                         string const tmp = numerical ? '#' + *it : year;
158                         label += author + ' ' + before_str + tmp + sep_str;
159
160                 // author; author; ...
161                 } else if (cite_type == "citeauthor") {
162                         label += author + sep_str;
163
164                 // year; year; ...
165                 } else if (cite_type == "citeyear" ||
166                            cite_type == "citeyearpar") {
167                         label += year + sep_str;
168                 }
169         }
170         label = rtrim(rtrim(label), sep);
171
172         if (!after_str.empty()) {
173                 if (cite_type == "citet") {
174                         // insert "after" before last ')'
175                         label.insert(label.size() - 1, after_str);
176                 } else {
177                         bool const add = !(numerical &&
178                                            (cite_type == "citeauthor" ||
179                                             cite_type == "citeyear"));
180                         if (add)
181                                 label += after_str;
182                 }
183         }
184
185         if (!before_str.empty() && (cite_type == "citep" ||
186                                     cite_type == "citealp" ||
187                                     cite_type == "citeyearpar")) {
188                 label = before_str + label;
189         }
190
191         if (cite_type == "citep" || cite_type == "citeyearpar")
192                 label = string(1, op) + label + string(1, cp);
193
194         return label;
195 }
196
197
198 string const getBasicLabel(string const & keyList, string const & after)
199 {
200         string keys(keyList);
201         string label;
202
203         if (contains(keys, ",")) {
204                 // Final comma allows while loop to cover all keys
205                 keys = ltrim(split(keys, label, ',')) + ',';
206                 while (contains(keys, ",")) {
207                         string key;
208                         keys = ltrim(split(keys, key, ','));
209                         label += ", " + key;
210                 }
211         } else
212                 label = keys;
213
214         if (!after.empty())
215                 label += ", " + after;
216
217         return '[' + label + ']';
218 }
219
220 } // anon namespace
221
222
223 InsetCitation::InsetCitation(InsetCommandParams const & p, bool)
224         : InsetCommand(p)
225 {}
226
227
228 InsetCitation::~InsetCitation()
229 {
230         InsetCommandMailer mailer("citation", *this);
231         mailer.hideDialog();
232 }
233
234
235 string const InsetCitation::generateLabel(Buffer const * buffer) const
236 {
237         string const before = string();
238         string const after  = getOptions();
239
240         string label;
241         if (buffer->params.use_natbib) {
242                 string cmd = getCmdName();
243                 if (cmd == "cite") {
244                         // We may be "upgrading" from an older LyX version.
245                         // If, however, we use "cite" because the necessary
246                         // author/year info is not present in the biblio
247                         // database, then getNatbibLabel will exit gracefully
248                         // and we'll call getBasicLabel.
249                         if (buffer->params.use_numerical_citations)
250                                 cmd = "citep";
251                         else
252                                 cmd = "citet";
253                 }
254                 label = getNatbibLabel(buffer, cmd, getContents(),
255                                        before, after,
256                                        buffer->params.use_numerical_citations);
257         }
258
259         // Fallback to fail-safe
260         if (label.empty()) {
261                 label = getBasicLabel(getContents(), after);
262         }
263
264         return label;
265 }
266
267
268 InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const * buffer) const
269 {
270         Cache::Style style = Cache::BASIC;
271
272         if (buffer->params.use_natbib) {
273                 if (buffer->params.use_numerical_citations) {
274                         style = Cache::NATBIB_NUM;
275                 } else {
276                         style = Cache::NATBIB_AY;
277                 }
278         }
279
280         return style;
281 }
282
283
284 string const InsetCitation::getScreenLabel(Buffer const * buffer) const
285 {
286         Cache::Style const style = getStyle(buffer);
287         if (cache.params == params() && cache.style == style)
288                 return cache.screen_label;
289
290         // The label has changed, so we have to re-create it.
291         string const before = string();
292         string const after  = getOptions();
293
294         string const glabel = generateLabel(buffer);
295
296         unsigned int const maxLabelChars = 45;
297
298         string label = glabel;
299         if (label.size() > maxLabelChars) {
300                 label.erase(maxLabelChars-3);
301                 label += "...";
302         }
303
304         cache.style  = style;
305         cache.params = params();
306         cache.generated_label = glabel;
307         cache.screen_label = label;
308
309         return label;
310 }
311
312
313 void InsetCitation::setLoadingBuffer(Buffer const * buffer, bool state) const
314 {
315         // Doesn't matter if there is no bv->buffer() entry in the map.
316         loading_buffer[buffer] = state;
317 }
318
319
320 void InsetCitation::edit(BufferView * bv, int, int, mouse_button::state)
321 {
322         // A call to edit() indicates that we're no longer loading the
323         // buffer but doing some real work.
324         setLoadingBuffer(bv->buffer(), false);
325
326         InsetCommandMailer mailer("citation", *this);
327         mailer.showDialog(bv);
328 }
329
330
331 void InsetCitation::edit(BufferView * bv, bool)
332 {
333         edit(bv, 0, 0, mouse_button::none);
334 }
335
336
337 int InsetCitation::ascii(Buffer const * buffer, ostream & os, int) const
338 {
339         string label;
340
341         if (cache.params == params() && cache.style == getStyle(buffer))
342                 label = cache.generated_label;
343         else
344                 label = generateLabel(buffer);
345
346         os << label;
347         return 0;
348 }
349
350
351 // Have to overwrite the default InsetCommand method in order to check that
352 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
353 // citations and then changes his mind, turning natbib support off. The output
354 // should revert to \cite[]{}
355 int InsetCitation::latex(Buffer const * buffer, ostream & os,
356                         bool /*fragile*/, bool/*fs*/) const
357 {
358         os << "\\";
359         if (buffer->params.use_natbib)
360                 os << getCmdName();
361         else
362                 os << "cite";
363
364 #warning What is this code supposed to do? (Lgb)
365 // my guess is that this is just waiting for when we support before,
366 // so it's a oneliner. But this is very silly ! - jbl
367
368 #if 1
369         // The current strange code
370
371         string const before = string();
372         string const after  = getOptions();
373         if (!before.empty() && buffer->params.use_natbib)
374                 os << '[' << before << "][" << after << ']';
375         else if (!after.empty())
376                 os << '[' << after << ']';
377 #else
378         // and the cleaned up equvalent, should it just be changed? (Lgb)
379         string const after  = getOptions();
380         if (!after.empty())
381                 os << '[' << after << ']';
382 #endif
383         string::const_iterator it  = getContents().begin();
384         string::const_iterator end = getContents().end();
385         // Paranoia check: make sure that there is no whitespace in here
386         string content;
387         char last = ',';
388         for (; it != end; ++it) {
389                 if (*it != ' ')
390                         last = *it;
391                 if (*it != ' ' || last != ',')
392                         content += *it;
393         }
394
395         os << '{' << content << '}';
396
397         return 0;
398 }
399
400
401 void InsetCitation::validate(LaTeXFeatures & features) const
402 {
403         if (features.bufferParams().use_natbib)
404                 features.require("natbib");
405 }