]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiBibtex.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[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         editPB->setEnabled(deletePB->isEnabled());
189         changed();
190 }
191
192
193 void GuiBibtex::on_buttonBox_accepted()
194 {
195         applyView();
196         clearSelection();
197         hide();
198 }
199
200
201 void GuiBibtex::browseBstPressed()
202 {
203         QString const file = browseBst(QString());
204
205         if (file.isEmpty())
206                 return;
207
208         QString const filen = changeExtension(file, "");
209         bool present = false;
210         int pres = 0;
211
212         for (int i = 0; i != styleCB->count(); ++i) {
213                 if (styleCB->itemText(i) == filen) {
214                         present = true;
215                         pres = i;
216                 }
217         }
218
219         if (!present)
220                 styleCB->insertItem(0, filen);
221
222         styleCB->setCurrentIndex(pres);
223         changed();
224 }
225
226
227 void GuiBibtex::browseBibPressed()
228 {
229         QString const file = browseBib(QString()).trimmed();
230
231         if (file.isEmpty())
232                 return;
233
234         QString const f = changeExtension(file, "");
235
236         if (!selected_bibs_.contains(f)) {
237                 selected_bibs_.append(f);
238                 setSelectedBibs(selected_bibs_);
239                 changed();
240         }
241 }
242
243
244 void GuiBibtex::on_editPB_clicked()
245 {
246         QModelIndexList selIdx =
247                 selectedLV->selectionModel()->selectedIndexes();
248         if (selIdx.isEmpty())
249                 return;
250         QModelIndex idx = selIdx.first();
251         QString sel = idx.data().toString();
252         FuncRequest fr(LFUN_INSET_EDIT, fromqstr(sel));
253         dispatch(fr);
254 }
255
256
257 void GuiBibtex::rescanClicked()
258 {
259         rescanBibStyles();
260         updateContents();
261 }
262
263
264 void GuiBibtex::clearSelection()
265 {
266         selected_bibs_.clear();
267         setSelectedBibs(selected_bibs_);
268 }
269
270
271 void GuiBibtex::setSelectedBibs(QStringList const sl)
272 {
273         selected_model_.clear();
274         QStringList headers;
275         headers << qt_("Database")
276                 << qt_("File Encoding");
277         selected_model_.setHorizontalHeaderLabels(headers);
278         bool const moreencs = usingBiblatex() && sl.count() > 1;
279         selectedLV->setColumnHidden(1, !moreencs);
280         selectedLV->verticalHeader()->setVisible(false);
281         selectedLV->horizontalHeader()->setVisible(moreencs);
282         if (moreencs) {
283                 bibEncodingLA->setText(qt_("General E&ncoding:"));
284                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
285                                               "encoding than the LyX document, specify it here. "
286                                               "If indivivual databases have different encodings, "
287                                               "you can set it in the list above."));
288         } else {
289                 bibEncodingLA->setText(qt_("E&ncoding:"));
290                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
291                                               "encoding than the LyX document, specify it here"));
292         }
293         QStringList::const_iterator it  = sl.begin();
294         QStringList::const_iterator end = sl.end();
295         for (int i = 0; it != end; ++it, ++i) {
296                 QStandardItem * si = new QStandardItem();
297                 si->setData(*it);
298                 si->setText(*it);
299                 si->setToolTip(*it);
300                 si->setEditable(false);
301                 selected_model_.insertRow(i, si);
302                 QComboBox * cb = new QComboBox;
303                 cb->addItem(qt_("General Encoding"), "general");
304                 cb->addItem(qt_("Document Encoding"), "auto");
305                 QMap<QString, QString>::const_iterator it = encodings_.constBegin();
306                 while (it != encodings_.constEnd()) {
307                         cb->addItem(it.key(), it.value());
308                         ++it;
309                 }
310                 cb->setToolTip(qt_("If this bibliography database uses a different "
311                                    "encoding than specified below, set it here"));
312                 selectedLV->setIndexWidget(selected_model_.index(i, 1), cb);
313         }
314         editPB->setEnabled(deletePB->isEnabled());
315 }
316
317
318 QStringList GuiBibtex::selectedBibs()
319 {
320         QStringList res;
321         for (int i = 0; i != selected_model_.rowCount(); ++i) {
322                 QStandardItem const * item = selected_model_.item(i);
323                 if (item)
324                         res.append(item->text());
325         }
326         return res;
327 }
328
329
330 void GuiBibtex::databaseChanged()
331 {
332         selected_bibs_ = selectedBibs();
333         setSelectedBibs(selected_bibs_);
334 }
335
336
337 void GuiBibtex::updateContents()
338 {
339         bool bibtopic = usingBibtopic();
340         bool biblatex = usingBiblatex();
341
342         if (biblatex)
343                 setTitle(qt_("Biblatex Bibliography"));
344         else
345                 setTitle(qt_("BibTeX Bibliography"));
346
347         QString const bibstyle = styleFile();
348
349         bibtocCB->setChecked(bibtotoc() && !bibtopic);
350         bibtocCB->setEnabled(!bibtopic);
351
352         btPrintCO->clear();
353         btPrintCO->addItem(qt_("all cited references"), toqstr("btPrintCited"));
354         if (bibtopic)
355                 btPrintCO->addItem(qt_("all uncited references"), toqstr("btPrintNotCited"));
356         btPrintCO->addItem(qt_("all references"), toqstr("btPrintAll"));
357         if (usingBiblatex() && !buffer().masterParams().multibib.empty())
358                 btPrintCO->addItem(qt_("all reference units"), toqstr("bibbysection"));
359
360         docstring btprint = params_["btprint"];
361         if (btprint.empty())
362                 // default
363                 btprint = from_ascii("btPrintCited");
364         btPrintCO->setCurrentIndex(btPrintCO->findData(toqstr(btprint)));
365
366         docstring encoding = params_["encoding"];
367         if (encoding.empty())
368                 // default
369                 encoding = from_ascii("default");
370         bibEncodingCO->setCurrentIndex(bibEncodingCO->findData(toqstr(encoding)));
371
372         // Only useful for biblatex
373         biblatexOptsLA->setVisible(biblatex);
374         biblatexOptsLE->setVisible(biblatex);
375
376         // only useful for BibTeX
377         bstGB->setVisible(!biblatex);
378
379         if (!biblatex) {
380                 styleCB->clear();
381
382                 int item_nr = -1;
383
384                 QStringList const str = bibStyles();
385                 for (int i = 0; i != str.count(); ++i) {
386                         QString item = changeExtension(str[i], "");
387                         if (item == bibstyle)
388                                 item_nr = i;
389                         styleCB->addItem(item);
390                 }
391
392                 if (item_nr == -1 && !bibstyle.isEmpty()) {
393                         styleCB->addItem(bibstyle);
394                         item_nr = styleCB->count() - 1;
395                 }
396
397
398                 if (item_nr != -1)
399                         styleCB->setCurrentIndex(item_nr);
400                 else
401                         styleCB->clearEditText();
402         } else
403                 biblatexOptsLE->setText(toqstr(params_["biblatexopts"]));
404
405         setFileEncodings(getVectorFromString(params_["file_encodings"], from_ascii("\t")));
406         editPB->setEnabled(deletePB->isEnabled());
407 }
408
409
410 void GuiBibtex::applyView()
411 {
412         docstring dbs;
413
414         int maxCount = selected_bibs_.count();
415         for (int i = 0; i < maxCount; i++) {
416                 if (i != 0)
417                         dbs += ',';
418                 QString item = selected_bibs_.at(i);
419                 docstring bibfile = qstring_to_ucs4(item);
420                 dbs += bibfile;
421         }
422
423         params_["bibfiles"] = dbs;
424
425         docstring const bibstyle = qstring_to_ucs4(styleCB->currentText());
426         bool const bibtotoc = bibtocCB->isChecked();
427
428         if (bibtotoc && !bibstyle.empty()) {
429                 // both bibtotoc and style
430                 params_["options"] = "bibtotoc," + bibstyle;
431         } else if (bibtotoc) {
432                 // bibtotoc and no style
433                 params_["options"] = from_ascii("bibtotoc");
434         } else {
435                 // only style. An empty one is valid, because some
436                 // documentclasses have an own \bibliographystyle{}
437                 // command!
438                 params_["options"] = bibstyle;
439         }
440
441         params_["biblatexopts"] = qstring_to_ucs4(biblatexOptsLE->text());
442
443         params_["btprint"] = qstring_to_ucs4(btPrintCO->itemData(btPrintCO->currentIndex()).toString());
444
445         params_["encoding"] = qstring_to_ucs4(bibEncodingCO->itemData(bibEncodingCO->currentIndex()).toString());
446
447         if (usingBiblatex())
448                 params_["file_encodings"] = getStringFromVector(getFileEncodings(), from_ascii("\t"));
449 }
450
451
452 QString GuiBibtex::browseBib(QString const & in_name) const
453 {
454         QString const label1 = qt_("D&ocuments");
455         QString const dir1 = toqstr(lyxrc.document_path);
456         QStringList const filter(qt_("BibTeX Databases (*.bib)"));
457         return browseRelToParent(in_name, bufferFilePath(),
458                 qt_("Select a BibTeX database to add"), filter, false, label1, dir1);
459 }
460
461
462 QString GuiBibtex::browseBst(QString const & in_name) const
463 {
464         QString const label1 = qt_("D&ocuments");
465         QString const dir1 = toqstr(lyxrc.document_path);
466         QStringList const filter(qt_("BibTeX Styles (*.bst)"));
467         return browseRelToParent(in_name, bufferFilePath(),
468                 qt_("Select a BibTeX style"), filter, false, label1, dir1);
469 }
470
471
472 QStringList GuiBibtex::bibStyles() const
473 {
474         QStringList sdata = texFileList("bstFiles.lst");
475         // test whether we have a valid list, otherwise run rescan
476         if (sdata.isEmpty()) {
477                 rescanBibStyles();
478                 sdata = texFileList("bstFiles.lst");
479         }
480         for (int i = 0; i != sdata.size(); ++i)
481                 sdata[i] = onlyFileName(sdata[i]);
482         // sort on filename only (no path)
483         sdata.sort();
484         return sdata;
485 }
486
487
488 QStringList GuiBibtex::bibFiles(bool const extension) const
489 {
490         QStringList sdata = texFileList("bibFiles.lst");
491         // test whether we have a valid list, otherwise run rescan
492         if (sdata.isEmpty()) {
493                 rescanBibStyles();
494                 sdata = texFileList("bibFiles.lst");
495         }
496         for (int i = 0; i != sdata.size(); ++i)
497                 sdata[i] = extension ? onlyFileName(sdata[i])
498                                      : changeExtension(onlyFileName(sdata[i]), "");
499         // sort on filename only (no path)
500         sdata.sort();
501         return sdata;
502 }
503
504
505 vector<docstring> GuiBibtex::getFileEncodings()
506 {
507         vector<docstring> res;
508         for (int i = 0; i != selected_model_.rowCount(); ++i) {
509                 QStandardItem const * key = selected_model_.item(i, 0);
510                 QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(i, 1)));
511                 QString fenc = cb ? cb->itemData(cb->currentIndex()).toString() : QString();
512                 if (key && !key->text().isEmpty() && !fenc.isEmpty() && fenc != "general")
513                         res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(fenc));
514         }
515         return res;
516 }
517
518
519 void GuiBibtex::setFileEncodings(vector<docstring> const m)
520 {
521         for (docstring const & s: m) {
522                 docstring key;
523                 QString enc = toqstr(split(s, key, ' '));
524                 QModelIndexList qmil =
525                                 selected_model_.match(selected_model_.index(0, 0),
526                                                      Qt::DisplayRole, toqstr(key), 1,
527                                                      Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
528                 if (!qmil.empty()) {
529                         QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(qmil.front().row(), 1)));
530                         cb->setCurrentIndex(cb->findData(enc));
531                 }
532         }
533 }
534
535
536 void GuiBibtex::rescanBibStyles() const
537 {
538         if (usingBiblatex())
539                 rescanTexStyles("bib");
540         else
541                 rescanTexStyles("bst bib");
542 }
543
544
545 void GuiBibtex::findText(QString const & text)
546 {
547         QStringList const result = bibFiles(false).filter(text);
548         available_model_.setStringList(result);
549 }
550
551
552 void GuiBibtex::filterChanged(const QString & text)
553 {
554         if (!text.isEmpty()) {
555                 findText(filter_->text());
556                 return;
557         }
558         findText(filter_->text());
559         filter_->setFocus();
560 }
561
562
563 void GuiBibtex::filterPressed()
564 {
565         findText(filter_->text());
566 }
567
568
569 void GuiBibtex::resetFilter()
570 {
571         filter_->setText(QString());
572         findText(filter_->text());
573 }
574
575
576
577 bool GuiBibtex::usingBibtopic() const
578 {
579         return buffer().params().useBibtopic();
580 }
581
582
583 bool GuiBibtex::bibtotoc() const
584 {
585         return prefixIs(to_utf8(params_["options"]), "bibtotoc");
586 }
587
588
589 bool GuiBibtex::usingBiblatex() const
590 {
591         return buffer().masterBuffer()->params().useBiblatex();
592 }
593
594
595 QString GuiBibtex::styleFile() const
596 {
597         // the different bibtex packages have (and need) their
598         // own "plain" stylefiles
599         QString defaultstyle = toqstr(buffer().params().defaultBiblioStyle());
600
601         QString bst = toqstr(params_["options"]);
602         if (bibtotoc()){
603                 // bibstyle exists?
604                 int pos = bst.indexOf(',');
605                 if (pos != -1) {
606                         // FIXME: check
607                         // docstring bibtotoc = from_ascii("bibtotoc");
608                         // bst = split(bst, bibtotoc, ',');
609                         bst = bst.mid(pos + 1);
610                 } else {
611                         bst.clear();
612                 }
613         }
614
615         // propose default style file for new insets
616         // existing insets might have (legally) no bst files
617         // (if the class already provides a style)
618         if (bst.isEmpty() && params_["bibfiles"].empty())
619                 bst = defaultstyle;
620
621         return bst;
622 }
623
624
625 bool GuiBibtex::initialiseParams(std::string const & sdata)
626 {
627         InsetCommand::string2params(sdata, params_);
628         init();
629         return true;
630 }
631
632
633 void GuiBibtex::dispatchParams()
634 {
635         std::string const lfun = InsetCommand::params2string(params_);
636         dispatch(FuncRequest(getLfun(), lfun));
637 }
638
639
640 Dialog * createGuiBibtex(GuiView & lv) { return new GuiBibtex(lv); }
641
642
643 } // namespace frontend
644 } // namespace lyx
645
646 #include "moc_GuiBibtex.cpp"