]> git.lyx.org Git - features.git/blob - src/frontends/qt/GuiBibtex.cpp
#10571 improved handling of WM's signal when switching from or to full-screen window
[features.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
31 #include "frontends/alert.h"
32
33 #include "insets/InsetBibtex.h"
34
35 #include "support/debug.h"
36 #include "support/ExceptionMessage.h"
37 #include "support/FileName.h"
38 #include "support/filetools.h" // changeExtension
39 #include "support/gettext.h"
40 #include "support/lstrings.h"
41
42 #include <QDialogButtonBox>
43 #include <QPushButton>
44 #include <QListWidget>
45 #include <QCheckBox>
46 #include <QLineEdit>
47
48 using namespace std;
49 using namespace lyx::support;
50
51 namespace lyx {
52 namespace frontend {
53
54
55 GuiBibtex::GuiBibtex(GuiView & lv)
56         : GuiDialog(lv, "bibtex", qt_("BibTeX Bibliography")),
57           params_(insetCode("bibtex"))
58 {
59         setupUi(this);
60
61         QDialog::setModal(true);
62         setWindowModality(Qt::WindowModal);
63
64         // The filter bar
65         filter_ = new FancyLineEdit(this);
66         filter_->setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
67         filter_->setButtonVisible(FancyLineEdit::Right, true);
68         filter_->setButtonToolTip(FancyLineEdit::Right, qt_("Clear text"));
69         filter_->setAutoHideButton(FancyLineEdit::Right, true);
70         filter_->setPlaceholderText(qt_("All avail. databases"));
71
72         filterBarL->addWidget(filter_, 0);
73         findKeysLA->setBuddy(filter_);
74
75         connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
76                 this, SLOT(slotButtonBox(QAbstractButton *)));
77         connect(stylePB, SIGNAL(clicked()),
78                 this, SLOT(browseBstPressed()));
79         connect(styleCB, SIGNAL(editTextChanged(QString)),
80                 this, SLOT(change_adaptor()));
81         connect(bibtocCB, SIGNAL(clicked()),
82                 this, SLOT(change_adaptor()));
83         connect(btPrintCO, SIGNAL(activated(int)),
84                 this, SLOT(change_adaptor()));
85         connect(rescanPB, SIGNAL(clicked()),
86                 this, SLOT(rescanClicked()));
87         connect(biblatexOptsLE, SIGNAL(textChanged(QString)),
88                 this, SLOT(change_adaptor()));
89         connect(bibEncodingCO, SIGNAL(activated(int)),
90                 this, SLOT(change_adaptor()));
91         connect(browseBibPB, SIGNAL(clicked()),
92                 this, SLOT(browseBibPressed()));
93
94         selected_model_.insertColumns(0, 1);
95         selectionManager = new GuiSelectionManager(this, availableLV, selectedLV,
96                         addBibPB, deletePB, upPB, downPB, &available_model_, &selected_model_);
97         connect(selectionManager, SIGNAL(selectionChanged()),
98                 this, SLOT(databaseChanged()));
99         connect(selectionManager, SIGNAL(updateHook()),
100                 this, SLOT(selUpdated()));
101         connect(selectionManager, SIGNAL(okHook()),
102                 this, SLOT(on_buttonBox_accepted()));
103
104         connect(filter_, SIGNAL(rightButtonClicked()),
105                 this, SLOT(resetFilter()));
106         connect(filter_, SIGNAL(textEdited(QString)),
107                 this, SLOT(filterChanged(QString)));
108         connect(filter_, SIGNAL(returnPressed()),
109                 this, SLOT(filterPressed()));
110 #if (QT_VERSION < 0x050000)
111         connect(filter_, SIGNAL(downPressed()),
112                 availableLV, SLOT(setFocus()));
113 #else
114         connect(filter_, &FancyLineEdit::downPressed,
115                 availableLV, [=](){ focusAndHighlight(availableLV); });
116 #endif
117
118         availableLV->setToolTip(formatToolTip(qt_("This list consists of all databases that are indexed by LaTeX and thus are found without a file path. "
119                                     "This is usually everything in the bib/ subdirectory of LaTeX's texmf tree. "
120                                     "If you want to reuse your own database, this is the place you should store it.")));
121
122         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
123         bc().setOK(buttonBox->button(QDialogButtonBox::Ok));
124         bc().setApply(buttonBox->button(QDialogButtonBox::Apply));
125         bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
126         bc().addReadOnly(stylePB);
127         bc().addReadOnly(styleCB);
128         bc().addReadOnly(bibtocCB);
129         bc().addReadOnly(bibEncodingCO);
130
131 #if (QT_VERSION < 0x050000)
132         selectedLV->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
133 #else
134         selectedLV->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
135 #endif
136
137         // Always put the default encoding in the first position.
138         bibEncodingCO->addItem(qt_("Document Encoding"), "default");
139         for (auto const & encvar : encodings) {
140                 if (!encvar.unsafe() && !encvar.guiName().empty())
141                         encodings_.insert(qt_(encvar.guiName()), toqstr(encvar.name()));
142         }
143         QMap<QString, QString>::const_iterator it = encodings_.constBegin();
144         while (it != encodings_.constEnd()) {
145                 bibEncodingCO->addItem(it.key(), it.value());
146                 ++it;
147         }
148
149         setFocusProxy(filter_);
150 }
151
152
153 void GuiBibtex::init()
154 {
155         all_bibs_ = bibFiles(false);
156         available_model_.setStringList(all_bibs_);
157
158         QString bibs = toqstr(params_["bibfiles"]);
159         if (bibs.isEmpty())
160                 selected_bibs_.clear();
161         else
162                 selected_bibs_ = bibs.split(",");
163         setSelectedBibs(selected_bibs_);
164
165         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
166         buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
167         selectionManager->update();
168 }
169
170
171 void GuiBibtex::change_adaptor()
172 {
173         setButtons();
174         changed();
175 }
176
177
178 void GuiBibtex::setButtons()
179 {
180         int const srows = selectedLV->model()->rowCount();
181         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(srows > 0);
182         buttonBox->button(QDialogButtonBox::Ok)->setEnabled(srows > 0);
183 }
184
185
186 void GuiBibtex::selUpdated()
187 {
188         selectionManager->update();
189         editPB->setEnabled(deletePB->isEnabled());
190         changed();
191 }
192
193
194 void GuiBibtex::on_buttonBox_accepted()
195 {
196         applyView();
197         clearSelection();
198         hide();
199 }
200
201
202 void GuiBibtex::browseBstPressed()
203 {
204         QString const file = browseBst(QString());
205
206         if (file.isEmpty())
207                 return;
208
209         QString const filen = changeExtension(file, "");
210         bool present = false;
211         int pres = 0;
212
213         for (int i = 0; i != styleCB->count(); ++i) {
214                 if (styleCB->itemText(i) == filen) {
215                         present = true;
216                         pres = i;
217                 }
218         }
219
220         if (!present)
221                 styleCB->insertItem(0, filen);
222
223         styleCB->setCurrentIndex(pres);
224         changed();
225 }
226
227
228 void GuiBibtex::browseBibPressed()
229 {
230         QString const file = browseBib(QString()).trimmed();
231
232         if (file.isEmpty())
233                 return;
234
235         QString const f = changeExtension(file, "");
236
237         if (!selected_bibs_.contains(f)) {
238                 selected_bibs_.append(f);
239                 setSelectedBibs(selected_bibs_);
240                 changed();
241         }
242 }
243
244
245 void GuiBibtex::on_editPB_clicked()
246 {
247         QModelIndexList selIdx =
248                 selectedLV->selectionModel()->selectedIndexes();
249         if (selIdx.isEmpty())
250                 return;
251         QModelIndex idx = selIdx.first();
252         QString sel = idx.data().toString();
253         FuncRequest fr(LFUN_INSET_EDIT, fromqstr(sel));
254         dispatch(fr);
255 }
256
257
258 void GuiBibtex::rescanClicked()
259 {
260         rescanBibStyles();
261         updateContents();
262 }
263
264
265 void GuiBibtex::clearSelection()
266 {
267         selected_bibs_.clear();
268         setSelectedBibs(selected_bibs_);
269 }
270
271
272 void GuiBibtex::setSelectedBibs(QStringList const sl)
273 {
274         selected_model_.clear();
275         QStringList headers;
276         headers << qt_("Database")
277                 << qt_("File Encoding");
278         selected_model_.setHorizontalHeaderLabels(headers);
279         bool const moreencs = usingBiblatex() && sl.count() > 1;
280         selectedLV->setColumnHidden(1, !moreencs);
281         selectedLV->verticalHeader()->setVisible(false);
282         selectedLV->horizontalHeader()->setVisible(moreencs);
283         if (moreencs) {
284                 bibEncodingLA->setText(qt_("General E&ncoding:"));
285                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
286                                               "encoding than the LyX document, specify it here. "
287                                               "If indivivual databases have different encodings, "
288                                               "you can set it in the list above."));
289         } else {
290                 bibEncodingLA->setText(qt_("E&ncoding:"));
291                 bibEncodingCO->setToolTip(qt_("If your bibliography databases use a different "
292                                               "encoding than the LyX document, specify it here"));
293         }
294         QStringList::const_iterator it  = sl.begin();
295         QStringList::const_iterator end = sl.end();
296         for (int i = 0; it != end; ++it, ++i) {
297                 QStandardItem * si = new QStandardItem();
298                 si->setData(*it);
299                 si->setText(*it);
300                 si->setToolTip(*it);
301                 si->setEditable(false);
302                 selected_model_.insertRow(i, si);
303                 QComboBox * cb = new QComboBox;
304                 cb->addItem(qt_("General Encoding"), "general");
305                 cb->addItem(qt_("Document Encoding"), "auto");
306                 QMap<QString, QString>::const_iterator it = encodings_.constBegin();
307                 while (it != encodings_.constEnd()) {
308                         cb->addItem(it.key(), it.value());
309                         ++it;
310                 }
311                 cb->setToolTip(qt_("If this bibliography database uses a different "
312                                    "encoding than specified below, set it here"));
313                 selectedLV->setIndexWidget(selected_model_.index(i, 1), cb);
314         }
315         editPB->setEnabled(deletePB->isEnabled());
316 }
317
318
319 QStringList GuiBibtex::selectedBibs()
320 {
321         QStringList res;
322         for (int i = 0; i != selected_model_.rowCount(); ++i) {
323                 QStandardItem const * item = selected_model_.item(i);
324                 if (item)
325                         res.append(item->text());
326         }
327         return res;
328 }
329
330
331 void GuiBibtex::databaseChanged()
332 {
333         selected_bibs_ = selectedBibs();
334         setSelectedBibs(selected_bibs_);
335 }
336
337
338 void GuiBibtex::updateContents()
339 {
340         bool const bibtopic = usingBibtopic();
341         bool const biblatex = usingBiblatex();
342
343         if (biblatex)
344                 setTitle(qt_("Biblatex Bibliography"));
345         else
346                 setTitle(qt_("BibTeX Bibliography"));
347
348         QString const bibstyle = styleFile();
349
350         bool const hasbibintoc = buffer().params().documentClass().bibInToc()
351                         && !biblatex;
352
353         bibtocCB->setChecked((bibtotoc() && !bibtopic) || hasbibintoc);
354         bibtocCB->setEnabled(!bibtopic && !hasbibintoc);
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"