]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
do what the FIXME suggested
[lyx.git] / src / frontends / qt4 / GuiCitation.cpp
1 /**
2  * \file GuiCitation.cpp
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 Kalle Dalheimer
8  * \author Abdelrazak Younes
9  * \author Richard Heck
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiCitation.h"
17
18 #include "qt_helpers.h"
19 #include "Buffer.h"
20 #include "BufferParams.h"
21
22 #include "insets/InsetCommand.h"
23
24 #include "support/debug.h"
25 #include "support/docstring.h"
26 #include "support/gettext.h"
27 #include "support/lstrings.h"
28
29 #include <QCloseEvent>
30 #include <QShowEvent>
31
32 #include <vector>
33 #include <string>
34
35 #undef KeyPress
36
37 #include <boost/regex.hpp>
38
39 #include <algorithm>
40 #include <string>
41 #include <vector>
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47 namespace frontend {
48
49 static vector<biblio::CiteStyle> citeStyles_;
50
51
52 template<typename String>
53 static QStringList to_qstring_list(vector<String> const & v)
54 {
55         QStringList qlist;
56
57         for (size_t i = 0; i != v.size(); ++i) {
58                 if (v[i].empty())
59                         continue;
60                 qlist.append(lyx::toqstr(v[i]));
61         }
62         return qlist;
63 }
64
65
66 static vector<lyx::docstring> to_docstring_vector(QStringList const & qlist)
67 {
68         vector<lyx::docstring> v;
69         for (int i = 0; i != qlist.size(); ++i) {
70                 if (qlist[i].isEmpty())
71                         continue;
72                 v.push_back(lyx::qstring_to_ucs4(qlist[i]));
73         }
74         return v;
75 }
76
77
78 GuiCitation::GuiCitation(GuiView & lv)
79         : GuiCommand(lv, "citation", qt_("Citation"))
80 {
81         setupUi(this);
82
83         connect(citationStyleCO, SIGNAL(activated(int)),
84                 this, SLOT(on_citationStyleCO_currentIndexChanged(int)));
85         connect(fulllistCB, SIGNAL(clicked()),
86                 this, SLOT(changed()));
87         connect(forceuppercaseCB, SIGNAL(clicked()),
88                 this, SLOT(changed()));
89         connect(textBeforeED, SIGNAL(textChanged(QString)),
90                 this, SLOT(changed()));
91         connect(textAfterED, SIGNAL(textChanged(QString)),
92                 this, SLOT(changed()));
93         connect(clearPB, SIGNAL(clicked()),
94                 findLE, SLOT(clear()));
95         connect(this, SIGNAL(rejected()), this, SLOT(cleanUp()));
96
97         selectionManager = new GuiSelectionManager(availableLV, selectedLV, 
98                         addPB, deletePB, upPB, downPB, available(), selected());
99         connect(selectionManager, SIGNAL(selectionChanged()),
100                 this, SLOT(setCitedKeys()));
101         connect(selectionManager, SIGNAL(updateHook()),
102                 this, SLOT(updateDialog()));
103         connect(selectionManager, SIGNAL(okHook()),
104                 this, SLOT(on_okPB_clicked()));
105
106         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
107 }
108
109
110 void GuiCitation::cleanUp() 
111 {
112         clearSelection();
113         clearParams();
114         close();
115 }
116
117
118 void GuiCitation::closeEvent(QCloseEvent * e)
119 {
120         clearSelection();
121         clearParams();
122         e->accept();
123 }
124
125
126 void GuiCitation::applyView()
127 {
128         int  const choice = max(0, citationStyleCO->currentIndex());
129         style_ = choice;
130         bool const full  = fulllistCB->isChecked();
131         bool const force = forceuppercaseCB->isChecked();
132
133         QString const before = textBeforeED->text();
134         QString const after = textAfterED->text();
135
136         apply(choice, full, force, before, after);
137 }
138
139
140 void GuiCitation::showEvent(QShowEvent * e)
141 {
142         init();
143         findLE->clear();
144         availableLV->setFocus();
145         GuiDialog::showEvent(e);
146 }
147
148
149 void GuiCitation::on_okPB_clicked()
150 {
151         applyView();
152         clearSelection();
153         hide();
154 }
155
156
157 void GuiCitation::on_cancelPB_clicked()
158 {
159         clearSelection();
160         hide();
161 }
162
163
164 void GuiCitation::on_applyPB_clicked()
165 {
166         applyView();
167 }
168
169
170 void GuiCitation::on_restorePB_clicked()
171 {
172         init();
173         updateView();
174 }
175
176
177 void GuiCitation::updateView()
178 {
179         init();
180         fillFields();
181         fillEntries();
182         updateDialog();
183 }
184
185
186 // The main point of separating this out is that the fill*() methods
187 // called in update() do not need to be called for INTERNAL updates,
188 // such as when addPB is pressed, as the list of fields, entries, etc,
189 // will not have changed. At the moment, however, the division between
190 // fillStyles() and updateStyle() doesn't lend itself to dividing the
191 // two methods, though they should be divisible.
192 void GuiCitation::updateDialog()
193 {
194         if (selectionManager->selectedFocused()) { 
195                 if (selectedLV->selectionModel()->selectedIndexes().isEmpty())
196                         updateInfo(availableLV->currentIndex());
197                 else
198                         updateInfo(selectedLV->currentIndex());
199         } else {
200                 if (availableLV->selectionModel()->selectedIndexes().isEmpty())
201                         updateInfo(QModelIndex());
202                 else
203                         updateInfo(availableLV->currentIndex());
204         }
205         setButtons();
206
207         textBeforeED->setText(textBefore());
208         textAfterED->setText(textAfter());
209         fillStyles();
210         updateStyle();
211 }
212
213
214 void GuiCitation::updateFormatting(biblio::CiteStyle currentStyle)
215 {
216         biblio::CiteEngine const engine = getEngine();
217         bool const natbib_engine =
218                 engine == biblio::ENGINE_NATBIB_AUTHORYEAR ||
219                 engine == biblio::ENGINE_NATBIB_NUMERICAL;
220         bool const basic_engine = engine == biblio::ENGINE_BASIC;
221
222         bool const haveSelection = 
223                 selectedLV->model()->rowCount() > 0;
224
225         bool const isNocite = currentStyle == biblio::NOCITE;
226
227         fulllistCB->setEnabled(natbib_engine && haveSelection && !isNocite);
228         forceuppercaseCB->setEnabled(natbib_engine && haveSelection && !isNocite);
229         textBeforeED->setEnabled(!basic_engine && haveSelection && !isNocite);
230         textBeforeLA->setEnabled(!basic_engine && haveSelection && !isNocite);
231         textAfterED->setEnabled(haveSelection && !isNocite);
232         textAfterLA->setEnabled(haveSelection && !isNocite);
233         citationStyleCO->setEnabled(haveSelection);
234         citationStyleLA->setEnabled(haveSelection);
235 }
236
237
238 void GuiCitation::updateStyle()
239 {
240         string const & command = params_.getCmdName();
241
242         // Find the style of the citekeys
243         vector<biblio::CiteStyle> const & styles = citeStyles_;
244         biblio::CitationStyle const cs(command);
245
246         vector<biblio::CiteStyle>::const_iterator cit =
247                 std::find(styles.begin(), styles.end(), cs.style);
248
249         // restore the latest natbib style
250         if (style_ >= 0 && style_ < citationStyleCO->count())
251                 citationStyleCO->setCurrentIndex(style_);
252         else
253                 citationStyleCO->setCurrentIndex(0);
254
255         if (cit != styles.end()) {
256                 int const i = int(cit - styles.begin());
257                 citationStyleCO->setCurrentIndex(i);
258                 fulllistCB->setChecked(cs.full);
259                 forceuppercaseCB->setChecked(cs.forceUCase);
260         } else {
261                 fulllistCB->setChecked(false);
262                 forceuppercaseCB->setChecked(false);
263         }
264         updateFormatting(cs.style);
265 }
266
267 //This one needs to be called whenever citationStyleCO needs
268 //to be updated---and this would be on anything that changes the
269 //selection in selectedLV, or on a general update.
270 void GuiCitation::fillStyles()
271 {
272         int const oldIndex = citationStyleCO->currentIndex();
273
274         citationStyleCO->clear();
275
276         QStringList selected_keys = selected()->stringList();
277         if (selected_keys.empty()) {
278                 citationStyleCO->setEnabled(false);
279                 citationStyleLA->setEnabled(false);
280                 return;
281         }
282
283         int curr = selectedLV->model()->rowCount() - 1;
284         if (curr < 0)
285                 return;
286
287         if (!selectedLV->selectionModel()->selectedIndexes().empty())
288                 curr = selectedLV->selectionModel()->selectedIndexes()[0].row();
289
290         QStringList sty = citationStyles(curr);
291
292         citationStyleCO->setEnabled(!sty.isEmpty());
293         citationStyleLA->setEnabled(!sty.isEmpty());
294
295         if (sty.isEmpty())
296                 return;
297
298         citationStyleCO->insertItems(0, sty);
299
300         if (oldIndex != -1 && oldIndex < citationStyleCO->count())
301                 citationStyleCO->setCurrentIndex(oldIndex);
302 }
303
304
305 void GuiCitation::fillFields()
306 {
307         fieldsCO->blockSignals(true);
308         int const oldIndex = fieldsCO->currentIndex();
309         fieldsCO->clear();
310         QStringList const & fields = getFieldsAsQStringList();
311         fieldsCO->insertItem(0, qt_("All Fields"));
312         fieldsCO->insertItem(1, qt_("Keys"));
313         fieldsCO->insertItems(2, fields);
314         if (oldIndex != -1 && oldIndex < fieldsCO->count())
315                 fieldsCO->setCurrentIndex(oldIndex);
316         fieldsCO->blockSignals(false);
317 }
318
319
320 void GuiCitation::fillEntries()
321 {
322         entriesCO->blockSignals(true);
323         int const oldIndex = entriesCO->currentIndex();
324         entriesCO->clear();
325         QStringList const & entries = getEntriesAsQStringList();
326         entriesCO->insertItem(0, qt_("All Entry Types"));
327         entriesCO->insertItems(1, entries);
328         if (oldIndex != -1 && oldIndex < entriesCO->count())
329                 entriesCO->setCurrentIndex(oldIndex);
330         entriesCO->blockSignals(false);
331 }
332
333
334 bool GuiCitation::isSelected(const QModelIndex & idx)
335 {
336         QString const str = idx.data().toString();
337         return selected()->stringList().contains(str);
338 }
339
340
341 void GuiCitation::setButtons()
342 {
343         selectionManager->update();
344         int const srows = selectedLV->model()->rowCount();
345         applyPB->setEnabled(srows > 0);
346         okPB->setEnabled(srows > 0);
347 }
348
349
350 void GuiCitation::updateInfo(QModelIndex const & idx)
351 {
352         if (idx.isValid()) {
353                 QString const keytxt = getKeyInfo(idx.data().toString());
354                 infoML->document()->setPlainText(keytxt);
355         } else
356                 infoML->document()->clear();
357 }
358
359
360 void GuiCitation::findText(QString const & text, bool reset)
361 {
362         //"All Fields" and "Keys" are the first two
363         int index = fieldsCO->currentIndex() - 2; 
364         vector<docstring> const & fields = availableFields();
365         docstring field;
366         
367         if (index <= -1 || index >= int(fields.size()))
368                 //either "All Fields" or "Keys" or an invalid value
369                 field = from_ascii("");
370         else
371                 field = fields[index];
372         
373         //Was it "Keys"?
374         bool const onlyKeys = index == -1;
375         
376         //"All Entry Types" is first.
377         index = entriesCO->currentIndex() - 1; 
378         vector<docstring> const & entries = availableEntries();
379         docstring entry_type;
380         if (index < 0 || index >= int(entries.size()))
381                 entry_type = from_ascii("");
382         else 
383                 entry_type = entries[index];
384         
385         bool const case_sentitive = caseCB->checkState();
386         bool const reg_exp = regexCB->checkState();
387         findKey(text, onlyKeys, field, entry_type, 
388                        case_sentitive, reg_exp, reset);
389         //FIXME
390         //It'd be nice to save and restore the current selection in 
391         //availableLV. Currently, we get an automatic reset, since the
392         //model is reset.
393         
394         updateDialog();
395 }
396
397
398 void GuiCitation::on_fieldsCO_currentIndexChanged(int /*index*/)
399 {
400         findText(findLE->text(), true);
401 }
402
403
404 void GuiCitation::on_entriesCO_currentIndexChanged(int /*index*/)
405 {
406         findText(findLE->text(), true);
407 }
408
409
410 void GuiCitation::on_citationStyleCO_currentIndexChanged(int index)
411 {
412         if (index >= 0 && index < citationStyleCO->count()) {
413                 vector<biblio::CiteStyle> const & styles = citeStyles_;
414                 updateFormatting(styles[index]);
415         }
416 }
417
418
419 void GuiCitation::on_findLE_textChanged(const QString & text)
420 {
421         clearPB->setDisabled(text.isEmpty());
422         if (text.isEmpty())
423                 findLE->setFocus();
424         findText(text);
425 }
426
427
428 void GuiCitation::on_caseCB_stateChanged(int)
429 {
430         findText(findLE->text());
431 }
432
433
434 void GuiCitation::on_regexCB_stateChanged(int)
435 {
436         findText(findLE->text());
437 }
438
439
440 void GuiCitation::changed()
441 {
442         setButtons();
443 }
444
445
446 void GuiCitation::apply(int const choice, bool full, bool force,
447         QString before, QString after)
448 {
449         if (cited_keys_.isEmpty())
450                 return;
451
452         vector<biblio::CiteStyle> const & styles = citeStyles_;
453         if (styles[choice] == biblio::NOCITE) {
454                 full = false;
455                 force = false;
456                 before.clear();
457                 after.clear();
458         }
459         
460         string const command =
461                 biblio::CitationStyle(styles[choice], full, force)
462                 .asLatexStr();
463
464         params_.setCmdName(command);
465         params_["key"] = qstring_to_ucs4(cited_keys_.join(","));
466         params_["before"] = qstring_to_ucs4(before);
467         params_["after"] = qstring_to_ucs4(after);
468         dispatchParams();
469 }
470
471
472 void GuiCitation::clearSelection()
473 {
474         cited_keys_.clear();
475         selected_model_.setStringList(cited_keys_);
476 }
477
478
479 QString GuiCitation::textBefore()
480 {
481         return toqstr(params_["before"]);
482 }
483
484
485 QString GuiCitation::textAfter()
486 {
487         return toqstr(params_["after"]);
488 }
489
490
491 void GuiCitation::init()
492 {
493         // Make the list of all available bibliography keys
494         all_keys_ = to_qstring_list(availableKeys());
495         available_model_.setStringList(all_keys_);
496
497         // Ditto for the keys cited in this inset
498         QString str = toqstr(params_["key"]);
499         if (str.isEmpty())
500                 cited_keys_.clear();
501         else
502                 cited_keys_ = str.split(",");
503         selected_model_.setStringList(cited_keys_);
504 }
505
506
507 void GuiCitation::findKey(QString const & str, bool only_keys,
508         docstring field, docstring entry_type,
509         bool case_sensitive, bool reg_exp, bool reset)
510 {
511         // Used for optimisation: store last searched string.
512         static QString last_searched_string;
513         // Used to disable the above optimisation.
514         static bool last_case_sensitive;
515         static bool last_reg_exp;
516         // Reset last_searched_string in case of changed option.
517         if (last_case_sensitive != case_sensitive
518                 || last_reg_exp != reg_exp) {
519                         LYXERR(Debug::GUI, "GuiCitation::findKey: optimisation disabled!");
520                 last_searched_string.clear();
521         }
522         // save option for next search.
523         last_case_sensitive = case_sensitive;
524         last_reg_exp = reg_exp;
525
526         Qt::CaseSensitivity qtcase = case_sensitive?
527                         Qt::CaseSensitive: Qt::CaseInsensitive;
528         QStringList keys;
529         // If new string (str) contains the last searched one...
530         if (!reset &&
531                 !last_searched_string.isEmpty() &&
532                 str.size() > 1 &&
533                 str.contains(last_searched_string, qtcase))
534                 // ... then only search within already found list.
535                 keys = available_model_.stringList();
536         else
537                 // ... else search all keys.
538                 keys = all_keys_;
539         // save searched string for next search.
540         last_searched_string = str;
541
542         QStringList result;
543         
544         // First, filter by entry_type, which will be faster than 
545         // what follows, so we may get to do that on less.
546         vector<docstring> keyVector = to_docstring_vector(keys);
547         filterByEntryType(keyVector, entry_type);
548         
549         if (str.isEmpty())
550                 result = to_qstring_list(keyVector);
551         else
552                 result = to_qstring_list(searchKeys(keyVector, only_keys, 
553                         qstring_to_ucs4(str), field, case_sensitive, reg_exp));
554         
555         available_model_.setStringList(result);
556 }
557
558
559 QStringList GuiCitation::getFieldsAsQStringList()
560 {
561         return to_qstring_list(availableFields());
562 }
563
564
565 QStringList GuiCitation::getEntriesAsQStringList()
566 {
567         return to_qstring_list(availableEntries());
568 }
569
570
571 QStringList GuiCitation::citationStyles(int sel)
572 {
573         docstring const key = qstring_to_ucs4(cited_keys_[sel]);
574         return to_qstring_list(bibkeysInfo_.getCiteStrings(key, buffer()));
575 }
576
577
578 QString GuiCitation::getKeyInfo(QString const & sel)
579 {
580         return toqstr(getInfo(qstring_to_ucs4(sel)));
581 }
582
583
584 void GuiCitation::setCitedKeys() 
585 {
586         cited_keys_ = selected_model_.stringList();
587 }
588
589
590 bool GuiCitation::initialiseParams(string const & data)
591 {
592         InsetCommandMailer::string2params(lfun_name_, data, params_);
593
594         biblio::CiteEngine const engine = buffer().params().getEngine();
595
596         bibkeysInfo_.fillWithBibKeys(&buffer());
597         
598         citeStyles_ = biblio::getCiteStyles(engine);
599
600         return true;
601 }
602
603
604 void GuiCitation::clearParams()
605 {
606         params_.clear();
607         bibkeysInfo_.clear();
608 }
609
610
611 vector<docstring> const GuiCitation::availableKeys() const
612 {
613         return bibkeysInfo_.getKeys();
614 }
615
616
617 vector<docstring> const GuiCitation::availableFields() const
618 {
619         return bibkeysInfo_.getFields();
620 }
621
622
623 vector<docstring> const GuiCitation::availableEntries() const
624 {
625         return bibkeysInfo_.getEntries();
626 }
627
628
629 void GuiCitation::filterByEntryType(
630         vector<docstring> & keyVector, docstring entry_type) 
631 {
632         if (entry_type.empty())
633                 return;
634         
635         vector<docstring>::iterator it = keyVector.begin();
636         vector<docstring>::iterator end = keyVector.end();
637         
638         vector<docstring> result;
639         for (; it != end; ++it) {
640                 docstring const key = *it;
641                 BiblioInfo::const_iterator cit = bibkeysInfo_.find(key);
642                 if (cit == bibkeysInfo_.end())
643                         continue;
644                 if (cit->second.entryType() == entry_type)
645                         result.push_back(key);
646         }
647         keyVector = result;
648 }
649
650
651 biblio::CiteEngine GuiCitation::getEngine() const
652 {
653         return buffer().params().getEngine();
654 }
655
656
657 docstring GuiCitation::getInfo(docstring const & key) const
658 {
659         if (bibkeysInfo_.empty())
660                 return docstring();
661
662         return bibkeysInfo_.getInfo(key);
663 }
664
665
666 // Escape special chars.
667 // All characters are literals except: '.|*?+(){}[]^$\'
668 // These characters are literals when preceded by a "\", which is done here
669 // @todo: This function should be moved to support, and then the test in tests
670 //        should be moved there as well.
671 static docstring escape_special_chars(docstring const & expr)
672 {
673         // Search for all chars '.|*?+(){}[^$]\'
674         // Note that '[' and '\' must be escaped.
675         // This is a limitation of boost::regex, but all other chars in BREs
676         // are assumed literal.
677         static const boost::regex reg("[].|*?+(){}^$\\[\\\\]");
678
679         // $& is a perl-like expression that expands to all
680         // of the current match
681         // The '$' must be prefixed with the escape character '\' for
682         // boost to treat it as a literal.
683         // Thus, to prefix a matched expression with '\', we use:
684         // FIXME: UNICODE
685         return from_utf8(boost::regex_replace(to_utf8(expr), reg, "\\\\$&"));
686 }
687
688
689 vector<docstring> GuiCitation::searchKeys(
690         vector<docstring> const & keys_to_search, bool only_keys,
691         docstring const & search_expression, docstring field,
692         bool case_sensitive, bool regex)
693 {
694         vector<docstring> foundKeys;
695
696         docstring expr = trim(search_expression);
697         if (expr.empty())
698                 return foundKeys;
699
700         if (!regex)
701                 // We must escape special chars in the search_expr so that
702                 // it is treated as a simple string by boost::regex.
703                 expr = escape_special_chars(expr);
704
705         boost::regex reg_exp;
706         try {
707                 reg_exp.assign(to_utf8(expr), case_sensitive ?
708                         boost::regex_constants::normal : boost::regex_constants::icase);
709         } catch (boost::regex_error & e) {
710                 // boost::regex throws an exception if the regular expression is not
711                 // valid.
712                 LYXERR(Debug::GUI, e.what());
713                 return vector<docstring>();
714         }
715
716         vector<docstring>::const_iterator it = keys_to_search.begin();
717         vector<docstring>::const_iterator end = keys_to_search.end();
718         for (; it != end; ++it ) {
719                 BiblioInfo::const_iterator info = bibkeysInfo_.find(*it);
720                 if (info == bibkeysInfo_.end())
721                         continue;
722                 
723                 BibTeXInfo const & kvm = info->second;
724                 string data;
725                 if (only_keys)
726                         data = to_utf8(*it);
727                 else if (field.empty())
728                         data = to_utf8(*it) + ' ' + to_utf8(kvm.allData());
729                 else if (kvm.hasField(field))
730                         data = to_utf8(kvm.getValueForField(field));
731                 
732                 if (data.empty())
733                         continue;
734
735                 try {
736                         if (boost::regex_search(data, reg_exp))
737                                 foundKeys.push_back(*it);
738                 }
739                 catch (boost::regex_error & e) {
740                         LYXERR(Debug::GUI, e.what());
741                         return vector<docstring>();
742                 }
743         }
744         return foundKeys;
745 }
746
747
748 Dialog * createGuiCitation(GuiView & lv) { return new GuiCitation(lv); }
749
750
751 } // namespace frontend
752 } // namespace lyx
753
754 #include "GuiCitation_moc.cpp"
755