2 * \file GuiLyXFiles.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Jürgen Spitzmüller
8 * Full author contact details are available in file CREDITS.
13 #include "GuiLyXFiles.h"
14 #include "GuiApplication.h"
15 #include "qt_helpers.h"
17 #include "FileDialog.h"
18 #include "FuncRequest.h"
22 #include "support/environment.h"
23 #include "support/gettext.h"
24 #include "support/lstrings.h"
25 #include "support/Messages.h"
26 #include "support/qstring_helpers.h"
27 #include "support/Package.h"
30 #include <QDirIterator>
31 #include <QTreeWidget>
34 using namespace lyx::support;
41 QString const guiString(QString const & in)
43 // recode specially encoded chars in file names (URL encoding and underbar)
44 return QString::fromUtf8(QByteArray::fromPercentEncoding(in.toUtf8())).replace('_', ' ');
50 QMap<QString, QString> GuiLyXFiles::getFiles()
52 QMap<QString, QString> result;
53 // We look for lyx files in the subdirectory dir of
55 // 2) build_lyxdir (if not empty)
57 // in this order. Files with a given sub-hierarchy will
58 // only be listed once.
59 // We also consider i18n subdirectories and store them separately.
63 // The three locations to look at.
64 string const user = addPath(package().user_support().absFileName(), fromqstr(type_));
65 string const build = addPath(package().build_support().absFileName(), fromqstr(type_));
66 string const system = addPath(package().system_support().absFileName(), fromqstr(type_));
68 available_languages_.insert(toqstr("en"), qt_("English"));
70 QString const type = fileTypeCO->itemData(fileTypeCO->currentIndex()).toString();
72 // Search in the base paths
73 if (type == "all" || type == "user")
75 if (type == "all" || type == "system")
79 for (int i = 0; i < dirs.size(); ++i) {
80 QString const & dir = dirs.at(i);
81 QDirIterator it(dir, QDir::Files, QDirIterator::Subdirectories);
82 while (it.hasNext()) {
83 QString fn(QFile(it.next()).fileName());
84 if (!fn.endsWith(getSuffix()))
86 QString relpath = toqstr(makeRelPath(qstring_to_ucs4(fn),
87 qstring_to_ucs4(dir)));
89 int s = relpath.indexOf('/', 0);
90 QString cat = qt_("General");
91 QString localization = "en";
94 cat = relpath.left(s);
95 if (all_languages_.contains(cat)
96 && !all_languages_.contains(dir.right(dir.lastIndexOf('/')))) {
97 QMap<QString, QString>::const_iterator li = all_languages_.find(cat);
98 // Skip i18n dir, but add language to the combo
99 if (!available_languages_.contains(li.key()))
100 available_languages_.insert(li.key(), li.value());
102 int sc = relpath.indexOf('/', s + 1);
103 cat = (sc == -1) ? qt_("General") : relpath.mid(s + 1, sc - s - 1);
107 int sc = relpath.indexOf('/', s + 1);
108 QString const subcat = (sc == -1) ?
109 QString() : relpath.mid(s + 1, sc - s - 1);
110 if (!subcat.isEmpty())
114 if (!relpaths.contains(relpath)) {
115 relpaths.append(relpath);
116 if (localization != "en")
118 relpath = relpath.mid(relpath.indexOf('/') + 1);
119 result.insert(relpath, cat);
121 QMap<QString, QString> lm;
122 if (localizations_.contains(relpath))
123 lm = localizations_.find(relpath).value();
124 lm.insert(localization, fn);
125 localizations_.insert(relpath, lm);
129 // Find and store GUI language
130 for (auto const & l : guilangs_) {
131 // First try with the full name
132 // `en' files are not in a subdirectory
133 if (available_languages_.contains(toqstr(l))) {
134 guilang_ = toqstr(l);
137 // Then the name without country code
138 string const shortl = token(l, '_', 0);
139 if (available_languages_.contains(toqstr(shortl))) {
140 guilang_ = toqstr(shortl);
144 // pre-fill the language combo (it will be updated once an item
147 QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
148 while (i != available_languages_.constEnd()) {
149 languageCO->addItem(i.value(), i.key());
157 GuiLyXFiles::GuiLyXFiles(GuiView & lv)
158 : GuiDialog(lv, "lyxfiles", qt_("New File From Template"))
162 // Get all supported languages (by code) in order to exclude those
164 QAbstractItemModel * language_model = guiApp->languageModel();
165 language_model->sort(0);
166 for (int i = 0; i != language_model->rowCount(); ++i) {
167 QModelIndex index = language_model->index(i, 0);
168 Language const * lang =
169 languages.getLanguage(fromqstr(index.data(Qt::UserRole).toString()));
172 QString const code = toqstr(lang->code());
173 if (!all_languages_.contains(code))
174 all_languages_.insert(code, qt_(lang->display()));
175 // Also store code without country code
176 QString const shortcode = code.left(code.indexOf('_'));
177 if (shortcode != code && !all_languages_.contains(shortcode))
178 all_languages_.insert(shortcode, qt_(lang->display()));
181 string lang = getGuiMessages().language();
182 string const language = getEnv("LANGUAGE");
183 if (!language.empty())
184 lang += ":" + language;
185 guilangs_ = getVectorFromString(lang, ":");
188 filter_ = new FancyLineEdit(this);
189 filter_->setClearButton(true);
190 filter_->setPlaceholderText(qt_("All available files"));
191 filter_->setToolTip(qt_("Enter string to filter the list of available files"));
192 connect(filter_, &FancyLineEdit::downPressed,
193 filesLW, [this](){ focusAndHighlight(filesLW); });
195 filterBarL->addWidget(filter_, 0);
196 findKeysLA->setBuddy(filter_);
198 connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
199 this, SLOT(slotButtonBox(QAbstractButton *)));
201 connect(filesLW, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
202 this, SLOT(fileSelectionChanged()));
203 connect(filesLW, SIGNAL(itemSelectionChanged()),
204 this, SLOT(fileSelectionChanged()));
205 connect(filter_, SIGNAL(textEdited(QString)),
206 this, SLOT(filterLabels()));
207 connect(filter_, SIGNAL(rightButtonClicked()),
208 this, SLOT(resetFilter()));
210 bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
211 bc().setOK(buttonBox->button(QDialogButtonBox::Open));
212 bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
214 QIcon user_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-user")
215 : getPixmap("images/", "lyxfiles-user", "svgz,png"));
216 QIcon system_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-system")
217 : getPixmap("images/", "lyxfiles-system", "svgz,png"));
218 fileTypeCO->addItem(qt_("User and System Files"), toqstr("all"));
219 fileTypeCO->addItem(user_icon, qt_("User Files Only"), toqstr("user"));
220 fileTypeCO->addItem(system_icon, qt_("System Files Only"), toqstr("system"));
222 setFocusProxy(filter_);
226 QString const GuiLyXFiles::getSuffix()
228 if (type_ == "bind" || type_ == "ui")
229 return toqstr(".") + type_;
230 else if (type_ == "kbd")
237 bool GuiLyXFiles::translateName() const
239 return (type_ == "templates" || type_ == "examples");
243 void GuiLyXFiles::fileSelectionChanged()
245 if (!filesLW->currentItem()
246 || !filesLW->currentItem()->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
247 // not a file (probably a header)
248 bc().setValid(false);
255 void GuiLyXFiles::on_fileTypeCO_activated(int)
261 void GuiLyXFiles::on_languageCO_activated(int i)
263 savelang_ = languageCO->itemData(i).toString();
264 if (!filesLW->currentItem())
267 filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
272 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem * item, int)
274 if (!item || !item->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
275 // not a file (probably a header)
276 bc().setValid(false);
285 void GuiLyXFiles::on_filesLW_itemClicked(QTreeWidgetItem * item, int)
288 bc().setValid(false);
292 QString const data = item->data(0, Qt::UserRole).toString();
293 if (!data.endsWith(getSuffix())) {
294 // not a file (probably a header)
295 bc().setValid(false);
300 QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
301 while (i != available_languages_.constEnd()) {
302 if (localizations_.contains(data)
303 && localizations_.find(data).value().contains(i.key()))
304 languageCO->addItem(i.value(), i.key());
308 QString const realpath = getRealPath();
309 filesLW->currentItem()->setData(0, Qt::ToolTipRole, realpath);
310 QIcon user_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-user")
311 : getPixmap("images/", "lyxfiles-user", "svgz,png"));
312 QIcon system_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-system")
313 : getPixmap("images/", "lyxfiles-system", "svgz,png"));
314 QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
315 user_icon : system_icon;
316 item->setIcon(0, file_icon);
320 void GuiLyXFiles::setLanguage()
322 // Enable language selection only if there is a selection.
323 bool const item_selected = filesLW->currentItem();
324 bool const language_alternatives = languageCO->count() > 1;
325 languageCO->setEnabled(item_selected && language_alternatives);
326 languageLA->setEnabled(item_selected && language_alternatives);
327 if (item_selected && language_alternatives)
328 languageCO->setToolTip(qt_("All available languages of the selected file are displayed here.\n"
329 "The selected language version will be opened."));
330 else if (item_selected)
331 languageCO->setToolTip(qt_("No alternative language versions available for the selected file."));
333 languageCO->setToolTip(qt_("If alternative languages are available for a given file,\n"
334 "they can be chosen here if a file is selected."));
335 // first try last setting
336 if (!savelang_.isEmpty()) {
337 int index = languageCO->findData(savelang_);
339 languageCO->setCurrentIndex(index);
343 // next, try GUI lang
344 if (!guilang_.isEmpty()) {
345 int index = languageCO->findData(guilang_);
347 languageCO->setCurrentIndex(index);
351 // Finally, fall back to English (which should be always there)
352 int index = languageCO->findData(toqstr("en"));
354 languageCO->setCurrentIndex(index);
359 void GuiLyXFiles::on_browsePB_pressed()
361 QString path1 = toqstr(lyxrc.document_path);
362 QString path2 = toqstr(lyxrc.example_path);
363 QString title = qt_("Select example file");
364 QString filter = qt_("LyX Documents (*.lyx)");
365 QString b1 = qt_("D&ocuments");
366 QString b2 = qt_("&Examples");
368 if (type_ == "templates") {
369 path2 = toqstr(lyxrc.template_path);
370 title = qt_("Select template file");
371 b1 = qt_("D&ocuments");
372 b2 = qt_("&Templates");
374 else if (type_ != "examples") {
375 path1 = toqstr(addName(package().user_support().absFileName(), fromqstr(type_)));
376 path2 = toqstr(addName(package().system_support().absFileName(), fromqstr(type_)));
377 b1 = qt_("&User files");
378 b2 = qt_("&System files");
381 title = qt_("Chose UI file");
382 filter = qt_("LyX UI Files (*.ui)");
384 if (type_ == "bind") {
385 title = qt_("Chose bind file");
386 filter = qt_("LyX Bind Files (*.bind)");
388 if (type_ == "kbd") {
389 title = qt_("Chose keyboard map");
390 filter = qt_("LyX Keymap Files (*.kmap)");
393 FileDialog dlg(title);
394 dlg.setButton1(b1, path1);
395 dlg.setButton2(b2, path2);
397 FileDialog::Result result = dlg.open(path2, QStringList(filter));
399 if (result.first != FileDialog::Later && !result.second.isEmpty()) {
400 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
407 void GuiLyXFiles::updateContents()
410 QMap<QString, QString> files = getFiles();
411 languageCO->model()->sort(0);
415 QIcon user_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-user")
416 : getPixmap("images/", "lyxfiles-user", "svgz,png"));
417 QIcon system_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-system")
418 : getPixmap("images/", "lyxfiles-system", "svgz,png"));
419 QIcon user_folder_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-user-folder")
420 : getPixmap("images/", "lyxfiles-user-folder", "svgz,png"));
421 QIcon system_folder_icon(guiApp ? guiApp->getScaledPixmap("images/", "lyxfiles-system-folder")
422 : getPixmap("images/", "lyxfiles-system-folder", "svgz,png"));
425 QMap<QString, QString>::const_iterator it = files.constBegin();
427 capfont.setBold(true);
428 while (it != files.constEnd()) {
429 QFileInfo const info = QFileInfo(it.key());
430 QString const realpath = getRealPath(it.key());
431 QString cat = it.value();
434 if (cat.contains('/')) {
436 cat = catsave.left(catsave.indexOf('/'));
437 subcat = toqstr(translateIfPossible(
438 qstring_to_ucs4(guiString(catsave.mid(catsave.indexOf('/') + 1)))));
440 cat = toqstr(translateIfPossible(qstring_to_ucs4(guiString(cat))));
441 QTreeWidgetItem * catItem;
442 if (!cats.contains(cat)) {
443 catItem = new QTreeWidgetItem();
444 catItem->setText(0, cat);
445 catItem->setFont(0, capfont);
446 filesLW->insertTopLevelItem(0, catItem);
447 catItem->setExpanded(true);
450 catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
451 QTreeWidgetItem * item = new QTreeWidgetItem();
452 QString const filename = info.fileName();
453 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
454 // Special case: defaults.lyx
455 if (type_ == "templates" && guiname == "defaults")
456 guiname = qt_("Default Template");
457 else if (translateName())
458 guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiString(guiname))));
459 bool const user = realpath.startsWith(toqstr(package().user_support().absFileName()));
460 QIcon file_icon = user ? user_icon : system_icon;
461 item->setIcon(0, file_icon);
462 item->setData(0, Qt::UserRole, it.key());
463 item->setData(0, Qt::DisplayRole, guiname);
464 item->setData(0, Qt::ToolTipRole, realpath);
465 if (subcat.isEmpty())
466 catItem->addChild(item);
468 QTreeWidgetItem * subcatItem = nullptr;
469 if (cats.contains(catsave)) {
470 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
471 for (auto const & pcat : pcats) {
472 for (int cit = 0; cit < pcat->childCount(); ++cit) {
473 if (pcat->child(cit)->text(0) == subcat) {
474 subcatItem = pcat->child(cit);
481 subcatItem = new QTreeWidgetItem();
482 subcatItem->setText(0, subcat);
483 file_icon = user ? user_folder_icon : system_folder_icon;
484 subcatItem->setIcon(0, file_icon);
487 subcatItem->addChild(item);
488 catItem->addChild(subcatItem);
492 filesLW->sortItems(0, Qt::AscendingOrder);
497 bc().setValid(isValid());
503 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
505 switch (buttonBox->standardButton(button)) {
506 case QDialogButtonBox::Open:
509 case QDialogButtonBox::Cancel:
518 void GuiLyXFiles::filterLabels()
520 Qt::CaseSensitivity cs = csFindCB->isChecked() ?
521 Qt::CaseSensitive : Qt::CaseInsensitive;
522 // Collect "active" categories (containing entries
523 // that match the filter)
524 QVector<QTreeWidgetItem*> activeCats;
525 QTreeWidgetItemIterator it(filesLW);
527 if ((*it)->childCount() > 0) {
528 // Unhide parents (will be hidden
529 // below if necessary)
530 (*it)->setHidden(false);
534 bool const match = (*it)->text(0).contains(filter_->text(), cs);
536 // Register parents of matched entries
537 // so we don't hide those later.
538 QTreeWidgetItem * twi = *it;
542 activeCats << twi->parent();
543 // ascend further up if possible
547 (*it)->setHidden(!match);
550 // Iterate through parents once more
551 // to hide empty categories
552 it = QTreeWidgetItemIterator(filesLW);
554 if ((*it)->childCount() == 0) {
558 (*it)->setHidden(!activeCats.contains(*it));
564 void GuiLyXFiles::resetFilter()
566 filter_->setText(QString());
570 QString const GuiLyXFiles::getRealPath(QString relpath)
572 if (relpath.isEmpty() && filesLW->currentItem() != nullptr)
573 relpath = filesLW->currentItem()->data(0, Qt::UserRole).toString();
574 QString const language = languageCO->itemData(languageCO->currentIndex()).toString();
575 if (localizations_.contains(relpath)) {
576 if (localizations_.find(relpath).value().contains(language))
577 return localizations_.find(relpath).value().find(language).value();
578 else if (localizations_.find(relpath).value().contains(guilang_))
579 return localizations_.find(relpath).value().find(guilang_).value();
580 else if (localizations_.find(relpath).value().contains(toqstr("en")))
581 return localizations_.find(relpath).value().find(toqstr("en")).value();
587 void GuiLyXFiles::applyView()
589 file_ = getRealPath();
593 bool GuiLyXFiles::isValid()
595 return filesLW->currentItem() && filesLW->currentItem()->isSelected();
599 bool GuiLyXFiles::initialiseParams(string const & type)
601 type_ = type.empty() ? toqstr("templates") : toqstr(type);
607 void GuiLyXFiles::passParams(string const & data)
609 initialiseParams(data);
614 void GuiLyXFiles::selectItem(QString const & item)
616 /* Using an intermediary variable flags is needed up to at least
617 * Qt 5.5 because of a subtle namespace issue. See:
618 * https://stackoverflow.com/questions/10755058/qflags-enum-type-conversion-fails-all-of-a-sudden
620 Qt::MatchFlags const flags(Qt::MatchExactly|Qt::MatchRecursive);
621 QList<QTreeWidgetItem *> twi = filesLW->findItems(item, flags);
623 twi.first()->setSelected(true);
627 void GuiLyXFiles::paramsToDialog()
629 if (type_ == "examples")
630 setTitle(qt_("Open Example File"));
631 else if (type_ == "templates")
632 setTitle(qt_("New File From Template"));
634 setTitle(qt_("Open File"));
638 void GuiLyXFiles::dispatchParams()
644 if (type_ == "templates")
646 arg += quoteName(fromqstr(file_));
647 FuncCode const lfun = getLfun();
649 if (lfun == LFUN_NOACTION)
653 dispatch(FuncRequest(lfun, arg));
657 FuncCode GuiLyXFiles::getLfun() const
659 if (type_ == "examples")
660 return LFUN_FILE_OPEN;
661 else if (type_ == "templates")
662 return LFUN_BUFFER_NEW_TEMPLATE;
663 return LFUN_NOACTION;
667 } // namespace frontend
670 #include "moc_GuiLyXFiles.cpp"