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"
29 #include <QDirIterator>
30 #include <QTreeWidget>
33 using namespace lyx::support;
40 QString const guiString(QString const & in)
42 // recode specially encoded chars in file names (URL encoding and underbar)
43 return QString::fromUtf8(QByteArray::fromPercentEncoding(in.toUtf8())).replace('_', ' ');
49 QMap<QString, QString> GuiLyXFiles::getFiles()
51 QMap<QString, QString> result;
52 // We look for lyx files in the subdirectory dir of
54 // 2) build_lyxdir (if not empty)
56 // in this order. Files with a given sub-hierarchy will
57 // only be listed once.
58 // We also consider i18n subdirectories and store them separately.
62 // The three locations to look at.
63 string const user = addPath(package().user_support().absFileName(), fromqstr(type_));
64 string const build = addPath(package().build_support().absFileName(), fromqstr(type_));
65 string const system = addPath(package().system_support().absFileName(), fromqstr(type_));
67 available_languages_.insert(toqstr("en"), qt_("English"));
69 QString const type = fileTypeCO->itemData(fileTypeCO->currentIndex()).toString();
71 // Search in the base paths
72 if (type == "all" || type == "user")
74 if (type == "all" || type == "system")
78 for (int i = 0; i < dirs.size(); ++i) {
79 QString const & dir = dirs.at(i);
80 QDirIterator it(dir, QDir::Files, QDirIterator::Subdirectories);
81 while (it.hasNext()) {
82 QString fn(QFile(it.next()).fileName());
83 if (!fn.endsWith(getSuffix()))
85 QString relpath = toqstr(makeRelPath(qstring_to_ucs4(fn),
86 qstring_to_ucs4(dir)));
88 int s = relpath.indexOf('/', 0);
89 QString cat = qt_("General");
90 QString localization = "en";
93 cat = relpath.left(s);
94 if (all_languages_.contains(cat)
95 && !all_languages_.contains(dir.right(dir.lastIndexOf('/')))) {
96 QMap<QString, QString>::const_iterator li = all_languages_.find(cat);
97 // Skip i18n dir, but add language to the combo
98 if (!available_languages_.contains(li.key()))
99 available_languages_.insert(li.key(), li.value());
101 int sc = relpath.indexOf('/', s + 1);
102 cat = (sc == -1) ? qt_("General") : relpath.mid(s + 1, sc - s - 1);
106 int sc = relpath.indexOf('/', s + 1);
107 QString const subcat = (sc == -1) ?
108 QString() : relpath.mid(s + 1, sc - s - 1);
109 if (!subcat.isEmpty())
113 if (!relpaths.contains(relpath)) {
114 relpaths.append(relpath);
115 if (localization != "en")
117 relpath = relpath.mid(relpath.indexOf('/') + 1);
118 result.insert(relpath, cat);
120 QMap<QString, QString> lm;
121 if (localizations_.contains(relpath))
122 lm = localizations_.find(relpath).value();
123 lm.insert(localization, fn);
124 localizations_.insert(relpath, lm);
128 // Find and store GUI language
129 for (auto const & l : guilangs_) {
130 // First try with the full name
131 // `en' files are not in a subdirectory
132 if (available_languages_.contains(toqstr(l))) {
133 guilang_ = toqstr(l);
136 // Then the name without country code
137 string const shortl = token(l, '_', 0);
138 if (available_languages_.contains(toqstr(shortl))) {
139 guilang_ = toqstr(shortl);
143 // pre-fill the language combo (it will be updated once an item
146 QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
147 while (i != available_languages_.constEnd()) {
148 languageCO->addItem(i.value(), i.key());
156 GuiLyXFiles::GuiLyXFiles(GuiView & lv)
157 : GuiDialog(lv, "lyxfiles", qt_("New File From Template"))
161 // Get all supported languages (by code) in order to exclude those
163 QAbstractItemModel * language_model = guiApp->languageModel();
164 language_model->sort(0);
165 for (int i = 0; i != language_model->rowCount(); ++i) {
166 QModelIndex index = language_model->index(i, 0);
167 Language const * lang =
168 languages.getLanguage(fromqstr(index.data(Qt::UserRole).toString()));
171 QString const code = toqstr(lang->code());
172 if (!all_languages_.contains(code))
173 all_languages_.insert(code, qt_(lang->display()));
174 // Also store code without country code
175 QString const shortcode = code.left(code.indexOf('_'));
176 if (shortcode != code && !all_languages_.contains(shortcode))
177 all_languages_.insert(shortcode, qt_(lang->display()));
180 string lang = getGuiMessages().language();
181 string const language = getEnv("LANGUAGE");
182 if (!language.empty())
183 lang += ":" + language;
184 guilangs_ = getVectorFromString(lang, ":");
187 filter_ = new FancyLineEdit(this);
188 filter_->setClearButton(true);
189 filter_->setPlaceholderText(qt_("All available files"));
190 filter_->setToolTip(qt_("Enter string to filter the list of available files"));
191 #if (QT_VERSION < 0x050000)
192 connect(filter_, SIGNAL(downPressed()),
193 filesLW, SLOT(setFocus()));
195 connect(filter_, &FancyLineEdit::downPressed,
196 filesLW, [this](){ focusAndHighlight(filesLW); });
199 filterBarL->addWidget(filter_, 0);
200 findKeysLA->setBuddy(filter_);
202 connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
203 this, SLOT(slotButtonBox(QAbstractButton *)));
205 connect(filesLW, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
206 this, SLOT(fileSelectionChanged()));
207 connect(filesLW, SIGNAL(itemSelectionChanged()),
208 this, SLOT(fileSelectionChanged()));
209 connect(filter_, SIGNAL(textEdited(QString)),
210 this, SLOT(filterLabels()));
211 connect(filter_, SIGNAL(rightButtonClicked()),
212 this, SLOT(resetFilter()));
214 bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
215 bc().setOK(buttonBox->button(QDialogButtonBox::Open));
216 bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
218 //filesLW->setViewMode(QListView::ListMode);
219 filesLW->setIconSize(QSize(22, 22));
221 QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
222 QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
223 fileTypeCO->addItem(qt_("User and System Files"), toqstr("all"));
224 fileTypeCO->addItem(user_icon, qt_("User Files Only"), toqstr("user"));
225 fileTypeCO->addItem(system_icon, qt_("System Files Only"), toqstr("system"));
227 setFocusProxy(filter_);
231 QString const GuiLyXFiles::getSuffix()
233 if (type_ == "bind" || type_ == "ui")
234 return toqstr(".") + type_;
235 else if (type_ == "kbd")
242 bool GuiLyXFiles::translateName() const
244 return (type_ == "templates" || type_ == "examples");
248 void GuiLyXFiles::fileSelectionChanged()
250 if (!filesLW->currentItem()
251 || !filesLW->currentItem()->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
252 // not a file (probably a header)
253 bc().setValid(false);
260 void GuiLyXFiles::on_fileTypeCO_activated(int)
266 void GuiLyXFiles::on_languageCO_activated(int i)
268 savelang_ = languageCO->itemData(i).toString();
269 if (!filesLW->currentItem())
272 filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
277 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem * item, int)
279 if (!item || !item->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
280 // not a file (probably a header)
281 bc().setValid(false);
290 void GuiLyXFiles::on_filesLW_itemClicked(QTreeWidgetItem * item, int)
293 bc().setValid(false);
297 QString const data = item->data(0, Qt::UserRole).toString();
298 if (!data.endsWith(getSuffix())) {
299 // not a file (probably a header)
300 bc().setValid(false);
305 QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
306 while (i != available_languages_.constEnd()) {
307 if (localizations_.contains(data)
308 && localizations_.find(data).value().contains(i.key()))
309 languageCO->addItem(i.value(), i.key());
313 QString const realpath = getRealPath();
314 filesLW->currentItem()->setData(0, Qt::ToolTipRole, realpath);
315 QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
316 QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
317 QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
318 user_icon : system_icon;
319 item->setIcon(0, file_icon);
323 void GuiLyXFiles::setLanguage()
325 // Enable language selection only if there is a selection.
326 bool const item_selected = filesLW->currentItem();
327 bool const language_alternatives = languageCO->count() > 1;
328 languageCO->setEnabled(item_selected && language_alternatives);
329 languageLA->setEnabled(item_selected && language_alternatives);
330 if (item_selected && language_alternatives)
331 languageCO->setToolTip(qt_("All available languages of the selected file are displayed here.\n"
332 "The selected language version will be opened."));
333 else if (item_selected)
334 languageCO->setToolTip(qt_("No alternative language versions available for the selected file."));
336 languageCO->setToolTip(qt_("If alternative languages are available for a given file,\n"
337 "they can be chosen here if a file is selected."));
338 // first try last setting
339 if (!savelang_.isEmpty()) {
340 int index = languageCO->findData(savelang_);
342 languageCO->setCurrentIndex(index);
346 // next, try GUI lang
347 if (!guilang_.isEmpty()) {
348 int index = languageCO->findData(guilang_);
350 languageCO->setCurrentIndex(index);
354 // Finally, fall back to English (which should be always there)
355 int index = languageCO->findData(toqstr("en"));
357 languageCO->setCurrentIndex(index);
362 void GuiLyXFiles::on_browsePB_pressed()
364 QString path1 = toqstr(lyxrc.document_path);
365 QString path2 = toqstr(lyxrc.example_path);
366 QString title = qt_("Select example file");
367 QString filter = qt_("LyX Documents (*.lyx)");
368 QString b1 = qt_("D&ocuments");
369 QString b2 = qt_("&Examples");
371 if (type_ == "templates") {
372 path2 = toqstr(lyxrc.template_path);
373 title = qt_("Select template file");
374 b1 = qt_("D&ocuments");
375 b2 = qt_("&Templates");
377 else if (type_ != "examples") {
378 path1 = toqstr(addName(package().user_support().absFileName(), fromqstr(type_)));
379 path2 = toqstr(addName(package().system_support().absFileName(), fromqstr(type_)));
380 b1 = qt_("&User files");
381 b2 = qt_("&System files");
384 title = qt_("Chose UI file");
385 filter = qt_("LyX UI Files (*.ui)");
387 if (type_ == "bind") {
388 title = qt_("Chose bind file");
389 filter = qt_("LyX Bind Files (*.bind)");
391 if (type_ == "kbd") {
392 title = qt_("Chose keyboard map");
393 filter = qt_("LyX Keymap Files (*.kmap)");
396 FileDialog dlg(title);
397 dlg.setButton1(b1, path1);
398 dlg.setButton2(b2, path2);
400 FileDialog::Result result = dlg.open(path2, QStringList(filter));
402 if (result.first != FileDialog::Later && !result.second.isEmpty()) {
403 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
410 void GuiLyXFiles::updateContents()
413 QMap<QString, QString> files = getFiles();
414 languageCO->model()->sort(0);
417 QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
418 QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
420 QMap<QString, QString>::const_iterator it = files.constBegin();
422 capfont.setBold(true);
423 while (it != files.constEnd()) {
424 QFileInfo const info = QFileInfo(it.key());
425 QString const realpath = getRealPath(it.key());
426 QString cat = it.value();
429 if (cat.contains('/')) {
431 cat = catsave.left(catsave.indexOf('/'));
432 subcat = toqstr(translateIfPossible(
433 qstring_to_ucs4(guiString(catsave.mid(catsave.indexOf('/') + 1)))));
435 cat = toqstr(translateIfPossible(qstring_to_ucs4(guiString(cat))));
436 QTreeWidgetItem * catItem;
437 if (!cats.contains(cat)) {
438 catItem = new QTreeWidgetItem();
439 catItem->setText(0, cat);
440 catItem->setFont(0, capfont);
441 filesLW->insertTopLevelItem(0, catItem);
442 catItem->setExpanded(true);
445 catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
446 QTreeWidgetItem * item = new QTreeWidgetItem();
447 QString const filename = info.fileName();
448 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
449 // Special case: defaults.lyx
450 if (type_ == "templates" && guiname == "defaults")
451 guiname = qt_("Default Template");
452 else if (translateName())
453 guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiString(guiname))));
454 QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
455 user_icon : system_icon;
456 item->setIcon(0, file_icon);
457 item->setData(0, Qt::UserRole, it.key());
458 item->setData(0, Qt::DisplayRole, guiname);
459 item->setData(0, Qt::ToolTipRole, realpath);
460 if (subcat.isEmpty())
461 catItem->addChild(item);
463 QTreeWidgetItem * subcatItem = nullptr;
464 if (cats.contains(catsave)) {
465 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
466 for (auto const & pcat : pcats) {
467 for (int cit = 0; cit < pcat->childCount(); ++cit) {
468 if (pcat->child(cit)->text(0) == subcat) {
469 subcatItem = pcat->child(cit);
476 subcatItem = new QTreeWidgetItem();
477 subcatItem->setText(0, subcat);
478 subcatItem->setIcon(0, file_icon);
481 subcatItem->addChild(item);
482 catItem->addChild(subcatItem);
486 filesLW->sortItems(0, Qt::AscendingOrder);
492 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
494 switch (buttonBox->standardButton(button)) {
495 case QDialogButtonBox::Open:
498 case QDialogButtonBox::Cancel:
507 void GuiLyXFiles::filterLabels()
509 Qt::CaseSensitivity cs = csFindCB->isChecked() ?
510 Qt::CaseSensitive : Qt::CaseInsensitive;
511 QTreeWidgetItemIterator it(filesLW);
514 (*it)->childCount() == 0
515 && !(*it)->text(0).contains(filter_->text(), cs)
522 void GuiLyXFiles::resetFilter()
524 filter_->setText(QString());
528 QString const GuiLyXFiles::getRealPath(QString relpath)
530 if (relpath.isEmpty() && filesLW->currentItem() != nullptr)
531 relpath = filesLW->currentItem()->data(0, Qt::UserRole).toString();
532 QString const language = languageCO->itemData(languageCO->currentIndex()).toString();
533 if (localizations_.contains(relpath)) {
534 if (localizations_.find(relpath).value().contains(language))
535 return localizations_.find(relpath).value().find(language).value();
536 else if (localizations_.find(relpath).value().contains(guilang_))
537 return localizations_.find(relpath).value().find(guilang_).value();
538 else if (localizations_.find(relpath).value().contains(toqstr("en")))
539 return localizations_.find(relpath).value().find(toqstr("en")).value();
545 void GuiLyXFiles::applyView()
547 file_ = getRealPath();
551 bool GuiLyXFiles::isValid()
553 return filesLW->currentItem() && filesLW->currentItem()->isSelected();
557 bool GuiLyXFiles::initialiseParams(string const & type)
559 type_ = type.empty() ? toqstr("templates") : toqstr(type);
565 void GuiLyXFiles::passParams(string const & data)
567 initialiseParams(data);
572 void GuiLyXFiles::selectItem(QString const & item)
574 /* Using an intermediary variable flags is needed up to at least
575 * Qt 5.5 because of a subtle namespace issue. See:
576 * https://stackoverflow.com/questions/10755058/qflags-enum-type-conversion-fails-all-of-a-sudden
578 Qt::MatchFlags const flags(Qt::MatchExactly|Qt::MatchRecursive);
579 QList<QTreeWidgetItem *> twi = filesLW->findItems(item, flags);
581 twi.first()->setSelected(true);
585 void GuiLyXFiles::paramsToDialog()
587 if (type_ == "examples")
588 setTitle(qt_("Open Example File"));
589 else if (type_ == "templates")
590 setTitle(qt_("New File From Template"));
592 setTitle(qt_("Open File"));
594 bc().setValid(isValid());
598 void GuiLyXFiles::dispatchParams()
604 if (type_ == "templates")
606 arg += quoteName(fromqstr(file_));
607 FuncCode const lfun = getLfun();
609 if (lfun == LFUN_NOACTION)
613 dispatch(FuncRequest(lfun, arg));
617 FuncCode GuiLyXFiles::getLfun() const
619 if (type_ == "examples")
620 return LFUN_FILE_OPEN;
621 else if (type_ == "templates")
622 return LFUN_BUFFER_NEW_TEMPLATE;
623 return LFUN_NOACTION;
627 } // namespace frontend
630 #include "moc_GuiLyXFiles.cpp"