]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCitation.cpp
This is the last of a series of patches that merges the layout modules development...
[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()
106 {
107         return static_cast<ControlCitation &>(GuiDialog::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         init();
151         findLE->clear();
152         availableLV->setFocus();
153         QDialog::show();
154         raise();
155         activateWindow();
156 }
157
158
159 bool GuiCitationDialog::isVisibleView() const
160 {
161         return QDialog::isVisible();
162 }
163
164
165 void GuiCitationDialog::on_okPB_clicked()
166 {
167         applyView();
168         clearSelection();
169         hideView();
170 }
171
172
173 void GuiCitationDialog::on_cancelPB_clicked()
174 {
175         clearSelection();
176         hideView();
177 }
178
179
180 void GuiCitationDialog::on_applyPB_clicked()
181 {
182         applyView();
183 }
184
185
186 void GuiCitationDialog::on_restorePB_clicked()
187 {
188         init();
189         updateView();
190 }
191
192
193 void GuiCitationDialog::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 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->update();
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 void GuiCitationDialog::init()
493 {
494         // Make the list of all available bibliography keys
495         all_keys_ = to_qstring_list(controller().availableKeys());
496         available_model_.setStringList(all_keys_);
497
498         // Ditto for the keys cited in this inset
499         QString str = toqstr(controller().params()["key"]);
500         if (str.isEmpty())
501                 cited_keys_.clear();
502         else
503                 cited_keys_ = str.split(",");
504         selected_model_.setStringList(cited_keys_);
505 }
506
507
508 void GuiCitationDialog::findKey(QString const & str, bool only_keys,
509         docstring field, docstring entryType,
510         bool case_sensitive, bool reg_exp, bool reset)
511 {
512         // Used for optimisation: store last searched string.
513         static QString last_searched_string;
514         // Used to disable the above optimisation.
515         static bool last_case_sensitive;
516         static bool last_reg_exp;
517         // Reset last_searched_string in case of changed option.
518         if (last_case_sensitive != case_sensitive
519                 || last_reg_exp != reg_exp) {
520                         LYXERR(Debug::GUI) << "GuiCitation::findKey: optimisation disabled!" << std::endl;
521                 last_searched_string.clear();
522         }
523         // save option for next search.
524         last_case_sensitive = case_sensitive;
525         last_reg_exp = reg_exp;
526
527         Qt::CaseSensitivity qtcase = case_sensitive?
528                         Qt::CaseSensitive: Qt::CaseInsensitive;
529         QStringList keys;
530         // If new string (str) contains the last searched one...
531         if (!reset &&
532                 !last_searched_string.isEmpty() &&
533                 str.size() > 1 &&
534                 str.contains(last_searched_string, qtcase))
535                 // ... then only search within already found list.
536                 keys = available_model_.stringList();
537         else
538                 // ... else search all keys.
539                 keys = all_keys_;
540         // save searched string for next search.
541         last_searched_string = str;
542
543         QStringList result;
544         
545         // First, filter by entryType, which will be faster than 
546         // what follows, so we may get to do that on less.
547         vector<docstring> keyVector = to_docstring_vector(keys);
548         controller().filterByEntryType(keyVector, entryType);
549         
550         if (str.isEmpty())
551                 result = to_qstring_list(keyVector);
552         else
553                 result = to_qstring_list(controller().searchKeys(keyVector, only_keys, 
554                         qstring_to_ucs4(str), field, case_sensitive, reg_exp));
555         
556         available_model_.setStringList(result);
557 }
558
559
560 QStringList GuiCitationDialog::getFieldsAsQStringList()
561 {
562         return to_qstring_list(controller().availableFields());
563 }
564
565
566 QStringList GuiCitationDialog::getEntriesAsQStringList()
567 {
568         return to_qstring_list(controller().availableEntries());
569 }
570
571
572 QStringList GuiCitationDialog::citationStyles(int sel)
573 {
574         docstring const key = qstring_to_ucs4(cited_keys_[sel]);
575         return to_qstring_list(controller().getCiteStrings(key));
576 }
577
578
579 QString GuiCitationDialog::getKeyInfo(QString const & sel)
580 {
581         return toqstr(controller().getInfo(qstring_to_ucs4(sel)));
582 }
583
584
585 void GuiCitationDialog::setCitedKeys() 
586 {
587         cited_keys_ = selected_model_.stringList();
588 }
589
590 } // namespace frontend
591 } // namespace lyx
592
593 #include "GuiCitation_moc.cpp"
594