]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiBibtex.cpp
booktabs: support for \cmidrule trimming
[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         QString const item = selectionManager->getSelectedIndex().data().toString();
333         if (!selected_bibs_.contains(item)) {
334                 selected_bibs_.append(item);
335         } else
336                 selected_bibs_ = selectedBibs();
337         setSelectedBibs(selected_bibs_);
338 }
339
340
341 void GuiBibtex::updateContents()
342 {
343         bool bibtopic = usingBibtopic();
344         bool biblatex = usingBiblatex();
345
346         if (biblatex)
347                 setTitle(qt_("Biblatex Bibliography"));
348         else
349                 setTitle(qt_("BibTeX Bibliography"));
350
351         QString const bibstyle = styleFile();
352
353         bibtocCB->setChecked(bibtotoc() && !bibtopic);
354         bibtocCB->setEnabled(!bibtopic);
355
356         btPrintCO->clear();
357         btPrintCO->addItem(qt_("all cited references"), toqstr("btPrintCited"));
358         if (bibtopic)
359                 btPrintCO->addItem(qt_("all uncited references"), toqstr("btPrintNotCited"));
360         btPrintCO->addItem(qt_("all references"), toqstr("btPrintAll"));
361         if (usingBiblatex() && !buffer().masterParams().multibib.empty())
362                 btPrintCO->addItem(qt_("all reference units"), toqstr("bibbysection"));
363
364         docstring btprint = params_["btprint"];
365         if (btprint.empty())
366                 // default
367                 btprint = from_ascii("btPrintCited");
368         btPrintCO->setCurrentIndex(btPrintCO->findData(toqstr(btprint)));
369
370         docstring encoding = params_["encoding"];
371         if (encoding.empty())
372                 // default
373                 encoding = from_ascii("default");
374         bibEncodingCO->setCurrentIndex(bibEncodingCO->findData(toqstr(encoding)));
375
376         // Only useful for biblatex
377         biblatexOptsLA->setVisible(biblatex);
378         biblatexOptsLE->setVisible(biblatex);
379
380         // only useful for BibTeX
381         bstGB->setVisible(!biblatex);
382
383         if (!biblatex) {
384                 styleCB->clear();
385
386                 int item_nr = -1;
387
388                 QStringList const str = bibStyles();
389                 for (int i = 0; i != str.count(); ++i) {
390                         QString item = changeExtension(str[i], "");
391                         if (item == bibstyle)
392                                 item_nr = i;
393                         styleCB->addItem(item);
394                 }
395
396                 if (item_nr == -1 && !bibstyle.isEmpty()) {
397                         styleCB->addItem(bibstyle);
398                         item_nr = styleCB->count() - 1;
399                 }
400
401
402                 if (item_nr != -1)
403                         styleCB->setCurrentIndex(item_nr);
404                 else
405                         styleCB->clearEditText();
406         } else
407                 biblatexOptsLE->setText(toqstr(params_["biblatexopts"]));
408
409         setFileEncodings(getVectorFromString(params_["file_encodings"], from_ascii("\t")));
410         editPB->setEnabled(deletePB->isEnabled());
411 }
412
413
414 void GuiBibtex::applyView()
415 {
416         docstring dbs;
417
418         int maxCount = selected_bibs_.count();
419         for (int i = 0; i < maxCount; i++) {
420                 if (i != 0)
421                         dbs += ',';
422                 QString item = selected_bibs_.at(i);
423                 docstring bibfile = qstring_to_ucs4(item);
424                 dbs += bibfile;
425         }
426
427         params_["bibfiles"] = dbs;
428
429         docstring const bibstyle = qstring_to_ucs4(styleCB->currentText());
430         bool const bibtotoc = bibtocCB->isChecked();
431
432         if (bibtotoc && !bibstyle.empty()) {
433                 // both bibtotoc and style
434                 params_["options"] = "bibtotoc," + bibstyle;
435         } else if (bibtotoc) {
436                 // bibtotoc and no style
437                 params_["options"] = from_ascii("bibtotoc");
438         } else {
439                 // only style. An empty one is valid, because some
440                 // documentclasses have an own \bibliographystyle{}
441                 // command!
442                 params_["options"] = bibstyle;
443         }
444
445         params_["biblatexopts"] = qstring_to_ucs4(biblatexOptsLE->text());
446
447         params_["btprint"] = qstring_to_ucs4(btPrintCO->itemData(btPrintCO->currentIndex()).toString());
448
449         params_["encoding"] = qstring_to_ucs4(bibEncodingCO->itemData(bibEncodingCO->currentIndex()).toString());
450
451         if (usingBiblatex())
452                 params_["file_encodings"] = getStringFromVector(getFileEncodings(), from_ascii("\t"));
453 }
454
455
456 QString GuiBibtex::browseBib(QString const & in_name) const
457 {
458         QString const label1 = qt_("D&ocuments");
459         QString const dir1 = toqstr(lyxrc.document_path);
460         QStringList const filter(qt_("BibTeX Databases (*.bib)"));
461         return browseRelToParent(in_name, bufferFilePath(),
462                 qt_("Select a BibTeX database to add"), filter, false, label1, dir1);
463 }
464
465
466 QString GuiBibtex::browseBst(QString const & in_name) const
467 {
468         QString const label1 = qt_("D&ocuments");
469         QString const dir1 = toqstr(lyxrc.document_path);
470         QStringList const filter(qt_("BibTeX Styles (*.bst)"));
471         return browseRelToParent(in_name, bufferFilePath(),
472                 qt_("Select a BibTeX style"), filter, false, label1, dir1);
473 }
474
475
476 QStringList GuiBibtex::bibStyles() const
477 {
478         QStringList sdata = texFileList("bstFiles.lst");
479         // test whether we have a valid list, otherwise run rescan
480         if (sdata.isEmpty()) {
481                 rescanBibStyles();
482                 sdata = texFileList("bstFiles.lst");
483         }
484         for (int i = 0; i != sdata.size(); ++i)
485                 sdata[i] = onlyFileName(sdata[i]);
486         // sort on filename only (no path)
487         sdata.sort();
488         return sdata;
489 }
490
491
492 QStringList GuiBibtex::bibFiles(bool const extension) const
493 {
494         QStringList sdata = texFileList("bibFiles.lst");
495         // test whether we have a valid list, otherwise run rescan
496         if (sdata.isEmpty()) {
497                 rescanBibStyles();
498                 sdata = texFileList("bibFiles.lst");
499         }
500         for (int i = 0; i != sdata.size(); ++i)
501                 sdata[i] = extension ? onlyFileName(sdata[i])
502                                      : changeExtension(onlyFileName(sdata[i]), "");
503         // sort on filename only (no path)
504         sdata.sort();
505         return sdata;
506 }
507
508
509 vector<docstring> GuiBibtex::getFileEncodings()
510 {
511         vector<docstring> res;
512         for (int i = 0; i != selected_model_.rowCount(); ++i) {
513                 QStandardItem const * key = selected_model_.item(i, 0);
514                 QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(i, 1)));
515                 QString fenc = cb ? cb->itemData(cb->currentIndex()).toString() : QString();
516                 if (key && !key->text().isEmpty() && !fenc.isEmpty() && fenc != "general")
517                         res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(fenc));
518         }
519         return res;
520 }
521
522
523 void GuiBibtex::setFileEncodings(vector<docstring> const m)
524 {
525         for (docstring const & s: m) {
526                 docstring key;
527                 QString enc = toqstr(split(s, key, ' '));
528                 QModelIndexList qmil =
529                                 selected_model_.match(selected_model_.index(0, 0),
530                                                      Qt::DisplayRole, toqstr(key), 1,
531                                                      Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
532                 if (!qmil.empty()) {
533                         QComboBox * cb = qobject_cast<QComboBox*>(selectedLV->indexWidget(selected_model_.index(qmil.front().row(), 1)));
534                         cb->setCurrentIndex(cb->findData(enc));
535                 }
536         }
537 }
538
539
540 void GuiBibtex::rescanBibStyles() const
541 {
542         if (usingBiblatex())
543                 rescanTexStyles("bib");
544         else
545                 rescanTexStyles("bst bib");
546 }
547
548
549 void GuiBibtex::findText(QString const & text)
550 {
551         QStringList const result = bibFiles(false).filter(text);
552         available_model_.setStringList(result);
553 }
554
555
556 void GuiBibtex::filterChanged(const QString & text)
557 {
558         if (!text.isEmpty()) {
559                 findText(filter_->text());
560                 return;
561         }
562         findText(filter_->text());
563         filter_->setFocus();
564 }
565
566
567 void GuiBibtex::filterPressed()
568 {
569         findText(filter_->text());
570 }
571
572
573 void GuiBibtex::resetFilter()
574 {
575         filter_->setText(QString());
576         findText(filter_->text());
577 }
578
579
580
581 bool GuiBibtex::usingBibtopic() const
582 {
583         return buffer().params().useBibtopic();
584 }
585
586
587 bool GuiBibtex::bibtotoc() const
588 {
589         return prefixIs(to_utf8(params_["options"]), "bibtotoc");
590 }
591
592
593 bool GuiBibtex::usingBiblatex() const
594 {
595         return buffer().masterBuffer()->params().useBiblatex();
596 }
597
598
599 QString GuiBibtex::styleFile() const
600 {
601         // the different bibtex packages have (and need) their
602         // own "plain" stylefiles
603         QString defaultstyle = toqstr(buffer().params().defaultBiblioStyle());
604
605         QString bst = toqstr(params_["options"]);
606         if (bibtotoc()){
607                 // bibstyle exists?
608                 int pos = bst.indexOf(',');
609                 if (pos != -1) {
610                         // FIXME: check
611                         // docstring bibtotoc = from_ascii("bibtotoc");
612                         // bst = split(bst, bibtotoc, ',');
613                         bst = bst.mid(pos + 1);
614                 } else {
615                         bst.clear();
616                 }
617         }
618
619         // propose default style file for new insets
620         // existing insets might have (legally) no bst files
621         // (if the class already provides a style)
622         if (bst.isEmpty() && params_["bibfiles"].empty())
623                 bst = defaultstyle;
624
625         return bst;
626 }
627
628
629 bool GuiBibtex::initialiseParams(std::string const & sdata)
630 {
631         InsetCommand::string2params(sdata, params_);
632         init();
633         return true;
634 }
635
636
637 void GuiBibtex::dispatchParams()
638 {
639         std::string const lfun = InsetCommand::params2string(params_);
640         dispatch(FuncRequest(getLfun(), lfun));
641 }
642
643
644 Dialog * createGuiBibtex(GuiView & lv) { return new GuiBibtex(lv); }
645
646
647 } // namespace frontend
648 } // namespace lyx
649
650 #include "moc_GuiBibtex.cpp"