3 * \file GuiCitation.cpp
4 * This file is part of LyX, the document processor.
5 * Licence details can be found in the file COPYING.
7 * \author Angus Leeming
8 * \author Kalle Dalheimer
9 * \author Abdelrazak Younes
10 * \author Richard Heck
12 * Full author contact details are available in file CREDITS.
17 #include "GuiCitation.h"
19 #include "GuiSelectionManager.h"
20 #include "qt_helpers.h"
23 #include "BufferView.h"
24 #include "BiblioInfo.h"
25 #include "BufferParams.h"
26 #include "FuncRequest.h"
28 #include "insets/InsetCommand.h"
30 #include "support/debug.h"
31 #include "support/docstring.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
35 #include <QCloseEvent>
45 #include "support/regex.h"
52 using namespace lyx::support;
58 // I am guessing that it would not hurt to make these private members.
59 static vector<string> citeCmds_;
60 static vector<CitationStyle> citeStyles_;
63 template<typename String>
64 static QStringList to_qstring_list(vector<String> const & v)
68 for (size_t i = 0; i != v.size(); ++i) {
71 qlist.append(lyx::toqstr(v[i]));
77 static vector<lyx::docstring> to_docstring_vector(QStringList const & qlist)
79 vector<lyx::docstring> v;
80 for (int i = 0; i != qlist.size(); ++i) {
81 if (qlist[i].isEmpty())
83 v.push_back(lyx::qstring_to_ucs4(qlist[i]));
89 GuiCitation::GuiCitation(GuiView & lv)
90 : DialogView(lv, "citation", qt_("Citation")),
91 params_(insetCode("citation"))
95 connect(citationStyleCO, SIGNAL(activated(int)),
96 this, SLOT(on_citationStyleCO_currentIndexChanged(int)));
97 connect(fulllistCB, SIGNAL(clicked()),
98 this, SLOT(changed()));
99 connect(forceuppercaseCB, SIGNAL(clicked()),
100 this, SLOT(changed()));
101 connect(textBeforeED, SIGNAL(textChanged(QString)),
102 this, SLOT(updateStyles()));
103 connect(textAfterED, SIGNAL(textChanged(QString)),
104 this, SLOT(updateStyles()));
105 connect(findLE, SIGNAL(returnPressed()),
106 this, SLOT(on_searchPB_clicked()));
107 connect(textBeforeED, SIGNAL(returnPressed()),
108 this, SLOT(on_okPB_clicked()));
109 connect(textAfterED, SIGNAL(returnPressed()),
110 this, SLOT(on_okPB_clicked()));
112 selectionManager = new GuiSelectionManager(availableLV, selectedLV,
113 addPB, deletePB, upPB, downPB, &available_model_, &selected_model_);
114 connect(selectionManager, SIGNAL(selectionChanged()),
115 this, SLOT(setCitedKeys()));
116 connect(selectionManager, SIGNAL(updateHook()),
117 this, SLOT(updateControls()));
118 connect(selectionManager, SIGNAL(okHook()),
119 this, SLOT(on_okPB_clicked()));
121 setFocusProxy(availableLV);
125 GuiCitation::~GuiCitation()
127 delete selectionManager;
131 void GuiCitation::closeEvent(QCloseEvent * e)
134 DialogView::closeEvent(e);
138 void GuiCitation::applyView()
140 int const choice = max(0, citationStyleCO->currentIndex());
142 bool const full = fulllistCB->isChecked();
143 bool const force = forceuppercaseCB->isChecked();
145 QString const before = textBeforeED->text();
146 QString const after = textAfterED->text();
148 applyParams(choice, full, force, before, after);
152 void GuiCitation::showEvent(QShowEvent * e)
155 availableLV->setFocus();
156 DialogView::showEvent(e);
160 void GuiCitation::on_citationTB_currentChanged(int i)
164 else if (citationStyleCO->isEnabled())
165 citationStyleCO->setFocus();
167 textAfterED->setFocus();
171 void GuiCitation::on_okPB_clicked()
179 void GuiCitation::on_cancelPB_clicked()
186 void GuiCitation::on_applyPB_clicked()
192 void GuiCitation::on_restorePB_clicked()
198 void GuiCitation::updateControls()
200 BiblioInfo const & bi = bibInfo();
205 // The main point of separating this out is that the fill*() methods
206 // called in update() do not need to be called for INTERNAL updates,
207 // such as when addPB is pressed, as the list of fields, entries, etc,
208 // will not have changed.
209 void GuiCitation::updateControls(BiblioInfo const & bi)
211 QModelIndex idx = selectionManager->getSelectedIndex();
213 selectionManager->update();
217 void GuiCitation::updateFormatting(CitationStyle currentStyle)
219 bool const force = currentStyle.forceUpperCase;
220 bool const full = currentStyle.fullAuthorList &&
221 documentBuffer().params().fullAuthorList();
222 bool const textbefore = currentStyle.textBefore;
223 bool const textafter = currentStyle.textAfter;
225 bool const haveSelection =
226 selectedLV->model()->rowCount() > 0;
228 forceuppercaseCB->setEnabled(force && haveSelection);
229 fulllistCB->setEnabled(full && haveSelection);
230 textBeforeED->setEnabled(textbefore && haveSelection);
231 textBeforeLA->setEnabled(textbefore && haveSelection);
232 textAfterED->setEnabled(textafter && haveSelection);
233 textAfterLA->setEnabled(textafter && haveSelection);
234 citationStyleCO->setEnabled(haveSelection);
235 citationStyleLA->setEnabled(haveSelection);
239 // Update the styles for the style combo, citationStyleCO, and mark the
240 // settings as changed. Called upon changing the cited keys (including
241 // merely reordering the keys) or editing the text before/after fields.
242 void GuiCitation::updateStyles()
244 BiblioInfo const & bi = bibInfo();
250 // Update the styles for the style combo, citationStyleCO.
251 void GuiCitation::updateStyles(BiblioInfo const & bi)
253 QStringList selected_keys = selected_model_.stringList();
254 int curr = selectedLV->model()->rowCount() - 1;
256 if (curr < 0 || selected_keys.empty()) {
257 citationStyleCO->clear();
258 citationStyleCO->setEnabled(false);
259 citationStyleLA->setEnabled(false);
263 static const size_t max_length = 80;
264 QStringList sty = citationStyles(bi, max_length);
268 citationStyleCO->setEnabled(false);
269 citationStyleLA->setEnabled(false);
270 citationStyleCO->clear();
274 citationStyleCO->blockSignals(true);
277 int const oldIndex = citationStyleCO->currentIndex();
278 citationStyleCO->clear();
279 citationStyleCO->insertItems(0, sty);
280 citationStyleCO->setEnabled(true);
281 citationStyleLA->setEnabled(true);
283 if (oldIndex != -1 && oldIndex < citationStyleCO->count())
284 citationStyleCO->setCurrentIndex(oldIndex);
286 citationStyleCO->blockSignals(false);
290 void GuiCitation::fillFields(BiblioInfo const & bi)
292 fieldsCO->blockSignals(true);
293 int const oldIndex = fieldsCO->currentIndex();
295 QStringList const fields = to_qstring_list(bi.getFields());
296 fieldsCO->insertItem(0, qt_("All fields"));
297 fieldsCO->insertItem(1, qt_("Keys"));
298 fieldsCO->insertItems(2, fields);
299 if (oldIndex != -1 && oldIndex < fieldsCO->count())
300 fieldsCO->setCurrentIndex(oldIndex);
301 fieldsCO->blockSignals(false);
305 void GuiCitation::fillEntries(BiblioInfo const & bi)
307 entriesCO->blockSignals(true);
308 int const oldIndex = entriesCO->currentIndex();
310 QStringList const entries = to_qstring_list(bi.getEntries());
311 entriesCO->insertItem(0, qt_("All entry types"));
312 entriesCO->insertItems(1, entries);
313 if (oldIndex != -1 && oldIndex < entriesCO->count())
314 entriesCO->setCurrentIndex(oldIndex);
315 entriesCO->blockSignals(false);
319 bool GuiCitation::isSelected(QModelIndex const & idx)
321 QString const str = idx.data().toString();
322 return selected_model_.stringList().contains(str);
326 void GuiCitation::setButtons()
328 int const srows = selectedLV->model()->rowCount();
329 applyPB->setEnabled(srows > 0);
330 okPB->setEnabled(srows > 0);
334 void GuiCitation::updateInfo(BiblioInfo const & bi, QModelIndex const & idx)
336 if (!idx.isValid() || bi.empty()) {
337 infoML->document()->clear();
341 QString const keytxt = toqstr(
342 bi.getInfo(qstring_to_ucs4(idx.data().toString()), documentBuffer(), true));
343 infoML->document()->setHtml(keytxt);
347 void GuiCitation::findText(QString const & text, bool reset)
349 //"All Fields" and "Keys" are the first two
350 int index = fieldsCO->currentIndex() - 2;
351 BiblioInfo const & bi = bibInfo();
352 vector<docstring> const & fields = bi.getFields();
355 if (index <= -1 || index >= int(fields.size()))
356 //either "All Fields" or "Keys" or an invalid value
357 field = from_ascii("");
359 field = fields[index];
362 bool const onlyKeys = index == -1;
364 //"All Entry Types" is first.
365 index = entriesCO->currentIndex() - 1;
366 vector<docstring> const & entries = bi.getEntries();
367 docstring entry_type;
368 if (index < 0 || index >= int(entries.size()))
369 entry_type = from_ascii("");
371 entry_type = entries[index];
373 bool const case_sentitive = caseCB->checkState();
374 bool const reg_exp = regexCB->checkState();
375 findKey(bi, text, onlyKeys, field, entry_type,
376 case_sentitive, reg_exp, reset);
378 //It'd be nice to save and restore the current selection in
379 //availableLV. Currently, we get an automatic reset, since the
386 void GuiCitation::on_fieldsCO_currentIndexChanged(int /*index*/)
388 findText(findLE->text(), true);
392 void GuiCitation::on_entriesCO_currentIndexChanged(int /*index*/)
394 findText(findLE->text(), true);
398 void GuiCitation::on_citationStyleCO_currentIndexChanged(int index)
400 if (index >= 0 && index < citationStyleCO->count()) {
401 vector<CitationStyle> const & styles = citeStyles_;
402 updateFormatting(styles[index]);
408 void GuiCitation::on_findLE_textChanged(const QString & text)
410 bool const searchAsWeGo = (asTypeCB->checkState() == Qt::Checked);
411 searchPB->setDisabled(text.isEmpty() || searchAsWeGo);
412 if (!text.isEmpty()) {
414 findText(findLE->text());
417 findText(findLE->text());
421 void GuiCitation::on_searchPB_clicked()
423 findText(findLE->text(), true);
427 void GuiCitation::on_caseCB_stateChanged(int)
429 findText(findLE->text());
433 void GuiCitation::on_regexCB_stateChanged(int)
435 findText(findLE->text());
439 void GuiCitation::on_asTypeCB_stateChanged(int)
441 bool const searchAsWeGo = (asTypeCB->checkState() == Qt::Checked);
442 searchPB->setDisabled(findLE->text().isEmpty() || searchAsWeGo);
444 findText(findLE->text(), true);
448 void GuiCitation::changed()
454 void GuiCitation::applyParams(int const choice, bool full, bool force,
455 QString before, QString after)
457 if (cited_keys_.isEmpty())
460 vector<CitationStyle> const & styles = citeStyles_;
462 CitationStyle cs = styles[choice];
469 cs.forceUpperCase &= force;
470 cs.fullAuthorList &= full;
471 string const command = citationStyleToString(cs);
473 params_.setCmdName(command);
474 params_["key"] = qstring_to_ucs4(cited_keys_.join(","));
475 params_["before"] = qstring_to_ucs4(before);
476 params_["after"] = qstring_to_ucs4(after);
481 void GuiCitation::clearSelection()
484 selected_model_.setStringList(cited_keys_);
488 void GuiCitation::init()
490 // Make the list of all available bibliography keys
491 BiblioInfo const & bi = bibInfo();
492 all_keys_ = to_qstring_list(bi.getKeys());
493 available_model_.setStringList(all_keys_);
495 // Ditto for the keys cited in this inset
496 QString str = toqstr(params_["key"]);
500 cited_keys_ = str.split(",");
501 selected_model_.setStringList(cited_keys_);
503 // Initialize the drop downs
507 // Initialize the citation formatting
508 string const & cmd = params_.getCmdName();
509 CitationStyle const cs = citationStyleFromString(cmd);
510 forceuppercaseCB->setChecked(cs.forceUpperCase);
511 fulllistCB->setChecked(cs.fullAuthorList &&
512 documentBuffer().params().fullAuthorList());
513 textBeforeED->setText(toqstr(params_["before"]));
514 textAfterED->setText(toqstr(params_["after"]));
516 // Update the interface
519 if (selected_model_.rowCount()) {
520 selectedLV->blockSignals(true);
521 selectedLV->setFocus();
522 QModelIndex idx = selected_model_.index(0, 0);
523 selectedLV->selectionModel()->select(idx,
524 QItemSelectionModel::ClearAndSelect);
525 selectedLV->blockSignals(false);
527 // Find the citation style
528 vector<string> const & cmds = citeCmds_;
529 vector<string>::const_iterator cit =
530 std::find(cmds.begin(), cmds.end(), cs.cmd);
532 if (cit != cmds.end())
533 i = int(cit - cmds.begin());
535 // Set the style combo appropriately
536 citationStyleCO->blockSignals(true);
537 citationStyleCO->setCurrentIndex(i);
538 citationStyleCO->blockSignals(false);
539 updateFormatting(citeStyles_[i]);
541 availableLV->setFocus();
543 applyPB->setEnabled(false);
544 okPB->setEnabled(false);
548 void GuiCitation::findKey(BiblioInfo const & bi,
549 QString const & str, bool only_keys,
550 docstring field, docstring entry_type,
551 bool case_sensitive, bool reg_exp, bool reset)
554 // Used for optimisation: store last searched string.
555 static QString last_searched_string;
556 // Used to disable the above optimisation.
557 static bool last_case_sensitive;
558 static bool last_reg_exp;
559 // Reset last_searched_string in case of changed option.
560 if (last_case_sensitive != case_sensitive
561 || last_reg_exp != reg_exp) {
562 LYXERR(Debug::GUI, "GuiCitation::findKey: optimisation disabled!");
563 last_searched_string.clear();
565 // save option for next search.
566 last_case_sensitive = case_sensitive;
567 last_reg_exp = reg_exp;
569 Qt::CaseSensitivity qtcase = case_sensitive ?
570 Qt::CaseSensitive: Qt::CaseInsensitive;
572 // If new string (str) contains the last searched one...
574 !last_searched_string.isEmpty() &&
576 str.contains(last_searched_string, qtcase))
577 // ... then only search within already found list.
578 keys = available_model_.stringList();
580 // ... else search all keys.
582 // save searched string for next search.
583 last_searched_string = str;
587 // First, filter by entry_type, which will be faster than
588 // what follows, so we may get to do that on less.
589 vector<docstring> keyVector = to_docstring_vector(keys);
590 filterByEntryType(bi, keyVector, entry_type);
593 result = to_qstring_list(keyVector);
595 result = to_qstring_list(searchKeys(bi, keyVector, only_keys,
596 qstring_to_ucs4(str), field, case_sensitive, reg_exp));
598 available_model_.setStringList(result);
602 QStringList GuiCitation::citationStyles(BiblioInfo const & bi, size_t max_size)
604 docstring const before = qstring_to_ucs4(textBeforeED->text());
605 docstring const after = qstring_to_ucs4(textAfterED->text());
606 vector<docstring> const keys = to_docstring_vector(cited_keys_);
607 vector<CitationStyle> styles = citeStyles_;
608 // FIXME: pass a dictionary instead of individual before, after, dialog, etc.
609 vector<docstring> ret = bi.getCiteStrings(keys, styles, documentBuffer(),
610 before, after, from_utf8("dialog"), max_size);
611 return to_qstring_list(ret);
615 void GuiCitation::setCitedKeys()
617 cited_keys_ = selected_model_.stringList();
622 bool GuiCitation::initialiseParams(string const & data)
624 InsetCommand::string2params(data, params_);
625 citeCmds_ = documentBuffer().params().citeCommands();
626 citeStyles_ = documentBuffer().params().citeStyles();
632 void GuiCitation::clearParams()
638 void GuiCitation::filterByEntryType(BiblioInfo const & bi,
639 vector<docstring> & keyVector, docstring entry_type)
641 if (entry_type.empty())
644 vector<docstring>::iterator it = keyVector.begin();
645 vector<docstring>::iterator end = keyVector.end();
647 vector<docstring> result;
648 for (; it != end; ++it) {
649 docstring const key = *it;
650 BiblioInfo::const_iterator cit = bi.find(key);
653 if (cit->second.entryType() == entry_type)
654 result.push_back(key);
660 // Escape special chars.
661 // All characters are literals except: '.|*?+(){}[]^$\'
662 // These characters are literals when preceded by a "\", which is done here
663 // @todo: This function should be moved to support, and then the test in tests
664 // should be moved there as well.
665 static docstring escape_special_chars(docstring const & expr)
667 // Search for all chars '.|*?+(){}[^$]\'
668 // Note that '[', ']', and '\' must be escaped.
669 static const lyx::regex reg("[.|*?+(){}^$\\[\\]\\\\]");
671 // $& is an ECMAScript format expression that expands to all
672 // of the current match
673 #if defined(LYX_USE_CXX11) && defined(LYX_USE_STD_REGEX)
674 // To prefix a matched expression with a single literal backslash, we
675 // need to escape it for the C++ compiler and use:
677 return from_utf8(lyx::regex_replace(to_utf8(expr), reg, string("\\$&")));
679 // A backslash in the format string starts an escape sequence in boost.
680 // Thus, to prefix a matched expression with a single literal backslash,
681 // we need to give two backslashes to the regex engine, and escape both
682 // for the C++ compiler and use:
684 return from_utf8(lyx::regex_replace(to_utf8(expr), reg, string("\\\\$&")));
689 vector<docstring> GuiCitation::searchKeys(BiblioInfo const & bi,
690 vector<docstring> const & keys_to_search, bool only_keys,
691 docstring const & search_expression, docstring field,
692 bool case_sensitive, bool regex)
694 vector<docstring> foundKeys;
696 docstring expr = trim(search_expression);
701 // We must escape special chars in the search_expr so that
702 // it is treated as a simple string by lyx::regex.
703 expr = escape_special_chars(expr);
707 reg_exp.assign(to_utf8(expr), case_sensitive ?
708 lyx::regex_constants::ECMAScript : lyx::regex_constants::icase);
709 } catch (lyx::regex_error const & e) {
710 // lyx::regex throws an exception if the regular expression is not
712 LYXERR(Debug::GUI, e.what());
713 return vector<docstring>();
716 vector<docstring>::const_iterator it = keys_to_search.begin();
717 vector<docstring>::const_iterator end = keys_to_search.end();
718 for (; it != end; ++it ) {
719 BiblioInfo::const_iterator info = bi.find(*it);
720 if (info == bi.end())
723 BibTeXInfo const & kvm = info->second;
727 else if (field.empty())
728 data = to_utf8(*it) + ' ' + to_utf8(kvm.allData());
730 data = to_utf8(kvm[field]);
736 if (lyx::regex_search(data, reg_exp))
737 foundKeys.push_back(*it);
739 catch (lyx::regex_error const & e) {
740 LYXERR(Debug::GUI, e.what());
741 return vector<docstring>();
748 void GuiCitation::dispatchParams()
750 std::string const lfun = InsetCommand::params2string(params_);
751 dispatch(FuncRequest(getLfun(), lfun));
755 BiblioInfo const & GuiCitation::bibInfo() const
757 Buffer const & buf = documentBuffer();
758 buf.reloadBibInfoCache();
759 return buf.masterBibInfo();
763 void GuiCitation::saveSession() const
765 Dialog::saveSession();
768 sessionKey() + "/regex", regexCB->isChecked());
770 sessionKey() + "/casesensitive", caseCB->isChecked());
772 sessionKey() + "/autofind", asTypeCB->isChecked());
776 void GuiCitation::restoreSession()
778 Dialog::restoreSession();
781 settings.value(sessionKey() + "/regex").toBool());
783 settings.value(sessionKey() + "/casesensitive").toBool());
784 asTypeCB->setChecked(
785 settings.value(sessionKey() + "/autofind").toBool());
789 Dialog * createGuiCitation(GuiView & lv) { return new GuiCitation(lv); }
792 } // namespace frontend
795 #include "moc_GuiCitation.cpp"