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