]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
Transfer some more dialog related code from core to frontend:
[lyx.git] / src / frontends / qt4 / GuiCitation.cpp
1 /**
2  * \file GuiCitation.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Kalle Dalheimer
8  * \author Abdelrazak Younes
9  * \author Richard Heck
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiCitation.h"
17
18 #include "debug.h"
19 #include "gettext.h"
20 #include "qt_helpers.h"
21 #include "Buffer.h"
22 #include "BufferParams.h"
23
24 #include "support/lstrings.h"
25 #include "support/docstring.h"
26
27 #include "insets/InsetCommand.h"
28
29 #include <vector>
30 #include <string>
31
32 #include <QCloseEvent>
33
34 #undef KeyPress
35
36 #include <boost/regex.hpp>
37
38 #include <algorithm>
39 #include <string>
40 #include <vector>
41
42 using std::string;
43 using std::vector;
44 using std::pair;
45
46 namespace lyx {
47 namespace frontend {
48
49 static vector<biblio::CiteStyle> citeStyles_;
50
51
52 template<typename String>
53 static QStringList to_qstring_list(vector<String> const & v)
54 {
55         QStringList qlist;
56
57         for (size_t i = 0; i != v.size(); ++i) {
58                 if (v[i].empty())
59                         continue;
60                 qlist.append(lyx::toqstr(v[i]));
61         }
62         return qlist;
63 }
64
65
66 static vector<lyx::docstring> to_docstring_vector(QStringList const & qlist)
67 {
68         vector<lyx::docstring> v;
69         for (int i = 0; i != qlist.size(); ++i) {
70                 if (qlist[i].isEmpty())
71                         continue;
72                 v.push_back(lyx::qstring_to_ucs4(qlist[i]));
73         }
74         return v;
75 }
76
77
78 GuiCitation::GuiCitation(GuiView & lv)
79         : GuiCommand(lv, "citation")
80 {
81         setupUi(this);
82         setViewTitle(_("Citation"));
83
84         connect(citationStyleCO, SIGNAL(activated(int)),
85                 this, SLOT(changed()));
86         connect(fulllistCB, SIGNAL(clicked()),
87                 this, SLOT(changed()));
88         connect(forceuppercaseCB, SIGNAL(clicked()),
89                 this, SLOT(changed()));
90         connect(textBeforeED, SIGNAL(textChanged(QString)),
91                 this, SLOT(changed()));
92         connect(textAfterED, SIGNAL(textChanged(QString)),
93                 this, SLOT(changed()));
94         connect(clearPB, SIGNAL(clicked()),
95                 findLE, SLOT(clear()));
96         connect(this, SIGNAL(rejected()), this, SLOT(cleanUp()));
97
98         selectionManager = new GuiSelectionManager(availableLV, selectedLV, 
99                         addPB, deletePB, upPB, downPB, available(), selected());
100         connect(selectionManager, SIGNAL(selectionChanged()),
101                 this, SLOT(setCitedKeys()));
102         connect(selectionManager, SIGNAL(updateHook()),
103                 this, SLOT(updateDialog()));
104         connect(selectionManager, SIGNAL(okHook()),
105                 this, SLOT(on_okPB_clicked()));
106
107         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
108 }
109
110
111 void GuiCitation::cleanUp() 
112 {
113         clearSelection();
114         clearParams();
115         close();
116 }
117
118
119 void GuiCitation::closeEvent(QCloseEvent * e)
120 {
121         clearSelection();
122         clearParams();
123         GuiDialog::closeEvent(e);
124 }
125
126
127 void GuiCitation::applyView()
128 {
129         int  const choice = std::max(0, citationStyleCO->currentIndex());
130         style_ = choice;
131         bool const full  = fulllistCB->isChecked();
132         bool const force = forceuppercaseCB->isChecked();
133
134         QString const before = textBeforeED->text();
135         QString const after = textAfterED->text();
136
137         apply(choice, full, force, before, after);
138 }
139
140
141 void GuiCitation::hideView()
142 {
143         clearParams();
144         accept();
145 }
146
147
148 void GuiCitation::showView()
149 {
150         init();
151         findLE->clear();
152         availableLV->setFocus();
153         QDialog::show();
154         raise();
155         activateWindow();
156 }
157
158
159 bool GuiCitation::isVisibleView() const
160 {
161         return QDialog::isVisible();
162 }
163
164
165 void GuiCitation::on_okPB_clicked()
166 {
167         applyView();
168         clearSelection();
169         hideView();
170 }
171
172
173 void GuiCitation::on_cancelPB_clicked()
174 {
175         clearSelection();
176         hideView();
177 }
178
179
180 void GuiCitation::on_applyPB_clicked()
181 {
182         applyView();
183 }
184
185
186 void GuiCitation::on_restorePB_clicked()
187 {
188         init();
189         updateView();
190 }
191
192
193 void GuiCitation::updateView()
194 {
195         init();
196         fillFields();
197         fillEntries();
198         updateDialog();
199 }
200
201
202 // The main point of separating this out is that the fill*() methods
203 // called in update() do not need to be called for INTERNAL updates,
204 // such as when addPB is pressed, as the list of fields, entries, etc,
205 // will not have changed. At the moment, however, the division between
206 // fillStyles() and updateStyles() doesn't lend itself to dividing the
207 // two methods, though they should be divisible.
208 void GuiCitation::updateDialog()
209 {
210         if (selectionManager->selectedFocused()) { 
211                 if (selectedLV->selectionModel()->selectedIndexes().isEmpty())
212                         updateInfo(availableLV->currentIndex());
213                 else
214                         updateInfo(selectedLV->currentIndex());
215         } else {
216                 if (availableLV->selectionModel()->selectedIndexes().isEmpty())
217                         updateInfo(QModelIndex());
218                 else
219                         updateInfo(availableLV->currentIndex());
220         }
221         setButtons();
222
223         textBeforeED->setText(textBefore());
224         textAfterED->setText(textAfter());
225         fillStyles();
226         updateStyle();
227 }
228
229
230 void GuiCitation::updateStyle()
231 {
232         biblio::CiteEngine const engine = getEngine();
233         bool const natbib_engine =
234                 engine == biblio::ENGINE_NATBIB_AUTHORYEAR ||
235                 engine == biblio::ENGINE_NATBIB_NUMERICAL;
236         bool const basic_engine = engine == biblio::ENGINE_BASIC;
237
238         bool const haveSelection = 
239                 selectedLV->model()->rowCount() > 0;
240         fulllistCB->setEnabled(natbib_engine && haveSelection);
241         forceuppercaseCB->setEnabled(natbib_engine && haveSelection);
242         textBeforeED->setEnabled(!basic_engine && haveSelection);
243         textBeforeLA->setEnabled(!basic_engine && haveSelection);
244         textAfterED->setEnabled(haveSelection);
245         textAfterLA->setEnabled(haveSelection);
246         citationStyleCO->setEnabled(!basic_engine && haveSelection);
247         citationStyleLA->setEnabled(!basic_engine && haveSelection);
248
249         string const & command = params_.getCmdName();
250
251         // Find the style of the citekeys
252         vector<biblio::CiteStyle> const & styles = citeStyles_;
253         biblio::CitationStyle const cs(command);
254
255         vector<biblio::CiteStyle>::const_iterator cit =
256                 std::find(styles.begin(), styles.end(), cs.style);
257
258         // restore the latest natbib style
259         if (style_ >= 0 && style_ < citationStyleCO->count())
260                 citationStyleCO->setCurrentIndex(style_);
261         else
262                 citationStyleCO->setCurrentIndex(0);
263
264         if (cit != styles.end()) {
265                 int const i = int(cit - styles.begin());
266                 citationStyleCO->setCurrentIndex(i);
267                 fulllistCB->setChecked(cs.full);
268                 forceuppercaseCB->setChecked(cs.forceUCase);
269         } else {
270                 fulllistCB->setChecked(false);
271                 forceuppercaseCB->setChecked(false);
272         }
273 }
274
275
276 //This one needs to be called whenever citationStyleCO needs
277 //to be updated---and this would be on anything that changes the
278 //selection in selectedLV, or on a general update.
279 void GuiCitation::fillStyles()
280 {
281         int const oldIndex = citationStyleCO->currentIndex();
282
283         citationStyleCO->clear();
284
285         QStringList selected_keys = selected()->stringList();
286         if (selected_keys.empty()) {
287                 citationStyleCO->setEnabled(false);
288                 citationStyleLA->setEnabled(false);
289                 return;
290         }
291
292         int curr = selectedLV->model()->rowCount() - 1;
293         if (curr < 0)
294                 return;
295
296         if (!selectedLV->selectionModel()->selectedIndexes().empty())
297                 curr = selectedLV->selectionModel()->selectedIndexes()[0].row();
298
299         QStringList sty = citationStyles(curr);
300
301         bool const basic_engine = (getEngine() == biblio::ENGINE_BASIC);
302
303         citationStyleCO->setEnabled(!sty.isEmpty() && !basic_engine);
304         citationStyleLA->setEnabled(!sty.isEmpty() && !basic_engine);
305
306         if (sty.isEmpty() || basic_engine)
307                 return;
308
309         citationStyleCO->insertItems(0, sty);
310
311         if (oldIndex != -1 && oldIndex < citationStyleCO->count())
312                 citationStyleCO->setCurrentIndex(oldIndex);
313 }
314
315
316 void GuiCitation::fillFields()
317 {
318         fieldsCO->blockSignals(true);
319         int const oldIndex = fieldsCO->currentIndex();
320         fieldsCO->clear();
321         QStringList const & fields = getFieldsAsQStringList();
322         fieldsCO->insertItem(0, qt_("All Fields"));
323         fieldsCO->insertItem(1, qt_("Keys"));
324         fieldsCO->insertItems(2, fields);
325         if (oldIndex != -1 && oldIndex < fieldsCO->count())
326                 fieldsCO->setCurrentIndex(oldIndex);
327         fieldsCO->blockSignals(false);
328 }
329
330
331 void GuiCitation::fillEntries()
332 {
333         entriesCO->blockSignals(true);
334         int const oldIndex = entriesCO->currentIndex();
335         entriesCO->clear();
336         QStringList const & entries = getEntriesAsQStringList();
337         entriesCO->insertItem(0, qt_("All Entry Types"));
338         entriesCO->insertItems(1, entries);
339         if (oldIndex != -1 && oldIndex < entriesCO->count())
340                 entriesCO->setCurrentIndex(oldIndex);
341         entriesCO->blockSignals(false);
342 }
343
344
345 bool GuiCitation::isSelected(const QModelIndex & idx)
346 {
347         QString const str = idx.data().toString();
348         return selected()->stringList().contains(str);
349 }
350
351
352 void GuiCitation::setButtons()
353 {
354         selectionManager->update();
355         int const srows = selectedLV->model()->rowCount();
356         applyPB->setEnabled(srows > 0);
357         okPB->setEnabled(srows > 0);
358 }
359
360
361 void GuiCitation::updateInfo(QModelIndex const & idx)
362 {
363         if (idx.isValid()) {
364                 QString const keytxt = getKeyInfo(idx.data().toString());
365                 infoML->document()->setPlainText(keytxt);
366         } else
367                 infoML->document()->clear();
368 }
369
370
371 void GuiCitation::findText(QString const & text, bool reset)
372 {
373         //"All Fields" and "Keys" are the first two
374         int index = fieldsCO->currentIndex() - 2; 
375         vector<docstring> const & fields = availableFields();
376         docstring field;
377         
378         if (index <= -1 || index >= int(fields.size()))
379                 //either "All Fields" or "Keys" or an invalid value
380                 field = from_ascii("");
381         else
382                 field = fields[index];
383         
384         //Was it "Keys"?
385         bool const onlyKeys = index == -1;
386         
387         //"All Entry Types" is first.
388         index = entriesCO->currentIndex() - 1; 
389         vector<docstring> const & entries = availableEntries();
390         docstring entryType;
391         if (index < 0 || index >= int(entries.size()))
392                 entryType = from_ascii("");
393         else 
394                 entryType = entries[index];
395         
396         bool const case_sentitive = caseCB->checkState();
397         bool const reg_exp = regexCB->checkState();
398         findKey(text, onlyKeys, field, entryType, 
399                        case_sentitive, reg_exp, reset);
400         //FIXME
401         //It'd be nice to save and restore the current selection in 
402         //availableLV. Currently, we get an automatic reset, since the
403         //model is reset.
404         
405         updateDialog();
406 }
407
408
409 void GuiCitation::on_fieldsCO_currentIndexChanged(int /*index*/)
410 {
411         findText(findLE->text(), true);
412 }
413
414
415 void GuiCitation::on_entriesCO_currentIndexChanged(int /*index*/)
416 {
417         findText(findLE->text(), true);
418 }
419
420
421 void GuiCitation::on_findLE_textChanged(const QString & text)
422 {
423         clearPB->setDisabled(text.isEmpty());
424         if (text.isEmpty())
425                 findLE->setFocus();
426         findText(text);
427 }
428
429
430 void GuiCitation::on_caseCB_stateChanged(int)
431 {
432         findText(findLE->text());
433 }
434
435
436 void GuiCitation::on_regexCB_stateChanged(int)
437 {
438         findText(findLE->text());
439 }
440
441
442 void GuiCitation::changed()
443 {
444         fillStyles();
445         setButtons();
446 }
447
448
449 void GuiCitation::apply(int const choice, bool const full, bool const force,
450         QString before, QString after)
451 {
452         if (cited_keys_.isEmpty())
453                 return;
454
455         vector<biblio::CiteStyle> const & styles = citeStyles_;
456
457         string const command =
458                 biblio::CitationStyle(styles[choice], full, force)
459                 .asLatexStr();
460
461         params_.setCmdName(command);
462         params_["key"] = qstring_to_ucs4(cited_keys_.join(","));
463         params_["before"] = qstring_to_ucs4(before);
464         params_["after"] = qstring_to_ucs4(after);
465         dispatchParams();
466 }
467
468
469 void GuiCitation::clearSelection()
470 {
471         cited_keys_.clear();
472         selected_model_.setStringList(cited_keys_);
473 }
474
475
476 QString GuiCitation::textBefore()
477 {
478         return toqstr(params_["before"]);
479 }
480
481
482 QString GuiCitation::textAfter()
483 {
484         return toqstr(params_["after"]);
485 }
486
487
488 void GuiCitation::init()
489 {
490         // Make the list of all available bibliography keys
491         all_keys_ = to_qstring_list(availableKeys());
492         available_model_.setStringList(all_keys_);
493
494         // Ditto for the keys cited in this inset
495         QString str = toqstr(params_["key"]);
496         if (str.isEmpty())
497                 cited_keys_.clear();
498         else
499                 cited_keys_ = str.split(",");
500         selected_model_.setStringList(cited_keys_);
501 }
502
503
504 void GuiCitation::findKey(QString const & str, bool only_keys,
505         docstring field, docstring entryType,
506         bool case_sensitive, bool reg_exp, bool reset)
507 {
508         // Used for optimisation: store last searched string.
509         static QString last_searched_string;
510         // Used to disable the above optimisation.
511         static bool last_case_sensitive;
512         static bool last_reg_exp;
513         // Reset last_searched_string in case of changed option.
514         if (last_case_sensitive != case_sensitive
515                 || last_reg_exp != reg_exp) {
516                         LYXERR(Debug::GUI, "GuiCitation::findKey: optimisation disabled!");
517                 last_searched_string.clear();
518         }
519         // save option for next search.
520         last_case_sensitive = case_sensitive;
521         last_reg_exp = reg_exp;
522
523         Qt::CaseSensitivity qtcase = case_sensitive?
524                         Qt::CaseSensitive: Qt::CaseInsensitive;
525         QStringList keys;
526         // If new string (str) contains the last searched one...
527         if (!reset &&
528                 !last_searched_string.isEmpty() &&
529                 str.size() > 1 &&
530                 str.contains(last_searched_string, qtcase))
531                 // ... then only search within already found list.
532                 keys = available_model_.stringList();
533         else
534                 // ... else search all keys.
535                 keys = all_keys_;
536         // save searched string for next search.
537         last_searched_string = str;
538
539         QStringList result;
540         
541         // First, filter by entryType, which will be faster than 
542         // what follows, so we may get to do that on less.
543         vector<docstring> keyVector = to_docstring_vector(keys);
544         filterByEntryType(keyVector, entryType);
545         
546         if (str.isEmpty())
547                 result = to_qstring_list(keyVector);
548         else
549                 result = to_qstring_list(searchKeys(keyVector, only_keys, 
550                         qstring_to_ucs4(str), field, case_sensitive, reg_exp));
551         
552         available_model_.setStringList(result);
553 }
554
555
556 QStringList GuiCitation::getFieldsAsQStringList()
557 {
558         return to_qstring_list(availableFields());
559 }
560
561
562 QStringList GuiCitation::getEntriesAsQStringList()
563 {
564         return to_qstring_list(availableEntries());
565 }
566
567
568 QStringList GuiCitation::citationStyles(int sel)
569 {
570         docstring const key = qstring_to_ucs4(cited_keys_[sel]);
571         return to_qstring_list(bibkeysInfo_.getCiteStrings(key, buffer()));
572 }
573
574
575 QString GuiCitation::getKeyInfo(QString const & sel)
576 {
577         return toqstr(getInfo(qstring_to_ucs4(sel)));
578 }
579
580
581 void GuiCitation::setCitedKeys() 
582 {
583         cited_keys_ = selected_model_.stringList();
584 }
585
586
587 bool GuiCitation::initialiseParams(string const & data)
588 {
589         InsetCommandMailer::string2params(lfun_name_, data, params_);
590
591         biblio::CiteEngine const engine = buffer().params().getEngine();
592
593         bool use_styles = engine != biblio::ENGINE_BASIC;
594
595         bibkeysInfo_.fillWithBibKeys(&buffer());
596         
597         if (citeStyles_.empty())
598                 citeStyles_ = biblio::getCiteStyles(engine);
599         else {
600                 if ((use_styles && citeStyles_.size() == 1) ||
601                     (!use_styles && citeStyles_.size() != 1))
602                         citeStyles_ = biblio::getCiteStyles(engine);
603         }
604
605         return true;
606 }
607
608
609 void GuiCitation::clearParams()
610 {
611         params_.clear();
612         bibkeysInfo_.clear();
613 }
614
615
616 vector<docstring> const GuiCitation::availableKeys() const
617 {
618         return bibkeysInfo_.getKeys();
619 }
620
621
622 vector<docstring> const GuiCitation::availableFields() const
623 {
624         return bibkeysInfo_.getFields();
625 }
626
627
628 vector<docstring> const GuiCitation::availableEntries() const
629 {
630         return bibkeysInfo_.getEntries();
631 }
632
633
634 void GuiCitation::filterByEntryType(
635         vector<docstring> & keyVector, docstring entryType) 
636 {
637         if (entryType.empty())
638                 return;
639         
640         vector<docstring>::iterator it = keyVector.begin();
641         vector<docstring>::iterator end = keyVector.end();
642         
643         vector<docstring> result;
644         for (; it != end; ++it) {
645                 docstring const key = *it;
646                 BiblioInfo::const_iterator cit = bibkeysInfo_.find(key);
647                 if (cit == bibkeysInfo_.end())
648                         continue;
649                 if (cit->second.entryType == entryType)
650                         result.push_back(key);
651         }
652         keyVector = result;
653 }
654
655
656 biblio::CiteEngine GuiCitation::getEngine() const
657 {
658         return buffer().params().getEngine();
659 }
660
661
662 docstring GuiCitation::getInfo(docstring const & key) const
663 {
664         if (bibkeysInfo_.empty())
665                 return docstring();
666
667         return bibkeysInfo_.getInfo(key);
668 }
669
670
671 // Escape special chars.
672 // All characters are literals except: '.|*?+(){}[]^$\'
673 // These characters are literals when preceded by a "\", which is done here
674 // @todo: This function should be moved to support, and then the test in tests
675 //        should be moved there as well.
676 static docstring escape_special_chars(docstring const & expr)
677 {
678         // Search for all chars '.|*?+(){}[^$]\'
679         // Note that '[' and '\' must be escaped.
680         // This is a limitation of boost::regex, but all other chars in BREs
681         // are assumed literal.
682         static const boost::regex reg("[].|*?+(){}^$\\[\\\\]");
683
684         // $& is a perl-like expression that expands to all
685         // of the current match
686         // The '$' must be prefixed with the escape character '\' for
687         // boost to treat it as a literal.
688         // Thus, to prefix a matched expression with '\', we use:
689         // FIXME: UNICODE
690         return from_utf8(boost::regex_replace(to_utf8(expr), reg, "\\\\$&"));
691 }
692
693
694 vector<docstring> GuiCitation::searchKeys(
695         vector<docstring> const & keys_to_search, bool only_keys,
696         docstring const & search_expression, docstring field,
697         bool case_sensitive, bool regex)
698 {
699         vector<docstring> foundKeys;
700
701         docstring expr = support::trim(search_expression);
702         if (expr.empty())
703                 return foundKeys;
704
705         if (!regex)
706                 // We must escape special chars in the search_expr so that
707                 // it is treated as a simple string by boost::regex.
708                 expr = escape_special_chars(expr);
709
710         boost::regex reg_exp(to_utf8(expr), case_sensitive ?
711                 boost::regex_constants::normal : boost::regex_constants::icase);
712
713         vector<docstring>::const_iterator it = keys_to_search.begin();
714         vector<docstring>::const_iterator end = keys_to_search.end();
715         for (; it != end; ++it ) {
716                 BiblioInfo::const_iterator info = bibkeysInfo_.find(*it);
717                 if (info == bibkeysInfo_.end())
718                         continue;
719                 
720                 BibTeXInfo const & kvm = info->second;
721                 string data;
722                 if (only_keys)
723                         data = to_utf8(*it);
724                 else if (field.empty())
725                         data = to_utf8(*it) + ' ' + to_utf8(kvm.allData);
726                 else if (kvm.hasField(field))
727                         data = to_utf8(kvm.getValueForField(field));
728                 
729                 if (data.empty())
730                         continue;
731
732                 try {
733                         if (boost::regex_search(data, reg_exp))
734                                 foundKeys.push_back(*it);
735                 }
736                 catch (boost::regex_error &) {
737                         return vector<docstring>();
738                 }
739         }
740         return foundKeys;
741 }
742
743
744 Dialog * createGuiCitation(GuiView & lv) { return new GuiCitation(lv); }
745
746
747 } // namespace frontend
748 } // namespace lyx
749
750 #include "GuiCitation_moc.cpp"
751