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