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