]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiBibtex.cpp
Cut excessively long author lists before parsing them for the GUI
[lyx.git] / src / frontends / qt / GuiBibtex.cpp
1 /**
2  * \file GuiBibtex.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Herbert Voß
8  * \author Angus Leeming
9  * \author Jürgen Spitzmüller
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiBibtex.h"
17
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "CiteEnginesList.h"
21 #include "Encoding.h"
22 #include "FuncRequest.h"
23 #include "GuiApplication.h"
24 #include "LyXRC.h"
25 #include "qt_helpers.h"
26 #include "TextClass.h"
27 #include "Validator.h"
28
29 #include "ButtonPolicy.h"
30 #include "FancyLineEdit.h"
31
32 #include "frontends/alert.h"
33
34 #include "insets/InsetBibtex.h"
35
36 #include "support/debug.h"
37 #include "support/docstring_list.h"
38 #include "support/ExceptionMessage.h"
39 #include "support/FileName.h"
40 #include "support/filetools.h" // changeExtension
41 #include "support/gettext.h"
42 #include "support/lstrings.h"
43
44 #include <QDialogButtonBox>
45 #include <QPushButton>
46 #include <QListWidget>
47 #include <QCheckBox>
48 #include <QLineEdit>
49
50 using namespace std;
51 using namespace lyx::support;
52
53 namespace lyx {
54 namespace frontend {
55
56
57 GuiBibtex::GuiBibtex(GuiView & lv)
58         : GuiDialog(lv, "bibtex", qt_("BibTeX Bibliography")),
59           params_(insetCode("bibtex"))
60 {
61         setupUi(this);
62
63         QDialog::setModal(true);
64         setWindowModality(Qt::WindowModal);
65
66         // The filter bar
67         filter_ = new FancyLineEdit(this);
68         filter_->setClearButton(true);
69         filter_->setPlaceholderText(qt_("All avail. databases"));
70
71         filterBarL->addWidget(filter_, 0);
72         findKeysLA->setBuddy(filter_);
73
74         connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
75                 this, SLOT(slotButtonBox(QAbstractButton *)));
76         connect(stylePB, SIGNAL(clicked()),
77                 this, SLOT(browseBstPressed()));
78         connect(styleCB, SIGNAL(editTextChanged(QString)),
79                 this, SLOT(change_adaptor()));
80         connect(bibtocCB, SIGNAL(clicked()),
81                 this, SLOT(change_adaptor()));
82         connect(btPrintCO, SIGNAL(activated(int)),
83                 this, SLOT(change_adaptor()));
84         connect(rescanPB, SIGNAL(clicked()),
85                 this, SLOT(rescanClicked()));
86         connect(biblatexOptsLE, SIGNAL(textChanged(QString)),
87                 this, SLOT(change_adaptor()));
88         connect(bibEncodingCO, SIGNAL(activated(int)),
89                 this, SLOT(change_adaptor()));
90         connect(browseBibPB, SIGNAL(clicked()),
91                 this, SLOT(browseBibPressed()));
92         connect(inheritPB, SIGNAL(clicked()),
93                 this, SLOT(inheritPressed()));
94
95         selected_model_.insertColumns(0, 1);
96         selectionManager = new GuiSelectionManager(this, availableLV, selectedLV,
97                         addBibPB, deletePB, upPB, downPB, &available_model_, &selected_model_);
98         connect(selectionManager, SIGNAL(selectionChanged()),
99                 this, SLOT(databaseChanged()));
100         connect(selectionManager, SIGNAL(updateHook()),
101                 this, SLOT(selUpdated()));
102         connect(selectionManager, SIGNAL(okHook()),
103                 this, SLOT(on_buttonBox_accepted()));
104
105         connect(filter_, SIGNAL(rightButtonClicked()),
106                 this, SLOT(resetFilter()));
107         connect(filter_, SIGNAL(textEdited(QString)),
108                 this, SLOT(filterChanged(QString)));
109         connect(filter_, SIGNAL(returnPressed()),
110                 this, SLOT(filterPressed()));
111         connect(filter_, &FancyLineEdit::downPressed,
112                 availableLV, [this](){ focusAndHighlight(availableLV); });
113
114         availableLV->setToolTip(formatToolTip(qt_("This list consists of all databases that are indexed by LaTeX and thus are found without a file path. "
115                                     "This is usually everything in the bib/ subdirectory of LaTeX's texmf tree. "
116                                     "If you want to reuse your own database, this is the place you should store it.")));
117
118         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
119         bc().setOK(buttonBox->button(QDialogButtonBox::Ok));
120         bc().setApply(buttonBox->button(QDialogButtonBox::Apply));
121         bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
122         bc().addReadOnly(stylePB);
123         bc().addReadOnly(styleCB);
124         bc().addReadOnly(bibtocCB);
125         bc().addReadOnly(bibEncodingCO);
126
127         selectedLV->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
128
129         // Always put the default encoding in the first position.
130         bibEncodingCO->addItem(qt_("Document Encoding"), "default");
131         for (auto const & encvar : encodings) {
132                 if (!encvar.unsafe() && !encvar.guiName().empty())
133                         encodings_.insert(qt_(encvar.guiName()), toqstr(encvar.name()));
134         }
135         QMap<QString, QString>::const_iterator it = encodings_.constBegin();
136         while (it != encodings_.constEnd()) {
137                 bibEncodingCO->addItem(it.key(), it.value());
138                 ++it;
139         }
140
141         setFocusProxy(filter_);
142 }
143
144
145 void GuiBibtex::init()
146 {
147         all_bibs_ = bibFiles(false);
148         available_model_.setStringList(all_bibs_);
149
150         QString bibs = toqstr(params_["bibfiles"]);
151         if (bibs.isEmpty())
152                 selected_bibs_.clear();
153         else
154                 selected_bibs_ = bibs.split(",");
155         setSelectedBibs(selected_bibs_);
156
157         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
158         buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
159         selectionManager->update();
160 }
161
162
163 void GuiBibtex::change_adaptor()
164 {
165         setButtons();
166         changed();
167 }
168
169
170 void GuiBibtex::setButtons()
171 {
172         int const srows = selectedLV->model()->rowCount();
173         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(srows > 0);
174         buttonBox->button(QDialogButtonBox::Ok)->setEnabled(srows > 0);
175         inheritPB->setEnabled(hasInherits());
176 }
177
178
179 void GuiBibtex::selUpdated()
180 {
181         selectionManager->update();
182         editPB->setEnabled(deletePB->isEnabled());
183         changed();
184 }
185
186
187 void GuiBibtex::on_buttonBox_accepted()
188 {
189         applyView();
190         clearSelection();
191         hide();
192 }
193
194
195 void GuiBibtex::browseBstPressed()
196 {
197         QString const file = browseBst(QString());
198
199         if (file.isEmpty())
200                 return;
201
202         QString const filen = changeExtension(file, "");
203         bool present = false;
204         int pres = 0;
205
206         for (int i = 0; i != styleCB->count(); ++i) {
207                 if (styleCB->itemText(i) == filen) {
208                         present = true;
209                         pres = i;
210                 }
211         }
212
213         if (!present)
214                 styleCB->insertItem(0, filen);
215
216         styleCB->setCurrentIndex(pres);
217         changed();
218 }
219
220
221 void GuiBibtex::browseBibPressed()
222 {
223         QString const file = browseBib(QString()).trimmed();
224
225         if (file.isEmpty())
226                 return;
227
228         QString const f = changeExtension(file, "");
229
230         if (!selected_bibs_.contains(f)) {
231                 selected_bibs_.append(f);
232                 setSelectedBibs(selected_bibs_);
233                 changed();
234         }
235 }
236
237
238 bool GuiBibtex::hasInherits()
239 {
240         if (!buffer().parent())
241                 return false;
242
243         docstring_list const mbibs = buffer().masterBuffer()->getBibfiles();
244         if (mbibs.empty())
245                 return false;
246
247         for (auto const & f : mbibs) {
248                 if (!selected_bibs_.contains(toqstr(f)))
249                         return true;
250         }
251         return false;
252 }
253
254
255 void GuiBibtex::inheritPressed()
256 {
257         docstring_list const mbibs = buffer().masterBuffer()->getBibfiles();
258         bool chng = false;
259         vector<docstring> nfe;
260         for (auto const & f : mbibs) {
261                 if (!selected_bibs_.contains(toqstr(f))) {
262                         selected_bibs_.append(toqstr(f));
263                         setSelectedBibs(selected_bibs_);
264                         string enc;
265                         if (usingBiblatex()) {
266                                 string const bfe = buffer().masterParams().bibFileEncoding(to_utf8(f));
267                                 if (!bfe.empty())
268                                         nfe.push_back(f + " " + from_utf8(bfe));
269                         }
270                         chng = true;
271                 }
272         }
273         if (chng) {
274                 if (!nfe.empty())
275                         setFileEncodings(nfe);
276                 change_adaptor();
277         }
278 }
279
280
281 void GuiBibtex::on_editPB_clicked()
282 {
283         QModelIndexList selIdx =
284                 selectedLV->selectionModel()->selectedIndexes();
285         if (selIdx.isEmpty())
286                 return;
287         QModelIndex idx = selIdx.first();
288         QString sel = idx.data().toString();
289         FuncRequest fr(LFUN_INSET_EDIT, fromqstr(sel));
290         dispatch(fr);
291 }
292
293
294 void GuiBibtex::rescanClicked()
295 {
296         rescanBibStyles();
297         updateContents();
298 }
299
300
301 void GuiBibtex::clearSelection()
302 {
303         selected_bibs_.clear();
304         setSelectedBibs(selected_bibs_);
305 }
306
307
308 void GuiBibtex::setSelectedBibs(QStringList const & sl)
309 {
310         selected_model_.clear();
311         QStringList headers;
312         headers << qt_("Database")
313                 << qt_("File Encoding");
314         selected_model_.setHorizontalHeaderLabels(headers);
315         bool const moreencs = usingBiblatex() && sl.count() > 1;
316         selectedLV->setColumnHidden(1, !moreencs);
317         selectedLV->verticalHeader()->setVisible(false);
318         selectedLV->horizontalHeader()->setVisible(moreencs);
319         if (moreencs) {
320                 bibEncodingLA->setText(qt_("General E&ncoding:"));
321                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
322                                               "encoding than the LyX document, specify it here. "
323                                               "If indivivual databases have different encodings, "
324                                               "you can set it in the list above."));
325         } else {
326                 bibEncodingLA->setText(qt_("E&ncoding:"));
327                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
328                                               "encoding than the LyX document, specify it here"));
329         }
330         QStringList::const_iterator it  = sl.begin();
331         QStringList::const_iterator end = sl.end();
332         for (int i = 0; it != end; ++it, ++i) {
333                 QStandardItem * si = new QStandardItem();
334                 si->setData(*it);
335                 si->setText(*it);
336                 si->setToolTip(*it);
337                 si->setEditable(false);
338                 selected_model_.insertRow(i, si);
339                 QComboBox * cb = new QComboBox;
340                 cb->addItem(qt_("General Encoding"), "general");
341                 cb->addItem(qt_("Document Encoding"), "auto");
342                 QMap<QString, QString>::const_iterator it = encodings_.constBegin();
343                 while (it != encodings_.constEnd()) {
344                         cb->addItem(it.key(), it.value());
345                         ++it;
346                 }
347                 cb->setToolTip(qt_("If this bibliography database uses a different "
348                                    "encoding than specified below, set it here"));
349                 selectedLV->setIndexWidget(selected_model_.index(i, 1), cb);
350         }
351         editPB->setEnabled(deletePB->isEnabled());
352 }
353
354
355 QStringList GuiBibtex::selectedBibs()
356 {
357         QStringList res;
358         for (int i = 0; i != selected_model_.rowCount(); ++i) {
359                 QStandardItem const * item = selected_model_.item(i);
360                 if (item)
361                         res.append(item->text());
362         }
363         return res;
364 }
365
366
367 void GuiBibtex::databaseChanged()
368 {
369         selected_bibs_ = selectedBibs();
370         setSelectedBibs(selected_bibs_);
371 }
372
373
374 void GuiBibtex::updateContents()
375 {
376         bool const bibtopic = usingBibtopic();
377         bool const biblatex = usingBiblatex();
378
379         if (biblatex)
380                 setTitle(qt_("Biblatex Bibliography"));
381         else
382                 setTitle(qt_("BibTeX Bibliography"));
383
384         QString const bibstyle = styleFile();
385
386         bool const hasbibintoc = buffer().params().documentClass().bibInToc()
387                         && !biblatex;
388
389         bibtocCB->setChecked((bibtotoc() && !bibtopic) || hasbibintoc);
390         bibtocCB->setEnabled(!bibtopic && !hasbibintoc);
391
392         inheritPB->setEnabled(hasInherits());
393
394         btPrintCO->clear();
395         btPrintCO->addItem(qt_("all cited references"), toqstr("btPrintCited"));
396         if (bibtopic)
397                 btPrintCO->addItem(qt_("all uncited references"), toqstr("btPrintNotCited"));
398         btPrintCO->addItem(qt_("all references"), toqstr("btPrintAll"));
399         if (usingBiblatex() && !buffer().masterParams().multibib.empty())
400                 btPrintCO->addItem(qt_("all reference units"), toqstr("bibbysection"));
401
402         docstring btprint = params_["btprint"];
403         if (btprint.empty())
404                 // default
405                 btprint = from_ascii("btPrintCited");
406         btPrintCO->setCurrentIndex(btPrintCO->findData(toqstr(btprint)));
407
408         docstring encoding = params_["encoding"];
409         if (encoding.empty())
410                 // default
411                 encoding = from_ascii("default");
412         bibEncodingCO->setCurrentIndex(bibEncodingCO->findData(toqstr(encoding)));
413
414         // Only useful for biblatex
415         biblatexOptsLA->setVisible(biblatex);
416         biblatexOptsLE->setVisible(biblatex);
417
418         // only useful for BibTeX
419         bstGB->setVisible(!biblatex);
420
421         if (!biblatex) {
422                 styleCB->clear();
423
424                 int item_nr = -1;
425
426                 QStringList const str = bibStyles();
427                 for (int i = 0; i != str.count(); ++i) {
428                         QString item = changeExtension(str[i], "");
429                         if (item == bibstyle)
430                                 item_nr = i;
431                         styleCB->addItem(item);
432                 }
433
434                 if (item_nr == -1 && !bibstyle.isEmpty()) {
435                         styleCB->addItem(bibstyle);
436                         item_nr = styleCB->count() - 1;
437                 }
438
439
440                 if (item_nr != -1)
441                         styleCB->setCurrentIndex(item_nr);
442                 else
443                         styleCB->clearEditText();
444         } else
445                 biblatexOptsLE->setText(toqstr(params_["biblatexopts"]));
446
447         setFileEncodings(getVectorFromString(params_["file_encodings"], from_ascii("\t")));
448         editPB->setEnabled(deletePB->isEnabled());
449 }
450
451
452 void GuiBibtex::applyView()
453 {
454         docstring dbs;
455
456         int maxCount = selected_bibs_.count();
457         for (int i = 0; i < maxCount; i++) {
458                 if (i != 0)
459                         dbs += ',';
460                 QString item = selected_bibs_.at(i);
461                 docstring bibfile = qstring_to_ucs4(item);
462                 dbs += bibfile;
463         }
464
465         params_["bibfiles"] = dbs;
466
467         docstring const bibstyle = qstring_to_ucs4(styleCB->currentText());
468         bool const bibtotoc = bibtocCB->isChecked();
469
470         if (bibtotoc && !bibstyle.empty()) {
471                 // both bibtotoc and style
472                 params_["options"] = "bibtotoc," + bibstyle;
473         } else if (bibtotoc) {
474                 // bibtotoc and no style
475                 params_["options"] = from_ascii("bibtotoc");
476         } else {
477                 // only style. An empty one is valid, because some
478                 // documentclasses have an own \bibliographystyle{}
479                 // command!
480                 params_["options"] = bibstyle;
481         }
482
483         params_["biblatexopts"] = qstring_to_ucs4(biblatexOptsLE->text());
484
485         params_["btprint"] = qstring_to_ucs4(btPrintCO->itemData(btPrintCO->currentIndex()).toString());
486
487         params_["encoding"] = qstring_to_ucs4(bibEncodingCO->itemData(bibEncodingCO->currentIndex()).toString());
488
489         if (usingBiblatex())
490                 params_["file_encodings"] = getStringFromVector(getFileEncodings(), from_ascii("\t"));
491 }
492
493
494 QString GuiBibtex::browseBib(QString const & in_name)
495 {
496         QString const label1 = qt_("D&ocuments");
497         QString const dir1 = toqstr(lyxrc.document_path);
498         QStringList const filter(qt_("BibTeX Databases (*.bib)"));
499         return browseRelToParent(in_name, bufferFilePath(),
500                 qt_("Select a BibTeX database to add"), filter, false, label1, dir1);
501 }
502
503
504 QString GuiBibtex::browseBst(QString const & in_name)
505 {
506         QString const label1 = qt_("D&ocuments");
507         QString const dir1 = toqstr(lyxrc.document_path);
508         QStringList const filter(qt_("BibTeX Styles (*.bst)"));
509         return browseRelToParent(in_name, bufferFilePath(),
510                 qt_("Select a BibTeX style"), filter, false, label1, dir1);
511 }
512
513
514 QStringList GuiBibtex::bibStyles() const
515 {
516         QStringList sdata = texFileList("bstFiles.lst");
517         // test whether we have a valid list, otherwise run rescan
518         if (sdata.isEmpty()) {
519                 rescanBibStyles();
520                 sdata = texFileList("bstFiles.lst");
521         }
522         for (int i = 0; i != sdata.size(); ++i)
523                 sdata[i] = onlyFileName(sdata[i]);
524         // sort on filename only (no path)
525         sdata.sort();
526         return sdata;
527 }
528
529
530 QStringList GuiBibtex::bibFiles(bool const extension) const
531 {
532         QStringList sdata = texFileList("bibFiles.lst");
533         // test whether we have a valid list, otherwise run rescan
534         if (sdata.isEmpty()) {
535                 rescanBibStyles();
536                 sdata = texFileList("bibFiles.lst");
537         }
538         for (int i = 0; i != sdata.size(); ++i)
539                 sdata[i] = extension ? onlyFileName(sdata[i])
540                                      : changeExtension(onlyFileName(sdata[i]), "");
541         // sort on filename only (no path)
542         sdata.sort();
543         return sdata;
544 }
545
546
547 vector<docstring> GuiBibtex::getFileEncodings()
548 {
549         vector<docstring> res;
550         for (int i = 0; i != selected_model_.rowCount(); ++i) {
551                 QStandardItem const * key = selected_model_.item(i, 0);
552                 QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(i, 1)));
553                 QString fenc = cb ? cb->itemData(cb->currentIndex()).toString() : QString();
554                 if (key && !key->text().isEmpty() && !fenc.isEmpty() && fenc != "general")
555                         res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(fenc));
556         }
557         return res;
558 }
559
560
561 void GuiBibtex::setFileEncodings(vector<docstring> const & m)
562 {
563         for (docstring const & s: m) {
564                 docstring key;
565                 QString enc = toqstr(split(s, key, ' '));
566                 QModelIndexList qmil =
567                                 selected_model_.match(selected_model_.index(0, 0),
568                                                      Qt::DisplayRole, toqstr(key), 1,
569                                                      Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
570                 if (!qmil.empty()) {
571                         QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(qmil.front().row(), 1)));
572                         cb->setCurrentIndex(cb->findData(enc));
573                 }
574         }
575 }
576
577
578 void GuiBibtex::rescanBibStyles() const
579 {
580         if (usingBiblatex())
581                 rescanTexStyles("bib");
582         else
583                 rescanTexStyles("bst bib");
584 }
585
586
587 void GuiBibtex::findText(QString const & text)
588 {
589         QStringList const result = bibFiles(false).filter(text);
590         available_model_.setStringList(result);
591 }
592
593
594 void GuiBibtex::filterChanged(const QString & text)
595 {
596         if (!text.isEmpty()) {
597                 findText(filter_->text());
598                 return;
599         }
600         findText(filter_->text());
601         filter_->setFocus();
602 }
603
604
605 void GuiBibtex::filterPressed()
606 {
607         findText(filter_->text());
608 }
609
610
611 void GuiBibtex::resetFilter()
612 {
613         filter_->setText(QString());
614         findText(filter_->text());
615 }
616
617
618
619 bool GuiBibtex::usingBibtopic() const
620 {
621         return buffer().params().useBibtopic();
622 }
623
624
625 bool GuiBibtex::bibtotoc() const
626 {
627         return prefixIs(to_utf8(params_["options"]), "bibtotoc");
628 }
629
630
631 bool GuiBibtex::usingBiblatex() const
632 {
633         return buffer().masterBuffer()->params().useBiblatex();
634 }
635
636
637 QString GuiBibtex::styleFile() const
638 {
639         // the different bibtex packages have (and need) their
640         // own "plain" stylefiles
641         QString defaultstyle = toqstr(buffer().params().defaultBiblioStyle());
642
643         QString bst = toqstr(params_["options"]);
644         if (bibtotoc()){
645                 // bibstyle exists?
646                 int pos = bst.indexOf(',');
647                 if (pos != -1) {
648                         // FIXME: check
649                         // docstring bibtotoc = from_ascii("bibtotoc");
650                         // bst = split(bst, bibtotoc, ',');
651                         bst = bst.mid(pos + 1);
652                 } else {
653                         bst.clear();
654                 }
655         }
656
657         // propose default style file for new insets
658         // existing insets might have (legally) no bst files
659         // (if the class already provides a style)
660         if (bst.isEmpty() && params_["bibfiles"].empty())
661                 bst = defaultstyle;
662
663         return bst;
664 }
665
666
667 bool GuiBibtex::initialiseParams(std::string const & sdata)
668 {
669         InsetCommand::string2params(sdata, params_);
670         init();
671         return true;
672 }
673
674
675 void GuiBibtex::dispatchParams()
676 {
677         std::string const lfun = InsetCommand::params2string(params_);
678         dispatch(FuncRequest(getLfun(), lfun));
679         connectToNewInset();
680 }
681
682
683 } // namespace frontend
684 } // namespace lyx
685
686 #include "moc_GuiBibtex.cpp"