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