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