]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
7b355ef9bb6dfc5bdcbdeca4ecc52cdd94fea784
[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 "frontend_helpers.h"
21 #include "qt_helpers.h"
22
23 #include "support/lstrings.h"
24 #include "support/docstring.h"
25
26 #include <vector>
27 #include <string>
28
29 #include <QCloseEvent>
30
31 #undef KeyPress
32
33 #include <algorithm>
34 #include <string>
35 #include <vector>
36
37 using std::vector;
38 using std::string;
39
40
41 namespace lyx {
42 namespace frontend {
43
44 template<typename String>
45 static QStringList to_qstring_list(vector<String> const & v)
46 {
47         QStringList qlist;
48
49         for (size_t i = 0; i != v.size(); ++i) {
50                 if (v[i].empty())
51                         continue;
52                 qlist.append(lyx::toqstr(v[i]));
53         }
54         return qlist;
55 }
56
57
58 static vector<lyx::docstring> to_docstring_vector(QStringList const & qlist)
59 {
60         vector<lyx::docstring> v;
61         for (int i = 0; i != qlist.size(); ++i) {
62                 if (qlist[i].isEmpty())
63                         continue;
64                 v.push_back(lyx::qstring_to_ucs4(qlist[i]));
65         }
66         return v;
67 }
68
69
70 GuiCitationDialog::GuiCitationDialog(LyXView & lv)
71         : GuiDialog(lv, "citation")
72 {
73         setupUi(this);
74         setViewTitle(_("Citation"));
75         setController(new ControlCitation(*this));
76
77         connect(citationStyleCO, SIGNAL(activated(int)),
78                 this, SLOT(changed()));
79         connect(fulllistCB, SIGNAL(clicked()),
80                 this, SLOT(changed()));
81         connect(forceuppercaseCB, SIGNAL(clicked()),
82                 this, SLOT(changed()));
83         connect(textBeforeED, SIGNAL(textChanged(const QString&)),
84                 this, SLOT(changed()));
85         connect(textAfterED, SIGNAL(textChanged(const QString&)),
86                 this, SLOT(changed()));
87         connect(clearPB, SIGNAL(clicked()),
88                 findLE, SLOT(clear()));
89         connect(this, SIGNAL(rejected()), this, SLOT(cleanUp()));
90
91         selectionManager = 
92                 new GuiSelectionManager(availableLV, selectedLV, 
93                         addPB, deletePB, upPB, downPB, available(), selected());
94         connect(selectionManager, SIGNAL(selectionChanged()),
95                 this, SLOT(setCitedKeys()));
96         connect(selectionManager, SIGNAL(updateHook()),
97                 this, SLOT(updateDialog()));
98         connect(selectionManager, SIGNAL(okHook()),
99                                         this, SLOT(on_okPB_clicked()));
100
101         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
102 }
103
104
105 ControlCitation & GuiCitationDialog::controller() const
106 {
107         return static_cast<ControlCitation &>(Dialog::controller());
108 }
109
110
111 void GuiCitationDialog::cleanUp() 
112 {
113         clearSelection();
114         controller().clearParams();
115         close();
116 }
117
118
119 void GuiCitationDialog::closeEvent(QCloseEvent * e)
120 {
121         clearSelection();
122         controller().clearParams();
123         e->accept();
124 }
125
126
127 void GuiCitationDialog::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 GuiCitationDialog::hideView()
142 {
143         controller().clearParams();
144         accept();
145 }
146
147
148 void GuiCitationDialog::showView()
149 {
150         findLE->clear();
151         availableLV->setFocus();
152         QDialog::show();
153         raise();
154         activateWindow();
155 }
156
157
158 bool GuiCitationDialog::isVisibleView() const
159 {
160         return QDialog::isVisible();
161 }
162
163
164 void GuiCitationDialog::on_okPB_clicked()
165 {
166         applyView();
167         clearSelection();
168         hideView();
169 }
170
171
172 void GuiCitationDialog::on_cancelPB_clicked()
173 {
174         clearSelection();
175         hideView();
176 }
177
178
179 void GuiCitationDialog::on_applyPB_clicked()
180 {
181         applyView();
182 }
183
184
185 void GuiCitationDialog::on_restorePB_clicked()
186 {
187         init();
188         updateView();
189 }
190
191
192 void GuiCitationDialog::updateView()
193 {
194         fillFields();
195         fillEntries();
196         updateDialog();
197 }
198
199
200 // The main point of separating this out is that the fill*() methods
201 // called in update() do not need to be called for INTERNAL updates,
202 // such as when addPB is pressed, as the list of fields, entries, etc,
203 // will not have changed. At the moment, however, the division between
204 // fillStyles() and updateStyles() doesn't lend itself to dividing the
205 // two methods, though they should be divisible.
206 void GuiCitationDialog::updateDialog()
207 {
208         if (selectionManager->selectedFocused()) { 
209                 if (selectedLV->selectionModel()->selectedIndexes().isEmpty())
210                         updateInfo(availableLV->currentIndex());
211                 else
212                         updateInfo(selectedLV->currentIndex());
213         } else {
214                 if (availableLV->selectionModel()->selectedIndexes().isEmpty())
215                         updateInfo(QModelIndex());
216                 else
217                         updateInfo(availableLV->currentIndex());
218         }
219         setButtons();
220
221         textBeforeED->setText(textBefore());
222         textAfterED->setText(textAfter());
223         fillStyles();
224         updateStyle();
225 }
226
227
228 void GuiCitationDialog::updateStyle()
229 {
230         biblio::CiteEngine const engine = controller().getEngine();
231         bool const natbib_engine =
232                 engine == biblio::ENGINE_NATBIB_AUTHORYEAR ||
233                 engine == biblio::ENGINE_NATBIB_NUMERICAL;
234         bool const basic_engine = engine == biblio::ENGINE_BASIC;
235
236         bool const haveSelection = 
237                 selectedLV->model()->rowCount() > 0;
238         fulllistCB->setEnabled(natbib_engine && haveSelection);
239         forceuppercaseCB->setEnabled(natbib_engine && haveSelection);
240         textBeforeED->setEnabled(!basic_engine && haveSelection);
241         textBeforeLA->setEnabled(!basic_engine && haveSelection);
242         textAfterED->setEnabled(haveSelection);
243         textAfterLA->setEnabled(haveSelection);
244         citationStyleCO->setEnabled(!basic_engine && haveSelection);
245         citationStyleLA->setEnabled(!basic_engine && haveSelection);
246
247         string const & command = controller().params().getCmdName();
248
249         // Find the style of the citekeys
250         vector<biblio::CiteStyle> const & styles =
251                 ControlCitation::getCiteStyles();
252         biblio::CitationStyle const cs(command);
253
254         vector<biblio::CiteStyle>::const_iterator cit =
255                 std::find(styles.begin(), styles.end(), cs.style);
256
257         // restore the latest natbib style
258         if (style_ >= 0 && style_ < citationStyleCO->count())
259                 citationStyleCO->setCurrentIndex(style_);
260         else
261                 citationStyleCO->setCurrentIndex(0);
262
263         if (cit != styles.end()) {
264                 int const i = int(cit - styles.begin());
265                 citationStyleCO->setCurrentIndex(i);
266                 fulllistCB->setChecked(cs.full);
267                 forceuppercaseCB->setChecked(cs.forceUCase);
268         } else {
269                 fulllistCB->setChecked(false);
270                 forceuppercaseCB->setChecked(false);
271         }
272 }
273
274
275 //This one needs to be called whenever citationStyleCO needs
276 //to be updated---and this would be on anything that changes the
277 //selection in selectedLV, or on a general update.
278 void GuiCitationDialog::fillStyles()
279 {
280         int const oldIndex = citationStyleCO->currentIndex();
281
282         citationStyleCO->clear();
283
284         QStringList selected_keys = selected()->stringList();
285         if (selected_keys.empty()) {
286                 citationStyleCO->setEnabled(false);
287                 citationStyleLA->setEnabled(false);
288                 return;
289         }
290
291         int curr = selectedLV->model()->rowCount() - 1;
292         if (curr < 0)
293                 return;
294
295         if (!selectedLV->selectionModel()->selectedIndexes().empty())
296                 curr = selectedLV->selectionModel()->selectedIndexes()[0].row();
297
298         QStringList sty = citationStyles(curr);
299
300         bool const basic_engine =
301                         (controller().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 GuiCitationDialog::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 GuiCitationDialog::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 GuiCitationDialog::isSelected(const QModelIndex & idx)
346 {
347         QString const str = idx.data().toString();
348         return selected()->stringList().contains(str);
349 }
350
351
352 void GuiCitationDialog::setButtons()
353 {
354         selectionManager->updateView();
355         int const srows = selectedLV->model()->rowCount();
356         applyPB->setEnabled(srows > 0);
357         okPB->setEnabled(srows > 0);
358 }
359
360
361 void GuiCitationDialog::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 GuiCitationDialog::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 = controller().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 = controller().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 GuiCitationDialog::on_fieldsCO_currentIndexChanged(int /*index*/)
410 {
411         findText(findLE->text(), true);
412 }
413
414
415 void GuiCitationDialog::on_entriesCO_currentIndexChanged(int /*index*/)
416 {
417         findText(findLE->text(), true);
418 }
419
420
421 void GuiCitationDialog::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 GuiCitationDialog::on_caseCB_stateChanged(int)
431 {
432         findText(findLE->text());
433 }
434
435
436 void GuiCitationDialog::on_regexCB_stateChanged(int)
437 {
438         findText(findLE->text());
439 }
440
441
442 void GuiCitationDialog::changed()
443 {
444         fillStyles();
445         setButtons();
446 }
447
448
449 void GuiCitationDialog::apply(int const choice,
450         bool const full, bool const force,
451         QString before, QString after)
452 {
453         if (cited_keys_.isEmpty())
454                 return;
455
456         vector<biblio::CiteStyle> const & styles =
457                 ControlCitation::getCiteStyles();
458
459         string const command =
460                 biblio::CitationStyle(styles[choice], full, force)
461                 .asLatexStr();
462
463         controller().params().setCmdName(command);
464         controller().params()["key"] = qstring_to_ucs4(cited_keys_.join(","));
465         controller().params()["before"] = qstring_to_ucs4(before);
466         controller().params()["after"] = qstring_to_ucs4(after);
467         controller().dispatchParams();
468 }
469
470
471 void GuiCitationDialog::clearSelection()
472 {
473         cited_keys_.clear();
474         selected_model_.setStringList(cited_keys_);
475 }
476
477
478 QString GuiCitationDialog::textBefore()
479 {
480         return toqstr(controller().params()["before"]);
481 }
482
483
484 QString GuiCitationDialog::textAfter()
485 {
486         return toqstr(controller().params()["after"]);
487 }
488
489
490 bool GuiCitationDialog::initialiseParams(std::string const & data)
491 {
492         if (!controller().initialiseParams(data))
493                 return false;
494         init();
495         return true;
496 }
497
498
499 void GuiCitationDialog::init()
500 {
501         // Make the list of all available bibliography keys
502         all_keys_ = to_qstring_list(controller().availableKeys());
503         available_model_.setStringList(all_keys_);
504
505         // Ditto for the keys cited in this inset
506         QString str = toqstr(controller().params()["key"]);
507         if (str.isEmpty())
508                 cited_keys_.clear();
509         else
510                 cited_keys_ = str.split(",");
511         selected_model_.setStringList(cited_keys_);
512 }
513
514
515 void GuiCitationDialog::findKey(QString const & str, bool only_keys,
516         docstring field, docstring entryType,
517         bool case_sensitive, bool reg_exp, bool reset)
518 {
519         // Used for optimisation: store last searched string.
520         static QString last_searched_string;
521         // Used to disable the above optimisation.
522         static bool last_case_sensitive;
523         static bool last_reg_exp;
524         // Reset last_searched_string in case of changed option.
525         if (last_case_sensitive != case_sensitive
526                 || last_reg_exp != reg_exp) {
527                         LYXERR(Debug::GUI) << "GuiCitation::findKey: optimisation disabled!" << std::endl;
528                 last_searched_string.clear();
529         }
530         // save option for next search.
531         last_case_sensitive = case_sensitive;
532         last_reg_exp = reg_exp;
533
534         Qt::CaseSensitivity qtcase = case_sensitive?
535                         Qt::CaseSensitive: Qt::CaseInsensitive;
536         QStringList keys;
537         // If new string (str) contains the last searched one...
538         if (!reset &&
539                 !last_searched_string.isEmpty() &&
540                 str.size() > 1 &&
541                 str.contains(last_searched_string, qtcase))
542                 // ... then only search within already found list.
543                 keys = available_model_.stringList();
544         else
545                 // ... else search all keys.
546                 keys = all_keys_;
547         // save searched string for next search.
548         last_searched_string = str;
549
550         QStringList result;
551         
552         // First, filter by entryType, which will be faster than 
553         // what follows, so we may get to do that on less.
554         vector<docstring> keyVector = to_docstring_vector(keys);
555         controller().filterByEntryType(keyVector, entryType);
556         
557         if (str.isEmpty())
558                 result = to_qstring_list(keyVector);
559         else
560                 result = to_qstring_list(controller().searchKeys(keyVector, only_keys, 
561                         qstring_to_ucs4(str), field, case_sensitive, reg_exp));
562         
563         available_model_.setStringList(result);
564 }
565
566
567 QStringList GuiCitationDialog::getFieldsAsQStringList()
568 {
569         return to_qstring_list(controller().availableFields());
570 }
571
572
573 QStringList GuiCitationDialog::getEntriesAsQStringList()
574 {
575         return to_qstring_list(controller().availableEntries());
576 }
577
578
579 QStringList GuiCitationDialog::citationStyles(int sel)
580 {
581         docstring const key = qstring_to_ucs4(cited_keys_[sel]);
582         return to_qstring_list(controller().getCiteStrings(key));
583 }
584
585
586 QString GuiCitationDialog::getKeyInfo(QString const & sel)
587 {
588         return toqstr(controller().getInfo(qstring_to_ucs4(sel)));
589 }
590
591
592 void GuiCitationDialog::setCitedKeys() 
593 {
594         cited_keys_ = selected_model_.stringList();
595 }
596
597 } // namespace frontend
598 } // namespace lyx
599
600 #include "GuiCitation_moc.cpp"
601