]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiLyXFiles.cpp
GuiLyXFiles: prefill language combo
[lyx.git] / src / frontends / qt4 / GuiLyXFiles.cpp
1 /**
2  * \file GuiLyXFiles.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jürgen Spitzmüller
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "GuiLyXFiles.h"
14 #include "GuiApplication.h"
15 #include "qt_helpers.h"
16
17 #include "FileDialog.h"
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "FuncRequest.h"
21 #include "Language.h"
22 #include "LyXRC.h"
23
24 #include "support/environment.h"
25 #include "support/filetools.h"
26 #include "support/gettext.h"
27 #include "support/lstrings.h"
28 #include "support/Messages.h"
29 #include "support/qstring_helpers.h"
30 #include "support/Package.h"
31
32 #include <QDirIterator>
33 #include <QTreeWidget>
34
35 using namespace std;
36 using namespace lyx::support;
37
38 namespace lyx {
39 namespace frontend {
40
41
42 void GuiLyXFiles::getFiles(QMap<QString, QString> & in, QString const type)
43 {
44         // We look for lyx files in the subdirectory dir of
45         //   1) user_lyxdir
46         //   2) build_lyxdir (if not empty)
47         //   3) system_lyxdir
48         // in this order. Files with a given sub-hierarchy will
49         // only be listed once.
50         // We also consider i18n subdirectories and store them separately.
51         QStringList dirs;
52         QStringList relpaths;
53
54         // The three locations to look at.
55         string const user = addPath(package().user_support().absFileName(), fromqstr(type));
56         string const build = addPath(package().build_support().absFileName(), fromqstr(type));
57         string const system = addPath(package().system_support().absFileName(), fromqstr(type));
58
59         available_languages_.insert(toqstr("en"), qt_("English"));
60
61         // Search in the base path
62         dirs << toqstr(user)
63              << toqstr(build)
64              << toqstr(system);
65
66         for (int i = 0; i < dirs.size(); ++i) {
67                 QString const dir = dirs.at(i);
68                 QDirIterator it(dir, QDir::Files, QDirIterator::Subdirectories);
69                 while (it.hasNext()) {
70                         QString fn(QFile(it.next()).fileName());
71                         if (!fn.endsWith(getSuffix()))
72                                 continue;
73                         QString relpath = toqstr(makeRelPath(qstring_to_ucs4(fn),
74                                                              qstring_to_ucs4(dir)));
75                         // <cat>/
76                         int s = relpath.indexOf('/', 0);
77                         QString cat = qt_("General");
78                         QString localization = "en";
79                         if (s != -1) {
80                                 // <cat>/<subcat>/
81                                 cat = relpath.left(s);
82                                 int sc = relpath.indexOf('/', s + 1);
83                                 QString const subcat = (sc == -1) ?
84                                                         QString() : relpath.mid(s + 1, sc - s - 1);
85                                 if (all_languages_.contains(cat)
86                                     && !all_languages_.contains(dir.right(dir.lastIndexOf('/')))) {
87                                         QMap<QString, QString>::const_iterator li = all_languages_.find(cat);
88                                         // Skip i18n dir, but add language to the combo
89                                         if (!available_languages_.contains(li.key()))
90                                                 available_languages_.insert(li.key(), li.value());
91                                         localization = cat;
92                                 }
93                                 if (!subcat.isEmpty())
94                                         cat += '/' + subcat;
95                         }
96                         if (!relpaths.contains(relpath)) {
97                                 relpaths.append(relpath);
98                                 if (localization == "en")
99                                         in.insert(relpath, cat);
100                                 else
101                                         // strip off lang/
102                                         relpath = relpath.mid(relpath.indexOf('/') + 1);
103                                 QMap<QString, QString> lm;
104                                 if (localizations_.contains(relpath))
105                                         lm = localizations_.find(relpath).value();
106                                 lm.insert(localization, fn);
107                                 localizations_.insert(relpath, lm);
108                         }
109                 }
110         }
111         // Find and store GUI language
112         for (auto const & l : guilangs_) {
113                 // First try with the full name
114                 // `en' files are not in a subdirectory
115                 if (available_languages_.contains(toqstr(l))) {
116                         guilang_ = toqstr(l);
117                         break;
118                 }
119                 // Then the name without country code
120                 string const shortl = token(l, '_', 0);
121                 if (available_languages_.contains(toqstr(shortl))) {
122                         guilang_ = toqstr(shortl);
123                         break;
124                 }
125         }
126         // pre-fill the language combo (it will be updated once an item 
127         // has been clicked)
128         languageCO->clear();
129         languageCO->addItem(qt_("English"), toqstr("en"));
130         QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
131         while (i != available_languages_.constEnd()) {
132                 languageCO->addItem(i.value(), i.key());
133                 ++i;
134         }
135         setLanguage();
136 }
137
138
139 GuiLyXFiles::GuiLyXFiles(GuiView & lv)
140         : GuiDialog(lv, "lyxfiles", qt_("New File From Template"))
141 {
142         setupUi(this);
143
144         // Get all supported languages (by code) in order to exclude those
145         // dirs later.
146         QAbstractItemModel * language_model = guiApp->languageModel();
147         language_model->sort(0);
148         for (int i = 0; i != language_model->rowCount(); ++i) {
149                 QModelIndex index = language_model->index(i, 0);
150                 Language const * lang =
151                         languages.getLanguage(fromqstr(index.data(Qt::UserRole).toString()));
152                 if (!lang)
153                         continue;
154                 QString const code = toqstr(lang->code());
155                 if (!all_languages_.contains(code))
156                         all_languages_.insert(code, qt_(lang->display()));
157                 // Also store code without country code
158                 QString const shortcode = code.left(code.indexOf('_'));
159                 if (shortcode != code && !all_languages_.contains(shortcode))
160                         all_languages_.insert(shortcode, qt_(lang->display()));
161         }
162         // Get GUI language
163         string lang = getGuiMessages().language();
164         string const language = getEnv("LANGUAGE");
165         if (!language.empty())
166                 lang += ":" + language;
167         guilangs_ =  getVectorFromString(lang, ":");
168
169         // The filter bar
170         filter_ = new FancyLineEdit(this);
171         filter_->setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
172         filter_->setButtonVisible(FancyLineEdit::Right, true);
173         filter_->setButtonToolTip(FancyLineEdit::Right, qt_("Clear text"));
174         filter_->setAutoHideButton(FancyLineEdit::Right, true);
175         filter_->setPlaceholderText(qt_("All available files"));
176         filter_->setToolTip(qt_("Enter string to filter the list of available files"));
177 #if (QT_VERSION < 0x050000)
178         connect(filter_, SIGNAL(downPressed()),
179                 filesLW, SLOT(setFocus()));
180 #else
181         connect(filter_, &FancyLineEdit::downPressed,
182                 filesLW, [=](){ focusAndHighlight(filesLW); });
183 #endif
184
185         filterBarL->addWidget(filter_, 0);
186         findKeysLA->setBuddy(filter_);
187
188         connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
189                 this, SLOT(slotButtonBox(QAbstractButton *)));
190
191         connect(filesLW, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
192                 this, SLOT(changed_adaptor()));
193         connect(filesLW, SIGNAL(itemSelectionChanged()),
194                 this, SLOT(changed_adaptor()));
195         connect(filter_, SIGNAL(textEdited(QString)),
196                 this, SLOT(filterLabels()));
197         connect(filter_, SIGNAL(rightButtonClicked()),
198                 this, SLOT(resetFilter()));
199
200         bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
201         bc().setOK(buttonBox->button(QDialogButtonBox::Open));
202         bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
203
204         //filesLW->setViewMode(QListView::ListMode);
205         filesLW->setIconSize(QSize(22, 22));
206
207         setFocusProxy(filter_);
208 }
209
210
211 QString const GuiLyXFiles::getSuffix()
212 {
213         if (type_ == "bind" || type_ == "ui")
214                 return toqstr(".") + type_;
215         
216         return ".lyx";
217 }
218
219
220 bool GuiLyXFiles::translateName() const
221 {
222         return (type_ == "templates" || type_ == "examples");
223 }
224
225
226 void GuiLyXFiles::changed_adaptor()
227 {
228         changed();
229 }
230
231
232 void GuiLyXFiles::on_fileTypeCO_activated(int)
233 {
234         updateContents();
235 }
236
237
238 void GuiLyXFiles::on_languageCO_activated(int i)
239 {
240         savelang_ = languageCO->itemData(i).toString();
241         filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
242         changed();
243 }
244
245
246 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem * item, int)
247 {
248         if (!item->data(0, Qt::UserRole).toString().endsWith(getSuffix()))
249                 // not a file (probably a header)
250                 return;
251
252         applyView();
253         dispatchParams();
254         close();
255 }
256
257 void GuiLyXFiles::on_filesLW_itemClicked(QTreeWidgetItem * item, int)
258 {
259         QString const data = item->data(0, Qt::UserRole).toString();
260         if (!data.endsWith(getSuffix()))
261                 // not a file (probably a header)
262                 return;
263
264         languageCO->clear();
265         QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
266         while (i != available_languages_.constEnd()) {
267                 if (localizations_.contains(data)
268                     && localizations_.find(data).value().contains(i.key()))
269                         languageCO->addItem(i.value(), i.key());
270                 ++i;
271         }
272         languageCO->setToolTip(qt_("All available languages of the selected file are displayed here.\n"
273                                    "The selected language version will be opened."));
274         setLanguage();
275         filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
276 }
277
278
279 void GuiLyXFiles::setLanguage()
280 {
281         // first try last setting
282         if (!savelang_.isEmpty()) {
283                 int index = languageCO->findData(savelang_);
284                 if (index != -1) {
285                         languageCO->setCurrentIndex(index);
286                         return;
287                 }
288         }
289         // next, try GUI lang
290         if (!guilang_.isEmpty()) {
291                 int index = languageCO->findData(guilang_);
292                 if (index != -1) {
293                         languageCO->setCurrentIndex(index);
294                         return;
295                 }
296         }
297         // Finally, fall back to English (which should be always there)
298         int index = languageCO->findData(toqstr("en"));
299         if (index != -1) {
300                 languageCO->setCurrentIndex(index);
301         }
302 }
303
304
305 void GuiLyXFiles::on_browsePB_pressed()
306 {
307         bool const examples = (type_ == "examples");
308         FileDialog dlg(qt_("Select template file"));
309         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
310         if (examples)
311                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
312         else
313                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
314
315         FileDialog::Result result = dlg.open(examples ? toqstr(lyxrc.example_path)
316                                                       : toqstr(lyxrc.template_path),
317                                  QStringList(qt_("LyX Documents (*.lyx)")));
318
319         if (result.first != FileDialog::Later && !result.second.isEmpty()) {
320                 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
321                 dispatchParams();
322                 close();
323         }
324 }
325
326
327 void GuiLyXFiles::updateContents()
328 {
329         QString type = fileTypeCO->itemData(fileTypeCO->currentIndex()).toString();
330         QMap<QString, QString> files;
331         languageCO->clear();
332         getFiles(files, type);
333         languageCO->model()->sort(0);
334
335         filesLW->clear();
336         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
337         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
338         QStringList cats;
339         QMap<QString, QString>::const_iterator it = files.constBegin();
340         QFont capfont;
341         capfont.setBold(true);
342         while (it != files.constEnd()) {
343                 QFileInfo const info = QFileInfo(it.key());
344                 QString cat = it.value();
345                 QString subcat;
346                 QString catsave;
347                 if (cat.contains('/')) {
348                         catsave = cat;
349                         cat = catsave.left(catsave.indexOf('/'));
350                         subcat = toqstr(translateIfPossible(
351                                         qstring_to_ucs4(catsave.mid(
352                                                 catsave.indexOf('/') + 1).replace('_', ' '))));
353                 }
354                 cat =  toqstr(translateIfPossible(qstring_to_ucs4(cat.replace('_', ' '))));
355                 QTreeWidgetItem * catItem = new QTreeWidgetItem();
356                 if (!cats.contains(cat)) {
357                         catItem->setText(0, cat);
358                         catItem->setFont(0, capfont);
359                         filesLW->insertTopLevelItem(0, catItem);
360                         catItem->setExpanded(true);
361                         cats << cat;
362                 } else
363                         catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
364                 QTreeWidgetItem * item = new QTreeWidgetItem();
365                 QString const filename = info.fileName();
366                 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
367                 if (translateName())
368                         guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiname)));
369                 QIcon file_icon = (info.filePath().startsWith(toqstr(package().user_support().absFileName()))) ?
370                                 user_icon : system_icon;
371                 item->setIcon(0, file_icon);
372                 item->setData(0, Qt::UserRole, info.filePath());
373                 item->setData(0, Qt::DisplayRole, guiname);
374                 item->setData(0, Qt::ToolTipRole, info.filePath());
375                 if (subcat.isEmpty())
376                         catItem->addChild(item);
377                 else {
378                         QTreeWidgetItem * subcatItem = new QTreeWidgetItem();
379                         if (cats.contains(catsave)) {
380                                 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
381                                 for (int iit = 0; iit < pcats.size(); ++iit) {
382                                         for (int cit = 0; cit < pcats.at(iit)->childCount(); ++cit) {
383                                                 if (pcats.at(iit)->child(cit)->text(0) == subcat) {
384                                                         subcatItem = pcats.at(iit)->child(cit);
385                                                         break;
386                                                 }
387                                         }
388                                 }
389                         } else {
390                                 subcatItem->setText(0, subcat);
391                                 subcatItem->setIcon(0, file_icon);
392                                 cats << catsave;
393                         }
394                         subcatItem->addChild(item);
395                         catItem->addChild(subcatItem);
396                 }
397                 ++it;
398         }
399         filesLW->sortItems(0, Qt::AscendingOrder);
400         // redo filter
401         filterLabels();
402 }
403
404
405 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
406 {
407         switch (buttonBox->standardButton(button)) {
408         case QDialogButtonBox::Open:
409                 slotOK();
410                 break;
411         case QDialogButtonBox::Cancel:
412                 slotClose();
413                 break;
414         default:
415                 break;
416         }
417 }
418
419
420 void GuiLyXFiles::filterLabels()
421 {
422         Qt::CaseSensitivity cs = csFindCB->isChecked() ?
423                 Qt::CaseSensitive : Qt::CaseInsensitive;
424         QTreeWidgetItemIterator it(filesLW);
425         while (*it) {
426                 (*it)->setHidden(
427                         (*it)->childCount() == 0
428                         && !(*it)->text(0).contains(filter_->text(), cs)
429                 );
430                 ++it;
431         }
432 }
433
434
435 void GuiLyXFiles::resetFilter()
436 {
437         filter_->setText(QString());
438         filterLabels();
439 }
440
441 QString const GuiLyXFiles::getRealPath()
442 {
443         QString const relpath = filesLW->currentItem()->data(0, Qt::UserRole).toString();
444         QString const language = languageCO->itemData(languageCO->currentIndex()).toString();
445         if (localizations_.contains(relpath)
446             && localizations_.find(relpath).value().contains(language))
447                 return localizations_.find(relpath).value().find(language).value();
448         return QString();
449 }
450
451
452 void GuiLyXFiles::applyView()
453 {
454         file_ = getRealPath();
455 }
456
457
458 bool GuiLyXFiles::isValid()
459 {
460         return filesLW->currentItem() && filesLW->currentItem()->isSelected();
461 }
462
463
464 bool GuiLyXFiles::initialiseParams(string const & type)
465 {
466         type_ = type.empty() ? toqstr("templates") : toqstr(type);
467         paramsToDialog();
468         return true;
469 }
470
471
472 void GuiLyXFiles::paramsToDialog()
473 {
474         fileTypeCO->clear();
475         if (type_ == "examples" || type_ == "templates") {
476                 fileTypeCO->addItem(qt_("Templates"), toqstr("templates"));
477                 fileTypeCO->addItem(qt_("Examples"), toqstr("examples"));
478         } else if (type_ == "ui")
479                 fileTypeCO->addItem(qt_("User Interface Files"), toqstr("ui"));
480         else if (type_ == "bind")
481                 fileTypeCO->addItem(qt_("Key Binding Files"), toqstr("bind"));
482
483         if (!type_.isEmpty()) {
484                 int i = fileTypeCO->findData(type_);
485                 if (i != -1)
486                         fileTypeCO->setCurrentIndex(i);
487         }
488         if (type_ == "examples")
489                 setTitle(qt_("Open Example File"));
490         else if (type_ == "templates")
491                 setTitle(qt_("New File From Template"));
492         else
493                 setTitle(qt_("Open File"));
494
495         bc().setValid(isValid());
496 }
497
498
499 void GuiLyXFiles::dispatchParams()
500 {
501         if (file_.isEmpty())
502                 return;
503
504         string arg;
505         if (type_ == "templates")
506                 arg = "newfile ";
507         arg += fromqstr(file_);
508         FuncCode const lfun = getLfun();
509
510         dispatch(FuncRequest(lfun, arg));
511 }
512
513
514 FuncCode GuiLyXFiles::getLfun() const
515 {
516         if (type_ == "examples")
517                 return LFUN_FILE_OPEN;
518         else if (type_ == "templates")
519                 return LFUN_BUFFER_NEW_TEMPLATE;
520         return LFUN_NOACTION;
521 }
522
523 Dialog * createGuiLyXFiles(GuiView & lv) { return new GuiLyXFiles(lv); }
524
525
526 } // namespace frontend
527 } // namespace lyx
528
529 #include "moc_GuiLyXFiles.cpp"