]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
d8835c56ec931d34257e708c19ae4b0d03909ba2
[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         setWindowTitle(toqstr("LyX: " + getViewTitle()));
78
79         connect(citationStyleCO, SIGNAL(activated(int)),
80                 this, SLOT(changed()));
81         connect(fulllistCB, SIGNAL(clicked()),
82                 this, SLOT(changed()));
83         connect(forceuppercaseCB, SIGNAL(clicked()),
84                 this, SLOT(changed()));
85         connect(textBeforeED, SIGNAL(textChanged(const QString&)),
86                 this, SLOT(changed()));
87         connect(textAfterED, SIGNAL(textChanged(const QString&)),
88                 this, SLOT(changed()));
89         connect(clearPB, SIGNAL(clicked()),
90                 findLE, SLOT(clear()));
91         connect(this, SIGNAL(rejected()), this, SLOT(cleanUp()));
92
93         selectionManager = 
94                 new GuiSelectionManager(availableLV, selectedLV, 
95                         addPB, deletePB, upPB, downPB, available(), selected());
96         connect(selectionManager, SIGNAL(selectionChanged()),
97                 this, SLOT(setCitedKeys()));
98         connect(selectionManager, SIGNAL(updateHook()),
99                 this, SLOT(updateDialog()));
100         connect(selectionManager, SIGNAL(okHook()),
101                                         this, SLOT(on_okPB_clicked()));
102
103         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
104 }
105
106
107 ControlCitation & GuiCitationDialog::controller() const
108 {
109         return static_cast<ControlCitation &>(Dialog::controller());
110 }
111
112
113 void GuiCitationDialog::cleanUp() 
114 {
115         clearSelection();
116         controller().clearParams();
117         close();
118 }
119
120
121 void GuiCitationDialog::closeEvent(QCloseEvent * e)
122 {
123         clearSelection();
124         controller().clearParams();
125         e->accept();
126 }
127
128
129 void GuiCitationDialog::applyView()
130 {
131         int  const choice = std::max(0, citationStyleCO->currentIndex());
132         style_ = choice;
133         bool const full  = fulllistCB->isChecked();
134         bool const force = forceuppercaseCB->isChecked();
135
136         QString const before = textBeforeED->text();
137         QString const after = textAfterED->text();
138
139         apply(choice, full, force, before, after);
140 }
141
142
143 void GuiCitationDialog::hideView()
144 {
145         controller().clearParams();
146         accept();
147 }
148
149
150 void GuiCitationDialog::showView()
151 {
152         findLE->clear();
153         availableLV->setFocus();
154         QDialog::show();
155         raise();
156         activateWindow();
157 }
158
159
160 bool GuiCitationDialog::isVisibleView() const
161 {
162         return QDialog::isVisible();
163 }
164
165
166 void GuiCitationDialog::on_okPB_clicked()
167 {
168         applyView();
169         clearSelection();
170         hideView();
171 }
172
173
174 void GuiCitationDialog::on_cancelPB_clicked()
175 {
176         clearSelection();
177         hideView();
178 }
179
180
181 void GuiCitationDialog::on_applyPB_clicked()
182 {
183         applyView();
184 }
185
186
187 void GuiCitationDialog::on_restorePB_clicked()
188 {
189         init();
190         updateView();
191 }
192
193
194 void GuiCitationDialog::updateView()
195 {
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 GuiCitationDialog::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 GuiCitationDialog::updateStyle()
231 {
232         biblio::CiteEngine const engine = controller().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 = controller().params().getCmdName();
250
251         // Find the style of the citekeys
252         vector<biblio::CiteStyle> const & styles =
253                 ControlCitation::getCiteStyles();
254         biblio::CitationStyle const cs(command);
255
256         vector<biblio::CiteStyle>::const_iterator cit =
257                 std::find(styles.begin(), styles.end(), cs.style);
258
259         // restore the latest natbib style
260         if (style_ >= 0 && style_ < citationStyleCO->count())
261                 citationStyleCO->setCurrentIndex(style_);
262         else
263                 citationStyleCO->setCurrentIndex(0);
264
265         if (cit != styles.end()) {
266                 int const i = int(cit - styles.begin());
267                 citationStyleCO->setCurrentIndex(i);
268                 fulllistCB->setChecked(cs.full);
269                 forceuppercaseCB->setChecked(cs.forceUCase);
270         } else {
271                 fulllistCB->setChecked(false);
272                 forceuppercaseCB->setChecked(false);
273         }
274 }
275
276
277 //This one needs to be called whenever citationStyleCO needs
278 //to be updated---and this would be on anything that changes the
279 //selection in selectedLV, or on a general update.
280 void GuiCitationDialog::fillStyles()
281 {
282         int const oldIndex = citationStyleCO->currentIndex();
283
284         citationStyleCO->clear();
285
286         QStringList selected_keys = selected()->stringList();
287         if (selected_keys.empty()) {
288                 citationStyleCO->setEnabled(false);
289                 citationStyleLA->setEnabled(false);
290                 return;
291         }
292
293         int curr = selectedLV->model()->rowCount() - 1;
294         if (curr < 0)
295                 return;
296
297         if (!selectedLV->selectionModel()->selectedIndexes().empty())
298                 curr = selectedLV->selectionModel()->selectedIndexes()[0].row();
299
300         QStringList sty = citationStyles(curr);
301
302         bool const basic_engine =
303                         (controller().getEngine() == biblio::ENGINE_BASIC);
304
305         citationStyleCO->setEnabled(!sty.isEmpty() && !basic_engine);
306         citationStyleLA->setEnabled(!sty.isEmpty() && !basic_engine);
307
308         if (sty.isEmpty() || basic_engine)
309                 return;
310
311         citationStyleCO->insertItems(0, sty);
312
313         if (oldIndex != -1 && oldIndex < citationStyleCO->count())
314                 citationStyleCO->setCurrentIndex(oldIndex);
315 }
316
317
318 void GuiCitationDialog::fillFields()
319 {
320         fieldsCO->blockSignals(true);
321         int const oldIndex = fieldsCO->currentIndex();
322         fieldsCO->clear();
323         QStringList const & fields = getFieldsAsQStringList();
324         fieldsCO->insertItem(0, qt_("All Fields"));
325         fieldsCO->insertItem(1, qt_("Keys"));
326         fieldsCO->insertItems(2, fields);
327         if (oldIndex != -1 && oldIndex < fieldsCO->count())
328                 fieldsCO->setCurrentIndex(oldIndex);
329         fieldsCO->blockSignals(false);
330 }
331
332
333 void GuiCitationDialog::fillEntries()
334 {
335         entriesCO->blockSignals(true);
336         int const oldIndex = entriesCO->currentIndex();
337         entriesCO->clear();
338         QStringList const & entries = getEntriesAsQStringList();
339         entriesCO->insertItem(0, qt_("All Entry Types"));
340         entriesCO->insertItems(1, entries);
341         if (oldIndex != -1 && oldIndex < entriesCO->count())
342                 entriesCO->setCurrentIndex(oldIndex);
343         entriesCO->blockSignals(false);
344 }
345
346
347 bool GuiCitationDialog::isSelected(const QModelIndex & idx)
348 {
349         QString const str = idx.data().toString();
350         return selected()->stringList().contains(str);
351 }
352
353
354 void GuiCitationDialog::setButtons()
355 {
356         selectionManager->updateView();
357         int const srows = selectedLV->model()->rowCount();
358         applyPB->setEnabled(srows > 0);
359         okPB->setEnabled(srows > 0);
360 }
361
362
363 void GuiCitationDialog::updateInfo(QModelIndex const & idx)
364 {
365         if (idx.isValid()) {
366                 QString const keytxt = getKeyInfo(idx.data().toString());
367                 infoML->document()->setPlainText(keytxt);
368         } else
369                 infoML->document()->clear();
370 }
371
372
373 void GuiCitationDialog::findText(QString const & text, bool reset)
374 {
375         //"All Fields" and "Keys" are the first two
376         int index = fieldsCO->currentIndex() - 2; 
377         vector<docstring> const & fields = controller().availableFields();
378         docstring field;
379         
380         if (index <= -1 || index >= int(fields.size()))
381                 //either "All Fields" or "Keys" or an invalid value
382                 field = from_ascii("");
383         else
384                 field = fields[index];
385         
386         //Was it "Keys"?
387         bool const onlyKeys = index == -1;
388         
389         //"All Entry Types" is first.
390         index = entriesCO->currentIndex() - 1; 
391         vector<docstring> const & entries = controller().availableEntries();
392         docstring entryType;
393         if (index < 0 || index >= int(entries.size()))
394                 entryType = from_ascii("");
395         else 
396                 entryType = entries[index];
397         
398         bool const case_sentitive = caseCB->checkState();
399         bool const reg_exp = regexCB->checkState();
400         findKey(text, onlyKeys, field, entryType, 
401                        case_sentitive, reg_exp, reset);
402         //FIXME
403         //It'd be nice to save and restore the current selection in 
404         //availableLV. Currently, we get an automatic reset, since the
405         //model is reset.
406         
407         updateDialog();
408 }
409
410
411 void GuiCitationDialog::on_fieldsCO_currentIndexChanged(int /*index*/)
412 {
413         findText(findLE->text(), true);
414 }
415
416
417 void GuiCitationDialog::on_entriesCO_currentIndexChanged(int /*index*/)
418 {
419         findText(findLE->text(), true);
420 }
421
422
423 void GuiCitationDialog::on_findLE_textChanged(const QString & text)
424 {
425         clearPB->setDisabled(text.isEmpty());
426         if (text.isEmpty())
427                 findLE->setFocus();
428         findText(text);
429 }
430
431
432 void GuiCitationDialog::on_caseCB_stateChanged(int)
433 {
434         findText(findLE->text());
435 }
436
437
438 void GuiCitationDialog::on_regexCB_stateChanged(int)
439 {
440         findText(findLE->text());
441 }
442
443
444 void GuiCitationDialog::changed()
445 {
446         fillStyles();
447         setButtons();
448 }
449
450
451 void GuiCitationDialog::apply(int const choice,
452         bool const full, bool const force,
453         QString before, QString after)
454 {
455         if (cited_keys_.isEmpty())
456                 return;
457
458         vector<biblio::CiteStyle> const & styles =
459                 ControlCitation::getCiteStyles();
460
461         string const command =
462                 biblio::CitationStyle(styles[choice], full, force)
463                 .asLatexStr();
464
465         controller().params().setCmdName(command);
466         controller().params()["key"] = qstring_to_ucs4(cited_keys_.join(","));
467         controller().params()["before"] = qstring_to_ucs4(before);
468         controller().params()["after"] = qstring_to_ucs4(after);
469         controller().dispatchParams();
470 }
471
472
473 void GuiCitationDialog::clearSelection()
474 {
475         cited_keys_.clear();
476         selected_model_.setStringList(cited_keys_);
477 }
478
479
480 QString GuiCitationDialog::textBefore()
481 {
482         return toqstr(controller().params()["before"]);
483 }
484
485
486 QString GuiCitationDialog::textAfter()
487 {
488         return toqstr(controller().params()["after"]);
489 }
490
491
492 bool GuiCitationDialog::initialiseParams(std::string const & data)
493 {
494         if (!controller().initialiseParams(data))
495                 return false;
496         init();
497         return true;
498 }
499
500
501 void GuiCitationDialog::init()
502 {
503         // Make the list of all available bibliography keys
504         all_keys_ = to_qstring_list(controller().availableKeys());
505         available_model_.setStringList(all_keys_);
506
507         // Ditto for the keys cited in this inset
508         QString str = toqstr(controller().params()["key"]);
509         if (str.isEmpty())
510                 cited_keys_.clear();
511         else
512                 cited_keys_ = str.split(",");
513         selected_model_.setStringList(cited_keys_);
514 }
515
516
517 void GuiCitationDialog::findKey(QString const & str, bool only_keys,
518         docstring field, docstring entryType,
519         bool case_sensitive, bool reg_exp, bool reset)
520 {
521         // Used for optimisation: store last searched string.
522         static QString last_searched_string;
523         // Used to disable the above optimisation.
524         static bool last_case_sensitive;
525         static bool last_reg_exp;
526         // Reset last_searched_string in case of changed option.
527         if (last_case_sensitive != case_sensitive
528                 || last_reg_exp != reg_exp) {
529                         LYXERR(Debug::GUI) << "GuiCitation::findKey: optimisation disabled!" << std::endl;
530                 last_searched_string.clear();
531         }
532         // save option for next search.
533         last_case_sensitive = case_sensitive;
534         last_reg_exp = reg_exp;
535
536         Qt::CaseSensitivity qtcase = case_sensitive?
537                         Qt::CaseSensitive: Qt::CaseInsensitive;
538         QStringList keys;
539         // If new string (str) contains the last searched one...
540         if (!reset &&
541                 !last_searched_string.isEmpty() &&
542                 str.size() > 1 &&
543                 str.contains(last_searched_string, qtcase))
544                 // ... then only search within already found list.
545                 keys = available_model_.stringList();
546         else
547                 // ... else search all keys.
548                 keys = all_keys_;
549         // save searched string for next search.
550         last_searched_string = str;
551
552         QStringList result;
553         
554         // First, filter by entryType, which will be faster than 
555         // what follows, so we may get to do that on less.
556         vector<docstring> keyVector = to_docstring_vector(keys);
557         controller().filterByEntryType(keyVector, entryType);
558         
559         if (str.isEmpty())
560                 result = to_qstring_list(keyVector);
561         else
562                 result = to_qstring_list(controller().searchKeys(keyVector, only_keys, 
563                         qstring_to_ucs4(str), field, case_sensitive, reg_exp));
564         
565         available_model_.setStringList(result);
566 }
567
568
569 QStringList GuiCitationDialog::getFieldsAsQStringList()
570 {
571         return to_qstring_list(controller().availableFields());
572 }
573
574
575 QStringList GuiCitationDialog::getEntriesAsQStringList()
576 {
577         return to_qstring_list(controller().availableEntries());
578 }
579
580
581 QStringList GuiCitationDialog::citationStyles(int sel)
582 {
583         docstring const key = qstring_to_ucs4(cited_keys_[sel]);
584         return to_qstring_list(controller().getCiteStrings(key));
585 }
586
587
588 QString GuiCitationDialog::getKeyInfo(QString const & sel)
589 {
590         return toqstr(controller().getInfo(qstring_to_ucs4(sel)));
591 }
592
593
594 void GuiCitationDialog::setCitedKeys() 
595 {
596         cited_keys_ = selected_model_.stringList();
597 }
598
599 } // namespace frontend
600 } // namespace lyx
601
602 #include "GuiCitation_moc.cpp"
603