]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
Complete the removal of the embedding stuff. Maybe. It's hard to be sure we got every...
[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 = citeEngine();
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         InsetCommand::string2params(lfun_name_, data, params_);
593         biblio::CiteEngine const engine = buffer().params().citeEngine();
594         bibkeysInfo_.fillWithBibKeys(&buffer());
595         citeStyles_ = biblio::getCiteStyles(engine);
596         return true;
597 }
598
599
600 void GuiCitation::clearParams()
601 {
602         params_.clear();
603         bibkeysInfo_.clear();
604 }
605
606
607 vector<docstring> GuiCitation::availableKeys() const
608 {
609         return bibkeysInfo_.getKeys();
610 }
611
612
613 vector<docstring> GuiCitation::availableFields() const
614 {
615         return bibkeysInfo_.getFields();
616 }
617
618
619 vector<docstring> GuiCitation::availableEntries() const
620 {
621         return bibkeysInfo_.getEntries();
622 }
623
624
625 void GuiCitation::filterByEntryType(
626         vector<docstring> & keyVector, docstring entry_type) 
627 {
628         if (entry_type.empty())
629                 return;
630         
631         vector<docstring>::iterator it = keyVector.begin();
632         vector<docstring>::iterator end = keyVector.end();
633         
634         vector<docstring> result;
635         for (; it != end; ++it) {
636                 docstring const key = *it;
637                 BiblioInfo::const_iterator cit = bibkeysInfo_.find(key);
638                 if (cit == bibkeysInfo_.end())
639                         continue;
640                 if (cit->second.entryType() == entry_type)
641                         result.push_back(key);
642         }
643         keyVector = result;
644 }
645
646
647 biblio::CiteEngine GuiCitation::citeEngine() const
648 {
649         return buffer().params().citeEngine();
650 }
651
652
653 docstring GuiCitation::getInfo(docstring const & key) const
654 {
655         if (bibkeysInfo_.empty())
656                 return docstring();
657
658         return bibkeysInfo_.getInfo(key);
659 }
660
661
662 // Escape special chars.
663 // All characters are literals except: '.|*?+(){}[]^$\'
664 // These characters are literals when preceded by a "\", which is done here
665 // @todo: This function should be moved to support, and then the test in tests
666 //        should be moved there as well.
667 static docstring escape_special_chars(docstring const & expr)
668 {
669         // Search for all chars '.|*?+(){}[^$]\'
670         // Note that '[' and '\' must be escaped.
671         // This is a limitation of boost::regex, but all other chars in BREs
672         // are assumed literal.
673         static const boost::regex reg("[].|*?+(){}^$\\[\\\\]");
674
675         // $& is a perl-like expression that expands to all
676         // of the current match
677         // The '$' must be prefixed with the escape character '\' for
678         // boost to treat it as a literal.
679         // Thus, to prefix a matched expression with '\', we use:
680         // FIXME: UNICODE
681         return from_utf8(boost::regex_replace(to_utf8(expr), reg, "\\\\$&"));
682 }
683
684
685 vector<docstring> GuiCitation::searchKeys(
686         vector<docstring> const & keys_to_search, bool only_keys,
687         docstring const & search_expression, docstring field,
688         bool case_sensitive, bool regex)
689 {
690         vector<docstring> foundKeys;
691
692         docstring expr = trim(search_expression);
693         if (expr.empty())
694                 return foundKeys;
695
696         if (!regex)
697                 // We must escape special chars in the search_expr so that
698                 // it is treated as a simple string by boost::regex.
699                 expr = escape_special_chars(expr);
700
701         boost::regex reg_exp;
702         try {
703                 reg_exp.assign(to_utf8(expr), case_sensitive ?
704                         boost::regex_constants::normal : boost::regex_constants::icase);
705         } catch (boost::regex_error & e) {
706                 // boost::regex throws an exception if the regular expression is not
707                 // valid.
708                 LYXERR(Debug::GUI, e.what());
709                 return vector<docstring>();
710         }
711
712         vector<docstring>::const_iterator it = keys_to_search.begin();
713         vector<docstring>::const_iterator end = keys_to_search.end();
714         for (; it != end; ++it ) {
715                 BiblioInfo::const_iterator info = bibkeysInfo_.find(*it);
716                 if (info == bibkeysInfo_.end())
717                         continue;
718                 
719                 BibTeXInfo const & kvm = info->second;
720                 string data;
721                 if (only_keys)
722                         data = to_utf8(*it);
723                 else if (field.empty())
724                         data = to_utf8(*it) + ' ' + to_utf8(kvm.allData());
725                 else if (kvm.hasField(field))
726                         data = to_utf8(kvm.getValueForField(field));
727                 
728                 if (data.empty())
729                         continue;
730
731                 try {
732                         if (boost::regex_search(data, reg_exp))
733                                 foundKeys.push_back(*it);
734                 }
735                 catch (boost::regex_error & e) {
736                         LYXERR(Debug::GUI, e.what());
737                         return vector<docstring>();
738                 }
739         }
740         return foundKeys;
741 }
742
743
744 Dialog * createGuiCitation(GuiView & lv) { return new GuiCitation(lv); }
745
746
747 } // namespace frontend
748 } // namespace lyx
749
750 #include "GuiCitation_moc.cpp"
751