1 
2 /**
3  * \file GuiCitation.cpp
4  * This file is part of LyX, the document processor.
5  * Licence details can be found in the file COPYING.
6  *
7  * \author Angus Leeming
8  * \author Kalle Dalheimer
9  * \author Abdelrazak Younes
10  * \author Richard Heck
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14 
15 #include <config.h>
16 
17 #include "GuiCitation.h"
18 
19 #include "GuiApplication.h"
20 #include "GuiSelectionManager.h"
21 #include "qt_helpers.h"
22 
23 #include "Buffer.h"
24 #include "BufferView.h"
25 #include "BiblioInfo.h"
26 #include "BufferParams.h"
27 #include "TextClass.h"
28 #include "FuncRequest.h"
29 
30 #include "insets/InsetCitation.h"
31 #include "insets/InsetCommand.h"
32 
33 #include "support/debug.h"
34 #include "support/docstring.h"
35 #include "support/gettext.h"
36 #include "support/lstrings.h"
37 
38 #include <QCloseEvent>
39 #include <QMenu>
40 #include <QSettings>
41 #include <QShowEvent>
42 #include <QStandardItemModel>
43 #include <QVariant>
44 
45 #include <vector>
46 #include <string>
47 
48 #undef KeyPress
49 
50 #include "support/regex.h"
51 
52 #include <algorithm>
53 #include <string>
54 #include <vector>
55 
56 using namespace std;
57 using namespace lyx::support;
58 
59 namespace lyx {
60 namespace frontend {
61 
62 // FIXME THREAD
63 // I am guessing that it would not hurt to make these private members.
64 static vector<string> citeCmds_;
65 static vector<CitationStyle> citeStyles_;
66 
67 
68 template<typename String>
to_qstring_list(vector<String> const & v)69 static QStringList to_qstring_list(vector<String> const & v)
70 {
71 	QStringList qlist;
72 
73 	for (size_t i = 0; i != v.size(); ++i) {
74 		if (v[i].empty())
75 			continue;
76 		qlist.append(lyx::toqstr(v[i]));
77 	}
78 	return qlist;
79 }
80 
81 
to_docstring_vector(QStringList const & qlist)82 static vector<lyx::docstring> to_docstring_vector(QStringList const & qlist)
83 {
84 	vector<lyx::docstring> v;
85 	for (int i = 0; i != qlist.size(); ++i) {
86 		if (qlist[i].isEmpty())
87 			continue;
88 		v.push_back(lyx::qstring_to_ucs4(qlist[i]));
89 	}
90 	return v;
91 }
92 
93 
GuiCitation(GuiView & lv)94 GuiCitation::GuiCitation(GuiView & lv)
95 	: DialogView(lv, "citation", qt_("Citation")),
96 	  style_(QString()), params_(insetCode("citation"))
97 {
98 	setupUi(this);
99 
100 	// The filter bar
101 	filter_ = new FancyLineEdit(this);
102 	filter_->setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
103 	filter_->setButtonVisible(FancyLineEdit::Right, true);
104 	filter_->setButtonToolTip(FancyLineEdit::Right, qt_("Clear text"));
105 	filter_->setAutoHideButton(FancyLineEdit::Right, true);
106 	filter_->setPlaceholderText(qt_("All avail. citations"));
107 
108 	filterBarL->addWidget(filter_, 0);
109 	findKeysLA->setBuddy(filter_);
110 
111 	// Add search options as button menu
112 	regexp_ = new QAction(qt_("Regular e&xpression"), this);
113 	regexp_->setCheckable(true);
114 	casesense_ = new QAction(qt_("Case se&nsitive"), this);
115 	casesense_->setCheckable(true);
116 	instant_ = new QAction(qt_("Search as you &type"), this);
117 	instant_->setCheckable(true);
118 	instant_->setChecked(true);
119 
120 	QMenu * searchOpts = new QMenu(this);
121 	searchOpts->addAction(regexp_);
122 	searchOpts->addAction(casesense_);
123 	searchOpts->addAction(instant_);
124 	searchOptionsPB->setMenu(searchOpts);
125 
126 	connect(citationStyleCO, SIGNAL(activated(int)),
127 		this, SLOT(on_citationStyleCO_currentIndexChanged(int)));
128 	connect(starredCB, SIGNAL(clicked()),
129 		this, SLOT(updateStyles()));
130 	connect(literalCB, SIGNAL(clicked()),
131 		this, SLOT(changed()));
132 	connect(forceuppercaseCB, SIGNAL(clicked()),
133 		this, SLOT(updateStyles()));
134 	connect(textBeforeED, SIGNAL(textChanged(QString)),
135 		this, SLOT(updateStyles()));
136 	connect(textAfterED, SIGNAL(textChanged(QString)),
137 		this, SLOT(updateStyles()));
138 	connect(textBeforeED, SIGNAL(returnPressed()),
139 		this, SLOT(on_okPB_clicked()));
140 	connect(textAfterED, SIGNAL(returnPressed()),
141 		this, SLOT(on_okPB_clicked()));
142 
143 	selectionManager = new GuiSelectionManager(this, availableLV, selectedLV,
144 			addPB, deletePB, upPB, downPB, &available_model_, &selected_model_, 1);
145 	connect(selectionManager, SIGNAL(selectionChanged()),
146 		this, SLOT(setCitedKeys()));
147 	connect(selectionManager, SIGNAL(updateHook()),
148 		this, SLOT(updateControls()));
149 	connect(selectionManager, SIGNAL(okHook()),
150 		this, SLOT(on_okPB_clicked()));
151 
152 	connect(filter_, SIGNAL(rightButtonClicked()),
153 		this, SLOT(resetFilter()));
154 	connect(filter_, SIGNAL(textEdited(QString)),
155 		this, SLOT(filterChanged(QString)));
156 	connect(filter_, SIGNAL(returnPressed()),
157 		this, SLOT(filterPressed()));
158 #if (QT_VERSION < 0x050000)
159 	connect(filter_, SIGNAL(downPressed()),
160 	        availableLV, SLOT(setFocus()));
161 #else
162 	connect(filter_, &FancyLineEdit::downPressed,
163 	        availableLV, [=](){ focusAndHighlight(availableLV); });
164 #endif
165 	connect(regexp_, SIGNAL(triggered()),
166 		this, SLOT(regexChanged()));
167 	connect(casesense_, SIGNAL(triggered()),
168 		this, SLOT(caseChanged()));
169 	connect(instant_, SIGNAL(triggered(bool)),
170 		this, SLOT(instantChanged(bool)));
171 
172 #if (QT_VERSION < 0x050000)
173 	selectedLV->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
174 #else
175 	selectedLV->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
176 #endif
177 
178 	selectedLV->setToolTip(qt_("Ordered list of all cited references.\n"
179 				   "You can reorder, add and remove references with the buttons on the left."));
180 	setFocusProxy(filter_);
181 }
182 
183 
closeEvent(QCloseEvent * e)184 void GuiCitation::closeEvent(QCloseEvent * e)
185 {
186 	clearSelection();
187 	DialogView::closeEvent(e);
188 }
189 
190 
applyView()191 void GuiCitation::applyView()
192 {
193 	int const choice = max(0, citationStyleCO->currentIndex());
194 	style_ = citationStyleCO->itemData(citationStyleCO->currentIndex()).toString();
195 	bool const full  = starredCB->isChecked();
196 	bool const force = forceuppercaseCB->isChecked();
197 
198 	QString const before = textBeforeED->text();
199 	QString const after = textAfterED->text();
200 
201 	applyParams(choice, full, force, before, after);
202 }
203 
204 
showEvent(QShowEvent * e)205 void GuiCitation::showEvent(QShowEvent * e)
206 {
207 	if (!filter_->text().isEmpty())
208 		filterPressed();
209 	availableLV->setFocus();
210 	DialogView::showEvent(e);
211 }
212 
213 
on_okPB_clicked()214 void GuiCitation::on_okPB_clicked()
215 {
216 	applyView();
217 	clearSelection();
218 	hide();
219 }
220 
221 
on_cancelPB_clicked()222 void GuiCitation::on_cancelPB_clicked()
223 {
224 	clearSelection();
225 	hide();
226 }
227 
228 
on_applyPB_clicked()229 void GuiCitation::on_applyPB_clicked()
230 {
231 	applyView();
232 }
233 
234 
on_restorePB_clicked()235 void GuiCitation::on_restorePB_clicked()
236 {
237 	init();
238 	updateFilterHint();
239 	filterPressed();
240 }
241 
242 
on_literalCB_clicked()243 void GuiCitation::on_literalCB_clicked()
244 {
245 	InsetCitation::last_literal = literalCB->isChecked();
246 	changed();
247 }
248 
249 
updateControls()250 void GuiCitation::updateControls()
251 {
252 	BiblioInfo const & bi = bibInfo();
253 	updateControls(bi);
254 }
255 
256 
257 // The main point of separating this out is that the fill*() methods
258 // called in update() do not need to be called for INTERNAL updates,
259 // such as when addPB is pressed, as the list of fields, entries, etc,
260 // will not have changed.
updateControls(BiblioInfo const & bi)261 void GuiCitation::updateControls(BiblioInfo const & bi)
262 {
263 	QModelIndex idx = selectionManager->getSelectedIndex(1);
264 	updateInfo(bi, idx);
265 	int i = citationStyleCO->currentIndex();
266 	if (i == -1 || i > int(citeStyles_.size()))
267 		i = 0;
268 	updateFormatting(citeStyles_[i]);
269 	selectionManager->update();
270 }
271 
272 
updateFormatting(CitationStyle const & currentStyle)273 void GuiCitation::updateFormatting(CitationStyle const & currentStyle)
274 {
275 	BufferParams const bp = documentBuffer().params();
276 	bool const force = currentStyle.forceUpperCase;
277 	bool const starred = currentStyle.hasStarredVersion;
278 	bool const full = starred && bp.fullAuthorList();
279 	bool const textbefore = currentStyle.textBefore;
280 	bool const textafter = currentStyle.textAfter;
281 
282 	int const rows = selectedLV->model()->rowCount();
283 
284 	bool const qualified = currentStyle.hasQualifiedList
285 		&& (rows > 1
286 		    || !params_["pretextlist"].empty()
287 		    || !params_["posttextlist"].empty()
288 		    || !getPreTexts().empty()
289 		    || !getPostTexts().empty());
290 
291 	selectedLV->horizontalHeader()->setVisible(qualified);
292 	selectedLV->setColumnHidden(0, !qualified);
293 	selectedLV->setColumnHidden(2, !qualified);
294 	bool const haveSelection = rows > 0;
295 	if (qualified) {
296 		textBeforeLA->setText(qt_("General text befo&re:"));
297 		textAfterLA->setText(qt_("General &text after:"));
298 		textBeforeED->setToolTip(qt_("Text that precedes the whole reference list. "
299 					     "For text that precedes individual items, "
300 					     "double-click on the respective entry above."));
301 		textAfterLA->setToolTip(qt_("General &text after:"));
302 		textAfterED->setToolTip(qt_("Text that follows the whole reference list. "
303 					     "For text that follows individual items, "
304 					     "double-click on the respective entry above."));
305 	} else {
306 		textBeforeLA->setText(qt_("Text befo&re:"));
307 		if (textbefore && haveSelection)
308 			textBeforeED->setToolTip(qt_("Text that precedes the reference (e.g., \"cf.\")"));
309 		else
310 			textBeforeED->setToolTip(qt_("Text that precedes the reference (e.g., \"cf.\"), "
311 						     "if the current citation style supports this."));
312 		textAfterLA->setText(qt_("&Text after:"));
313 		if (textafter && haveSelection)
314 			textAfterED->setToolTip(qt_("Text that follows the reference (e.g., pages)"));
315 		else
316 			textAfterED->setToolTip(qt_("Text that follows the reference (e.g., pages), "
317 						    "if the current citation style supports this."));
318 	}
319 
320 	forceuppercaseCB->setEnabled(force && haveSelection);
321 	if (force && haveSelection)
322 		forceuppercaseCB->setToolTip(qt_("Force upper case in names (\"Del Piero\", not \"del Piero\")."));
323 	else
324 		forceuppercaseCB->setToolTip(qt_("Force upper case in names (\"Del Piero\", not \"del Piero\"), "
325 					     "if the current citation style supports this."));
326 	starredCB->setEnabled(full && haveSelection);
327 	textBeforeED->setEnabled(textbefore && haveSelection);
328 	textBeforeLA->setEnabled(textbefore && haveSelection);
329 	textAfterED->setEnabled(textafter && haveSelection);
330 	textAfterLA->setEnabled(textafter && haveSelection);
331 	literalCB->setEnabled(textbefore || textafter);
332 	citationStyleCO->setEnabled(haveSelection);
333 	citationStyleLA->setEnabled(haveSelection);
334 
335 	// Check if we have a custom string/tooltip for the starred version
336 	if (starred && !currentStyle.stardesc.empty()) {
337 		string val =
338 			bp.documentClass().getCiteMacro(bp.citeEngineType(), currentStyle.stardesc);
339 		docstring guistring;
340 		if (!val.empty()) {
341 			guistring = translateIfPossible(from_utf8(val));
342 			starredCB->setText(toqstr(guistring));
343 			starredCB->setEnabled(haveSelection);
344 		}
345 		if (!currentStyle.startooltip.empty()) {
346 			val = bp.documentClass().getCiteMacro(bp.citeEngineType(),
347 							      currentStyle.startooltip);
348 			if (!val.empty())
349 				guistring = translateIfPossible(from_utf8(val));
350 		}
351 		// Tooltip might also be empty
352 		starredCB->setToolTip(toqstr(guistring));
353 	} else {
354 		// This is the default meaning of the starred commands
355 		starredCB->setText(qt_("All aut&hors"));
356 		if (full && haveSelection)
357 			starredCB->setToolTip(qt_("Always list all authors (rather than using \"et al.\")"));
358 		else
359 			starredCB->setToolTip(qt_("Always list all authors (rather than using \"et al.\"), "
360 						  "if the current citation style supports this."));
361 	}
362 	if (availableLV->selectionModel()->selectedIndexes().isEmpty())
363 		availableLV->setToolTip(qt_("All references available for citing."));
364 	else
365 		availableLV->setToolTip(qt_("All references available for citing.\n"
366 					    "To add the selected one, hit Add, press Enter or double-click.\n"
367 					    "Hit Ctrl-Enter to add and close the dialog."));
368 }
369 
370 
371 // Update the styles for the style combo, citationStyleCO, and mark the
372 // settings as changed. Called upon changing the cited keys (including
373 // merely reordering the keys) or editing the text before/after fields.
updateStyles()374 void GuiCitation::updateStyles()
375 {
376 	BiblioInfo const & bi = bibInfo();
377 	updateStyles(bi);
378 	changed();
379 }
380 
381 
382 // Update the styles for the style combo, citationStyleCO.
updateStyles(BiblioInfo const & bi)383 void GuiCitation::updateStyles(BiblioInfo const & bi)
384 {
385 	QStringList selected_keys = selectedKeys();
386 	int curr = selectedLV->model()->rowCount() - 1;
387 
388 	if (curr < 0 || selected_keys.empty()) {
389 		citationStyleCO->clear();
390 		citationStyleCO->setEnabled(false);
391 		citationStyleLA->setEnabled(false);
392 		return;
393 	}
394 
395 	static const size_t max_length = 80;
396 	BiblioInfo::CiteStringMap sty = citationStyles(bi, max_length);
397 
398 	if (sty.empty()) {
399 		// some error
400 		citationStyleCO->setEnabled(false);
401 		citationStyleLA->setEnabled(false);
402 		citationStyleCO->clear();
403 		return;
404 	}
405 
406 	citationStyleCO->blockSignals(true);
407 
408 	// save old style selection
409 	QString const curdata =
410 		citationStyleCO->itemData(citationStyleCO->currentIndex()).toString();
411 	QString const olddata = (curdata.isEmpty()) ? style_ : curdata;
412 	citationStyleCO->clear();
413 	BiblioInfo::CiteStringMap::const_iterator cit = sty.begin();
414 	BiblioInfo::CiteStringMap::const_iterator end = sty.end();
415 	for (int ii = 1; cit != end; ++cit, ++ii)
416 		citationStyleCO->addItem(toqstr(cit->second), toqstr(cit->first));
417 	citationStyleCO->setEnabled(true);
418 	citationStyleLA->setEnabled(true);
419 	// restore old style selection
420 	int const i = citationStyleCO->findData(olddata);
421 	if (i != -1)
422 		citationStyleCO->setCurrentIndex(i);
423 
424 	citationStyleCO->blockSignals(false);
425 }
426 
427 
fillFields(BiblioInfo const & bi)428 void GuiCitation::fillFields(BiblioInfo const & bi)
429 {
430 	fieldsCO->blockSignals(true);
431 	int const oldIndex = fieldsCO->currentIndex();
432 	fieldsCO->clear();
433 	QStringList const fields = to_qstring_list(bi.getFields());
434 	fieldsCO->insertItem(0, qt_("All fields"));
435 	fieldsCO->insertItem(1, qt_("Keys"));
436 	fieldsCO->insertItems(2, fields);
437 	if (oldIndex != -1 && oldIndex < fieldsCO->count())
438 		fieldsCO->setCurrentIndex(oldIndex);
439 	fieldsCO->blockSignals(false);
440 }
441 
442 
fillEntries(BiblioInfo const & bi)443 void GuiCitation::fillEntries(BiblioInfo const & bi)
444 {
445 	entriesCO->blockSignals(true);
446 	int const oldIndex = entriesCO->currentIndex();
447 	entriesCO->clear();
448 	QStringList const entries = to_qstring_list(bi.getEntries());
449 	entriesCO->insertItem(0, qt_("All entry types"));
450 	entriesCO->insertItems(1, entries);
451 	if (oldIndex != -1 && oldIndex < entriesCO->count())
452 		entriesCO->setCurrentIndex(oldIndex);
453 	entriesCO->blockSignals(false);
454 }
455 
456 
isSelected(QModelIndex const & idx)457 bool GuiCitation::isSelected(QModelIndex const & idx)
458 {
459 	QString const str = idx.data().toString();
460 	return selectedKeys().contains(str);
461 }
462 
463 
setButtons()464 void GuiCitation::setButtons()
465 {
466 	int const srows = selectedLV->model()->rowCount();
467 	applyPB->setEnabled(srows > 0);
468 	okPB->setEnabled(srows > 0);
469 }
470 
471 
updateInfo(BiblioInfo const & bi,QModelIndex const & idx)472 void GuiCitation::updateInfo(BiblioInfo const & bi, QModelIndex const & idx)
473 {
474 	if (!idx.isValid() || bi.empty()) {
475 		infoML->document()->clear();
476 		infoML->setToolTip(qt_("Displays a sketchy preview if a citation is selected above"));
477 		return;
478 	}
479 
480 	infoML->setToolTip(qt_("Sketchy preview of the selected citation"));
481 	CiteItem ci;
482 	ci.richtext = true;
483 	QString const keytxt = toqstr(
484 		bi.getInfo(qstring_to_ucs4(idx.data().toString()), documentBuffer(), ci));
485 	infoML->document()->setHtml(keytxt);
486 }
487 
488 
findText(QString const & text,bool reset)489 void GuiCitation::findText(QString const & text, bool reset)
490 {
491 	//"All Fields" and "Keys" are the first two
492 	int index = fieldsCO->currentIndex() - 2;
493 	BiblioInfo const & bi = bibInfo();
494 	vector<docstring> const & fields = bi.getFields();
495 	docstring field;
496 
497 	if (index <= -1 || index >= int(fields.size()))
498 		//either "All Fields" or "Keys" or an invalid value
499 		field = from_ascii("");
500 	else
501 		field = fields[index];
502 
503 	//Was it "Keys"?
504 	bool const onlyKeys = index == -1;
505 
506 	//"All Entry Types" is first.
507 	index = entriesCO->currentIndex() - 1;
508 	vector<docstring> const & entries = bi.getEntries();
509 	docstring entry_type;
510 	if (index < 0 || index >= int(entries.size()))
511 		entry_type = from_ascii("");
512 	else
513 		entry_type = entries[index];
514 
515 	bool const case_sentitive = casesense_->isChecked();
516 	bool const reg_exp = regexp_->isChecked();
517 
518 	findKey(bi, text, onlyKeys, field, entry_type,
519 	               case_sentitive, reg_exp, reset);
520 	//FIXME
521 	//It'd be nice to save and restore the current selection in
522 	//availableLV. Currently, we get an automatic reset, since the
523 	//model is reset.
524 
525 	updateControls(bi);
526 }
527 
528 
on_fieldsCO_currentIndexChanged(int)529 void GuiCitation::on_fieldsCO_currentIndexChanged(int /*index*/)
530 {
531 	findText(filter_->text(), true);
532 }
533 
534 
on_entriesCO_currentIndexChanged(int)535 void GuiCitation::on_entriesCO_currentIndexChanged(int /*index*/)
536 {
537 	findText(filter_->text(), true);
538 }
539 
540 
on_citationStyleCO_currentIndexChanged(int index)541 void GuiCitation::on_citationStyleCO_currentIndexChanged(int index)
542 {
543 	if (index >= 0 && index < citationStyleCO->count()) {
544 		vector<CitationStyle> const & styles = citeStyles_;
545 		updateFormatting(styles[index]);
546 		changed();
547 	}
548 }
549 
550 
filterChanged(const QString & text)551 void GuiCitation::filterChanged(const QString & text)
552 {
553 	if (!text.isEmpty()) {
554 		if (instant_->isChecked())
555 			findText(filter_->text());
556 		return;
557 	}
558 	findText(filter_->text());
559 	filter_->setFocus();
560 }
561 
562 
filterPressed()563 void GuiCitation::filterPressed()
564 {
565 	findText(filter_->text(), true);
566 }
567 
568 
resetFilter()569 void GuiCitation::resetFilter()
570 {
571 	filter_->setText(QString());
572 	findText(filter_->text(), true);
573 }
574 
575 
caseChanged()576 void GuiCitation::caseChanged()
577 {
578 	findText(filter_->text());
579 }
580 
581 
regexChanged()582 void GuiCitation::regexChanged()
583 {
584 	findText(filter_->text());
585 }
586 
587 
updateFilterHint()588 void GuiCitation::updateFilterHint()
589 {
590 	QString hint = instant_->isChecked() ?
591 		qt_("Enter string to filter the list of available citations") :
592 		qt_("Enter string to filter the list of available citations and press <Enter>");
593 	hint += qt_("\nThe down arrow key will get you into the list of filtered citations.");
594 	filter_->setToolTip(hint);
595 }
596 
597 
instantChanged(bool checked)598 void GuiCitation::instantChanged(bool checked)
599 {
600 	if (checked)
601 		findText(filter_->text(), true);
602 
603 	updateFilterHint();
604 }
605 
606 
changed()607 void GuiCitation::changed()
608 {
609 	setButtons();
610 }
611 
612 
applyParams(int const choice,bool full,bool force,QString before,QString after)613 void GuiCitation::applyParams(int const choice, bool full, bool force,
614 	QString before, QString after)
615 {
616 	if (cited_keys_.isEmpty())
617 		return;
618 
619 	vector<CitationStyle> const & styles = citeStyles_;
620 
621 	CitationStyle cs = styles[choice];
622 
623 	if (!cs.textBefore)
624 		before.clear();
625 	if (!cs.textAfter)
626 		after.clear();
627 
628 	cs.forceUpperCase &= force;
629 	cs.hasStarredVersion &= full;
630 	string const command = citationStyleToString(cs);
631 
632 	params_.setCmdName(command);
633 	params_["key"] = qstring_to_ucs4(cited_keys_.join(","));
634 	params_["before"] = qstring_to_ucs4(before);
635 	params_["after"] = qstring_to_ucs4(after);
636 	if (cs.hasQualifiedList) {
637 		params_["pretextlist"] = getStringFromVector(getPreTexts(), from_ascii("\t"));
638 		params_["posttextlist"] = getStringFromVector(getPostTexts(), from_ascii("\t"));
639 	}
640 	params_["literal"] = literalCB->isChecked() ? from_ascii("true") : from_ascii("false");
641 	dispatchParams();
642 }
643 
644 
clearSelection()645 void GuiCitation::clearSelection()
646 {
647 	cited_keys_.clear();
648 	setSelectedKeys(cited_keys_);
649 }
650 
651 
setSelectedKeys(QStringList const sl)652 void GuiCitation::setSelectedKeys(QStringList const sl)
653 {
654 	selected_model_.clear();
655 	selected_model_.setColumnCount(3);
656 	QStringList headers;
657 	headers << qt_("Text before")
658 		<< qt_("Cite key")
659 		<< qt_("Text after");
660 	selected_model_.setHorizontalHeaderLabels(headers);
661 	selectedLV->setColumnHidden(0, true);
662 	selectedLV->setColumnHidden(2, true);
663 	selectedLV->verticalHeader()->setVisible(false);
664 	selectedLV->horizontalHeader()->setVisible(false);
665 	QStringList::const_iterator it  = sl.begin();
666 	QStringList::const_iterator end = sl.end();
667 	for (int i = 0; it != end; ++it, ++i) {
668 		QStandardItem * si = new QStandardItem();
669 		si->setData(*it);
670 		si->setText(*it);
671 		si->setToolTip(*it);
672 		si->setEditable(false);
673 		selected_model_.setItem(i, 1, si);
674 	}
675 }
676 
677 
selectedKeys()678 QStringList GuiCitation::selectedKeys()
679 {
680 	QStringList res;
681 	for (int i = 0; i != selected_model_.rowCount(); ++i) {
682 		QStandardItem const * item = selected_model_.item(i, 1);
683 		if (item)
684 			res.append(item->text());
685 	}
686 	return res;
687 }
688 
689 
setPreTexts(vector<docstring> const m)690 void GuiCitation::setPreTexts(vector<docstring> const m)
691 {
692 	for (docstring const & s: m) {
693 		QStandardItem * si = new QStandardItem();
694 		docstring key;
695 		docstring pre = split(s, key, ' ');
696 		si->setData(toqstr(pre));
697 		si->setText(toqstr(pre));
698 		QModelIndexList qmil =
699 				selected_model_.match(selected_model_.index(0, 1),
700 						     Qt::DisplayRole, toqstr(key), 1,
701 						     Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
702 		if (!qmil.empty())
703 			selected_model_.setItem(qmil.front().row(), 0, si);
704 	}
705 }
706 
707 
getPreTexts()708 vector<docstring> GuiCitation::getPreTexts()
709 {
710 	vector<docstring> res;
711 	for (int i = 0; i != selected_model_.rowCount(); ++i) {
712 		QStandardItem const * key = selected_model_.item(i, 1);
713 		QStandardItem const * pre = selected_model_.item(i, 0);
714 		if (key && pre && !key->text().isEmpty() && !pre->text().isEmpty())
715 			res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(pre->text()));
716 	}
717 	return res;
718 }
719 
720 
setPostTexts(vector<docstring> const m)721 void GuiCitation::setPostTexts(vector<docstring> const m)
722 {
723 	for (docstring const & s: m) {
724 		QStandardItem * si = new QStandardItem();
725 		docstring key;
726 		docstring post = split(s, key, ' ');
727 		si->setData(toqstr(post));
728 		si->setText(toqstr(post));
729 		QModelIndexList qmil =
730 				selected_model_.match(selected_model_.index(0, 1),
731 						     Qt::DisplayRole, toqstr(key), 1,
732 						     Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
733 		if (!qmil.empty())
734 			selected_model_.setItem(qmil.front().row(), 2, si);
735 	}
736 }
737 
738 
getPostTexts()739 vector<docstring> GuiCitation::getPostTexts()
740 {
741 	vector<docstring> res;
742 	for (int i = 0; i != selected_model_.rowCount(); ++i) {
743 		QStandardItem const * key = selected_model_.item(i, 1);
744 		QStandardItem const * post = selected_model_.item(i, 2);
745 		if (key && post && !key->text().isEmpty() && !post->text().isEmpty())
746 			res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(post->text()));
747 	}
748 	return res;
749 }
750 
751 
init()752 void GuiCitation::init()
753 {
754 	// Make the list of all available bibliography keys
755 	BiblioInfo const & bi = bibInfo();
756 	all_keys_ = to_qstring_list(bi.getKeys());
757 
758 	available_model_.setStringList(all_keys_);
759 
760 	// Ditto for the keys cited in this inset
761 	QString str = toqstr(params_["key"]);
762 	if (str.isEmpty())
763 		cited_keys_.clear();
764 	else
765 		cited_keys_ = str.split(",");
766 	setSelectedKeys(cited_keys_);
767 
768 	// Initialize the drop downs
769 	fillEntries(bi);
770 	fillFields(bi);
771 
772 	// Initialize the citation formatting
773 	string const & cmd = params_.getCmdName();
774 	CitationStyle const cs =
775 		citationStyleFromString(cmd, documentBuffer().params());
776 
777 	forceuppercaseCB->setChecked(cs.forceUpperCase);
778 	starredCB->setChecked(cs.hasStarredVersion &&
779 		documentBuffer().params().fullAuthorList());
780 	textBeforeED->setText(toqstr(params_["before"]));
781 	textAfterED->setText(toqstr(params_["after"]));
782 
783 	// if this is a new citation, we set the literal checkbox
784 	// to its last set value.
785 	if (cited_keys_.isEmpty())
786 		literalCB->setChecked(InsetCitation::last_literal);
787 	else
788 		literalCB->setChecked(params_["literal"] == "true");
789 
790 	setPreTexts(getVectorFromString(params_["pretextlist"], from_ascii("\t")));
791 	setPostTexts(getVectorFromString(params_["posttextlist"], from_ascii("\t")));
792 
793 	// Update the interface
794 	updateControls(bi);
795 	updateStyles(bi);
796 	if (selected_model_.rowCount()) {
797 		selectedLV->blockSignals(true);
798 		selectedLV->setFocus();
799 		selectedLV->selectRow(0);
800 		selectedLV->blockSignals(false);
801 
802 		// Find the citation style
803 		vector<string> const & cmds = citeCmds_;
804 		vector<string>::const_iterator cit =
805 			std::find(cmds.begin(), cmds.end(), cs.name);
806 		int i = 0;
807 		if (cit != cmds.end())
808 			i = int(cit - cmds.begin());
809 
810 		// Set the style combo appropriately
811 		citationStyleCO->blockSignals(true);
812 		citationStyleCO->setCurrentIndex(i);
813 		citationStyleCO->blockSignals(false);
814 		updateFormatting(citeStyles_[i]);
815 	} else
816 		availableLV->setFocus();
817 
818 	applyPB->setEnabled(false);
819 	okPB->setEnabled(false);
820 }
821 
822 
findKey(BiblioInfo const & bi,QString const & str,bool only_keys,docstring field,docstring entry_type,bool case_sensitive,bool reg_exp,bool reset)823 void GuiCitation::findKey(BiblioInfo const & bi,
824 	QString const & str, bool only_keys,
825 	docstring field, docstring entry_type,
826 	bool case_sensitive, bool reg_exp, bool reset)
827 {
828 	// FIXME THREAD
829 	// This should be moved to a class member.
830 	// Used for optimisation: store last searched string.
831 	static QString last_searched_string;
832 	// Used to disable the above optimisation.
833 	static bool last_case_sensitive;
834 	static bool last_reg_exp;
835 	// Reset last_searched_string in case of changed option.
836 	if (last_case_sensitive != case_sensitive
837 		|| last_reg_exp != reg_exp) {
838 			LYXERR(Debug::GUI, "GuiCitation::findKey: optimisation disabled!");
839 		last_searched_string.clear();
840 	}
841 	// save option for next search.
842 	last_case_sensitive = case_sensitive;
843 	last_reg_exp = reg_exp;
844 
845 	Qt::CaseSensitivity qtcase = case_sensitive ?
846 			Qt::CaseSensitive: Qt::CaseInsensitive;
847 	QStringList keys;
848 	// If new string (str) contains the last searched one...
849 	if (!reset &&
850 		!last_searched_string.isEmpty() &&
851 		str.size() > 1 &&
852 		str.contains(last_searched_string, qtcase))
853 		// ... then only search within already found list.
854 		keys = available_model_.stringList();
855 	else
856 		// ... else search all keys.
857 		keys = all_keys_;
858 	// save searched string for next search.
859 	last_searched_string = str;
860 
861 	QStringList result;
862 
863 	// First, filter by entry_type, which will be faster than
864 	// what follows, so we may get to do that on less.
865 	vector<docstring> keyVector = to_docstring_vector(keys);
866 	filterByEntryType(bi, keyVector, entry_type);
867 
868 	if (str.isEmpty())
869 		result = to_qstring_list(keyVector);
870 	else
871 		result = to_qstring_list(searchKeys(bi, keyVector, only_keys,
872 			qstring_to_ucs4(str), field, case_sensitive, reg_exp));
873 
874 	available_model_.setStringList(result);
875 }
876 
877 
citationStyles(BiblioInfo const & bi,size_t max_size)878 BiblioInfo::CiteStringMap GuiCitation::citationStyles(BiblioInfo const & bi, size_t max_size)
879 {
880 	vector<docstring> const keys = to_docstring_vector(cited_keys_);
881 	vector<CitationStyle> styles = citeStyles_;
882 	int ind = citationStyleCO->currentIndex();
883 	if (ind == -1)
884 		ind = 0;
885 	CitationStyle cs = styles[ind];
886 	vector<docstring> pretexts = getPreTexts();
887 	vector<docstring> posttexts = getPostTexts();
888 	bool const qualified = cs.hasQualifiedList
889 		&& (selectedLV->model()->rowCount() > 1
890 		    || !pretexts.empty()
891 		    || !posttexts.empty());
892 	std::map<docstring, docstring> pres;
893 	for (docstring const & s: pretexts) {
894 		docstring key;
895 		docstring val = split(s, key, ' ');
896 		pres[key] = val;
897 	}
898 	std::map<docstring, docstring> posts;
899 	for (docstring const & s: posttexts) {
900 		docstring key;
901 		docstring val = split(s, key, ' ');
902 		posts[key] = val;
903 	}
904 	CiteItem ci;
905 	ci.textBefore = qstring_to_ucs4(textBeforeED->text());
906 	ci.textAfter = qstring_to_ucs4(textAfterED->text());
907 	ci.forceUpperCase = forceuppercaseCB->isChecked();
908 	ci.Starred = starredCB->isChecked();
909 	ci.context = CiteItem::Dialog;
910 	ci.max_size = max_size;
911 	ci.isQualified = qualified;
912 	ci.pretexts = pres;
913 	ci.posttexts = posts;
914 	BiblioInfo::CiteStringMap ret = bi.getCiteStrings(keys, styles, documentBuffer(), ci);
915 	return ret;
916 }
917 
918 
setCitedKeys()919 void GuiCitation::setCitedKeys()
920 {
921 	cited_keys_ = selectedKeys();
922 	updateStyles();
923 }
924 
925 
initialiseParams(string const & data)926 bool GuiCitation::initialiseParams(string const & data)
927 {
928 	InsetCommand::string2params(data, params_);
929 	citeCmds_ = documentBuffer().params().citeCommands();
930 	citeStyles_ = documentBuffer().params().citeStyles();
931 	init();
932 	return true;
933 }
934 
935 
clearParams()936 void GuiCitation::clearParams()
937 {
938 	params_.clear();
939 }
940 
941 
filterByEntryType(BiblioInfo const & bi,vector<docstring> & keyVector,docstring entry_type)942 void GuiCitation::filterByEntryType(BiblioInfo const & bi,
943 	vector<docstring> & keyVector, docstring entry_type)
944 {
945 	if (entry_type.empty())
946 		return;
947 
948 	vector<docstring>::iterator it = keyVector.begin();
949 	vector<docstring>::iterator end = keyVector.end();
950 
951 	vector<docstring> result;
952 	for (; it != end; ++it) {
953 		docstring const key = *it;
954 		BiblioInfo::const_iterator cit = bi.find(key);
955 		if (cit == bi.end())
956 			continue;
957 		if (cit->second.entryType() == entry_type)
958 			result.push_back(key);
959 	}
960 	keyVector = result;
961 }
962 
963 
964 // Escape special chars.
965 // All characters are literals except: '.|*?+(){}[]^$\'
966 // These characters are literals when preceded by a "\", which is done here
967 // @todo: This function should be moved to support, and then the test in tests
968 //        should be moved there as well.
escape_special_chars(docstring const & expr)969 static docstring escape_special_chars(docstring const & expr)
970 {
971 	// Search for all chars '.|*?+(){}[^$]\'
972 	// Note that '[', ']', and '\' must be escaped.
973 	static const lyx::regex reg("[.|*?+(){}^$\\[\\]\\\\]");
974 
975 	// $& is an ECMAScript format expression that expands to all
976 	// of the current match
977 #ifdef LYX_USE_STD_REGEX
978 	// To prefix a matched expression with a single literal backslash, we
979 	// need to escape it for the C++ compiler and use:
980 	// FIXME: UNICODE
981 	return from_utf8(lyx::regex_replace(to_utf8(expr), reg, string("\\$&")));
982 #else
983 	// A backslash in the format string starts an escape sequence in boost.
984 	// Thus, to prefix a matched expression with a single literal backslash,
985 	// we need to give two backslashes to the regex engine, and escape both
986 	// for the C++ compiler and use:
987 	// FIXME: UNICODE
988 	return from_utf8(lyx::regex_replace(to_utf8(expr), reg, string("\\\\$&")));
989 #endif
990 }
991 
992 
searchKeys(BiblioInfo const & bi,vector<docstring> const & keys_to_search,bool only_keys,docstring const & search_expression,docstring field,bool case_sensitive,bool regex)993 vector<docstring> GuiCitation::searchKeys(BiblioInfo const & bi,
994 	vector<docstring> const & keys_to_search, bool only_keys,
995  	docstring const & search_expression, docstring field,
996 	bool case_sensitive, bool regex)
997 {
998 	vector<docstring> foundKeys;
999 
1000 	docstring expr = trim(search_expression);
1001 	if (expr.empty())
1002 		return foundKeys;
1003 
1004 	if (!regex)
1005 		// We must escape special chars in the search_expr so that
1006 		// it is treated as a simple string by lyx::regex.
1007 		expr = escape_special_chars(expr);
1008 
1009 	lyx::regex reg_exp;
1010 	try {
1011 		reg_exp.assign(to_utf8(expr), case_sensitive ?
1012 			lyx::regex_constants::ECMAScript : lyx::regex_constants::icase);
1013 	} catch (lyx::regex_error const & e) {
1014 		// lyx::regex throws an exception if the regular expression is not
1015 		// valid.
1016 		LYXERR(Debug::GUI, e.what());
1017 		return vector<docstring>();
1018 	}
1019 
1020 	vector<docstring>::const_iterator it = keys_to_search.begin();
1021 	vector<docstring>::const_iterator end = keys_to_search.end();
1022 	for (; it != end; ++it ) {
1023 		BiblioInfo::const_iterator info = bi.find(*it);
1024 		if (info == bi.end())
1025 			continue;
1026 
1027 		BibTeXInfo const & kvm = info->second;
1028 		string data;
1029 		if (only_keys)
1030 			data = to_utf8(*it);
1031 		else if (field.empty())
1032 			data = to_utf8(*it) + ' ' + to_utf8(kvm.allData());
1033 		else
1034 			data = to_utf8(kvm[field]);
1035 
1036 		if (data.empty())
1037 			continue;
1038 
1039 		try {
1040 			if (lyx::regex_search(data, reg_exp))
1041 				foundKeys.push_back(*it);
1042 		}
1043 		catch (lyx::regex_error const & e) {
1044 			LYXERR(Debug::GUI, e.what());
1045 			return vector<docstring>();
1046 		}
1047 	}
1048 	return foundKeys;
1049 }
1050 
1051 
dispatchParams()1052 void GuiCitation::dispatchParams()
1053 {
1054 	std::string const lfun = InsetCommand::params2string(params_);
1055 	dispatch(FuncRequest(getLfun(), lfun));
1056 }
1057 
1058 
bibInfo() const1059 BiblioInfo const & GuiCitation::bibInfo() const
1060 {
1061 	Buffer const & buf = documentBuffer();
1062 	buf.reloadBibInfoCache();
1063 	return buf.masterBibInfo();
1064 }
1065 
1066 
saveSession(QSettings & settings) const1067 void GuiCitation::saveSession(QSettings & settings) const
1068 {
1069 	Dialog::saveSession(settings);
1070 	settings.setValue(
1071 		sessionKey() + "/regex", regexp_->isChecked());
1072 	settings.setValue(
1073 		sessionKey() + "/casesensitive", casesense_->isChecked());
1074 	settings.setValue(
1075 		sessionKey() + "/autofind", instant_->isChecked());
1076 	settings.setValue(
1077 		sessionKey() + "/citestyle", style_);
1078 	settings.setValue(
1079 		sessionKey() + "/literal", InsetCitation::last_literal);
1080 }
1081 
1082 
restoreSession()1083 void GuiCitation::restoreSession()
1084 {
1085 	Dialog::restoreSession();
1086 	QSettings settings;
1087 	regexp_->setChecked(settings.value(sessionKey() + "/regex").toBool());
1088 	casesense_->setChecked(settings.value(sessionKey() + "/casesensitive").toBool());
1089 	instant_->setChecked(settings.value(sessionKey() + "/autofind", true).toBool());
1090 	style_ = settings.value(sessionKey() + "/citestyle").toString();
1091 	InsetCitation::last_literal =
1092 		settings.value(sessionKey() + "/literal", false).toBool();
1093 	updateFilterHint();
1094 }
1095 
1096 
createGuiCitation(GuiView & lv)1097 Dialog * createGuiCitation(GuiView & lv) { return new GuiCitation(lv); }
1098 
1099 
1100 } // namespace frontend
1101 } // namespace lyx
1102 
1103 #include "moc_GuiCitation.cpp"
1104 
1105