]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiLyXFiles.cpp
Improve some debug messages
[lyx.git] / src / frontends / qt / 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 "FuncRequest.h"
19 #include "Language.h"
20 #include "LyXRC.h"
21
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"
28
29 #include <QDirIterator>
30 #include <QTreeWidget>
31
32 using namespace std;
33 using namespace lyx::support;
34
35 namespace lyx {
36 namespace frontend {
37
38 namespace {
39
40 QString const guiString(QString const & in)
41 {
42         // recode specially encoded chars in file names (URL encoding and underbar)
43         return QString::fromUtf8(QByteArray::fromPercentEncoding(in.toUtf8())).replace('_', ' ');
44 }
45
46 } // namespace anon
47
48
49 QMap<QString, QString> GuiLyXFiles::getFiles()
50 {
51         QMap<QString, QString> result;
52         // We look for lyx files in the subdirectory dir of
53         //   1) user_lyxdir
54         //   2) build_lyxdir (if not empty)
55         //   3) system_lyxdir
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.
59         QStringList dirs;
60         QStringList relpaths;
61
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_));
66
67         available_languages_.insert(toqstr("en"), qt_("English"));
68
69         QString const type = fileTypeCO->itemData(fileTypeCO->currentIndex()).toString();
70
71         // Search in the base paths
72         if (type == "all" || type == "user")
73                 dirs << toqstr(user);
74         if (type == "all" || type == "system")
75                 dirs << toqstr(build)
76                      << toqstr(system);
77
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()))
84                                 continue;
85                         QString relpath = toqstr(makeRelPath(qstring_to_ucs4(fn),
86                                                              qstring_to_ucs4(dir)));
87                         // <cat>/
88                         int s = relpath.indexOf('/', 0);
89                         QString cat = qt_("General");
90                         QString localization = "en";
91                         if (s != -1) {
92                                 // <cat>/<subcat>/
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());
100                                         localization = cat;
101                                         int sc = relpath.indexOf('/', s + 1);
102                                         cat = (sc == -1) ? qt_("General") : relpath.mid(s + 1, sc - s - 1);
103                                         s = sc;
104                                 }
105                                 if (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())
110                                                 cat += '/' + subcat;
111                                 }
112                         }
113                         if (!relpaths.contains(relpath)) {
114                                 relpaths.append(relpath);
115                                 if (localization != "en")
116                                         // strip off lang/
117                                         relpath = relpath.mid(relpath.indexOf('/') + 1);
118                                 result.insert(relpath, cat);
119                                                                         
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);
125                         }
126                 }
127         }
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);
134                         break;
135                 }
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);
140                         break;
141                 }
142         }
143         // pre-fill the language combo (it will be updated once an item 
144         // has been clicked)
145         languageCO->clear();
146         QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
147         while (i != available_languages_.constEnd()) {
148                 languageCO->addItem(i.value(), i.key());
149                 ++i;
150         }
151         setLanguage();
152         languageLA->setText(qt_("Preferred &Language:"));
153         return result;
154 }
155
156
157 GuiLyXFiles::GuiLyXFiles(GuiView & lv)
158         : GuiDialog(lv, "lyxfiles", qt_("New File From Template"))
159 {
160         setupUi(this);
161
162         // Get all supported languages (by code) in order to exclude those
163         // dirs later.
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()));
170                 if (!lang)
171                         continue;
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()));
179         }
180         // Get GUI language
181         string lang = getGuiMessages().language();
182         string const language = getEnv("LANGUAGE");
183         if (!language.empty())
184                 lang += ":" + language;
185         guilangs_ =  getVectorFromString(lang, ":");
186
187         // The filter bar
188         filter_ = new FancyLineEdit(this);
189         filter_->setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
190         filter_->setButtonVisible(FancyLineEdit::Right, true);
191         filter_->setButtonToolTip(FancyLineEdit::Right, qt_("Clear text"));
192         filter_->setAutoHideButton(FancyLineEdit::Right, true);
193         filter_->setPlaceholderText(qt_("All available files"));
194         filter_->setToolTip(qt_("Enter string to filter the list of available files"));
195 #if (QT_VERSION < 0x050000)
196         connect(filter_, SIGNAL(downPressed()),
197                 filesLW, SLOT(setFocus()));
198 #else
199         connect(filter_, &FancyLineEdit::downPressed,
200                 filesLW, [=](){ focusAndHighlight(filesLW); });
201 #endif
202
203         filterBarL->addWidget(filter_, 0);
204         findKeysLA->setBuddy(filter_);
205
206         connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
207                 this, SLOT(slotButtonBox(QAbstractButton *)));
208
209         connect(filesLW, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
210                 this, SLOT(fileSelectionChanged()));
211         connect(filesLW, SIGNAL(itemSelectionChanged()),
212                 this, SLOT(fileSelectionChanged()));
213         connect(filter_, SIGNAL(textEdited(QString)),
214                 this, SLOT(filterLabels()));
215         connect(filter_, SIGNAL(rightButtonClicked()),
216                 this, SLOT(resetFilter()));
217
218         bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
219         bc().setOK(buttonBox->button(QDialogButtonBox::Open));
220         bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
221
222         //filesLW->setViewMode(QListView::ListMode);
223         filesLW->setIconSize(QSize(22, 22));
224
225         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
226         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
227         fileTypeCO->addItem(qt_("User and System Files"), toqstr("all"));
228         fileTypeCO->addItem(user_icon, qt_("User Files Only"), toqstr("user"));
229         fileTypeCO->addItem(system_icon, qt_("System Files Only"), toqstr("system"));
230
231         setFocusProxy(filter_);
232 }
233
234
235 QString const GuiLyXFiles::getSuffix()
236 {
237         if (type_ == "bind" || type_ == "ui")
238                 return toqstr(".") + type_;
239         else if (type_ == "kbd")
240                 return ".kmap";
241         
242         return ".lyx";
243 }
244
245
246 bool GuiLyXFiles::translateName() const
247 {
248         return (type_ == "templates" || type_ == "examples");
249 }
250
251
252 void GuiLyXFiles::fileSelectionChanged()
253 {
254         if (!filesLW->currentItem()
255             || !filesLW->currentItem()->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
256                 // not a file (probably a header)
257                 bc().setValid(false);
258                 return;
259         }
260         changed();
261 }
262
263
264 void GuiLyXFiles::on_fileTypeCO_activated(int)
265 {
266         updateContents();
267 }
268
269
270 void GuiLyXFiles::on_languageCO_activated(int i)
271 {
272         savelang_ = languageCO->itemData(i).toString();
273         if (!filesLW->currentItem())
274                 return;
275
276         filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
277         changed();
278 }
279
280
281 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem * item, int)
282 {
283         if (!item || !item->data(0, Qt::UserRole).toString().endsWith(getSuffix())) {
284                 // not a file (probably a header)
285                 bc().setValid(false);
286                 return;
287         }
288
289         applyView();
290         dispatchParams();
291         close();
292 }
293
294 void GuiLyXFiles::on_filesLW_itemClicked(QTreeWidgetItem * item, int)
295 {
296         if (!item) {
297                 bc().setValid(false);
298                 return;
299         }
300
301         QString const data = item->data(0, Qt::UserRole).toString();
302         if (!data.endsWith(getSuffix())) {
303                 // not a file (probably a header)
304                 bc().setValid(false);
305                 return;
306         }
307
308         languageCO->clear();
309         QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
310         while (i != available_languages_.constEnd()) {
311                 if (localizations_.contains(data)
312                     && localizations_.find(data).value().contains(i.key()))
313                         languageCO->addItem(i.value(), i.key());
314                 ++i;
315         }
316         languageLA->setText(qt_("File &Language:"));
317         languageCO->setToolTip(qt_("All available languages of the selected file are displayed here.\n"
318                                    "The selected language version will be opened."));
319         setLanguage();
320         QString const realpath = getRealPath();
321         filesLW->currentItem()->setData(0, Qt::ToolTipRole, realpath);
322         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
323         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
324         QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
325                         user_icon : system_icon;
326         item->setIcon(0, file_icon);
327 }
328
329
330 void GuiLyXFiles::setLanguage()
331 {
332         // Enable language selection only if there is a selection.
333         languageCO->setEnabled(languageCO->count() > 1);
334         languageLA->setEnabled(languageCO->count() > 1);
335         // first try last setting
336         if (!savelang_.isEmpty()) {
337                 int index = languageCO->findData(savelang_);
338                 if (index != -1) {
339                         languageCO->setCurrentIndex(index);
340                         return;
341                 }
342         }
343         // next, try GUI lang
344         if (!guilang_.isEmpty()) {
345                 int index = languageCO->findData(guilang_);
346                 if (index != -1) {
347                         languageCO->setCurrentIndex(index);
348                         return;
349                 }
350         }
351         // Finally, fall back to English (which should be always there)
352         int index = languageCO->findData(toqstr("en"));
353         if (index != -1) {
354                 languageCO->setCurrentIndex(index);
355         }
356 }
357
358
359 void GuiLyXFiles::on_browsePB_pressed()
360 {
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");
367
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");
373         }
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");
379         }
380         if (type_ == "ui") {
381                 title = qt_("Chose UI file");
382                 filter = qt_("LyX UI Files (*.ui)");
383         }
384         if (type_ == "bind") {
385                 title = qt_("Chose bind file");
386                 filter = qt_("LyX Bind Files (*.bind)");
387         }
388         if (type_ == "kbd") {
389                 title = qt_("Chose keyboard map");
390                 filter = qt_("LyX Keymap Files (*.kmap)");
391         }
392
393         FileDialog dlg(title);
394         dlg.setButton1(b1, path1);
395         dlg.setButton2(b2, path2);
396
397         FileDialog::Result result = dlg.open(path2, QStringList(filter));
398
399         if (result.first != FileDialog::Later && !result.second.isEmpty()) {
400                 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
401                 dispatchParams();
402                 close();
403         }
404 }
405
406
407 void GuiLyXFiles::updateContents()
408 {
409         languageCO->clear();
410         QMap<QString, QString> files = getFiles();
411         languageCO->model()->sort(0);
412
413         filesLW->clear();
414         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
415         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
416         QStringList cats;
417         QMap<QString, QString>::const_iterator it = files.constBegin();
418         QFont capfont;
419         capfont.setBold(true);
420         while (it != files.constEnd()) {
421                 QFileInfo const info = QFileInfo(it.key());
422                 QString const realpath = getRealPath(it.key());
423                 QString cat = it.value();
424                 QString subcat;
425                 QString catsave;
426                 if (cat.contains('/')) {
427                         catsave = cat;
428                         cat = catsave.left(catsave.indexOf('/'));
429                         subcat = toqstr(translateIfPossible(
430                                         qstring_to_ucs4(guiString(catsave.mid(catsave.indexOf('/') + 1)))));
431                 }
432                 cat =  toqstr(translateIfPossible(qstring_to_ucs4(guiString(cat))));
433                 QTreeWidgetItem * catItem;
434                 if (!cats.contains(cat)) {
435                         catItem = new QTreeWidgetItem();
436                         catItem->setText(0, cat);
437                         catItem->setFont(0, capfont);
438                         filesLW->insertTopLevelItem(0, catItem);
439                         catItem->setExpanded(true);
440                         cats << cat;
441                 } else
442                         catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
443                 QTreeWidgetItem * item = new QTreeWidgetItem();
444                 QString const filename = info.fileName();
445                 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
446                 // Special case: defaults.lyx
447                 if (type_ == "templates" && guiname == "defaults")
448                         guiname = qt_("Default Template");
449                 else if (translateName())
450                         guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiString(guiname))));
451                 QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
452                                 user_icon : system_icon;
453                 item->setIcon(0, file_icon);
454                 item->setData(0, Qt::UserRole, it.key());
455                 item->setData(0, Qt::DisplayRole, guiname);
456                 item->setData(0, Qt::ToolTipRole, realpath);
457                 if (subcat.isEmpty())
458                         catItem->addChild(item);
459                 else {
460                         QTreeWidgetItem * subcatItem = nullptr;
461                         if (cats.contains(catsave)) {
462                                 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
463                                 for (auto const & pcat : pcats) {
464                                         for (int cit = 0; cit < pcat->childCount(); ++cit) {
465                                                 if (pcat->child(cit)->text(0) == subcat) {
466                                                         subcatItem = pcat->child(cit);
467                                                         break;
468                                                 }
469                                         }
470                                 }
471                         }
472                         if (!subcatItem) {
473                                 subcatItem = new QTreeWidgetItem();
474                                 subcatItem->setText(0, subcat);
475                                 subcatItem->setIcon(0, file_icon);
476                                 cats << catsave;
477                         }
478                         subcatItem->addChild(item);
479                         catItem->addChild(subcatItem);
480                 }
481                 ++it;
482         }
483         filesLW->sortItems(0, Qt::AscendingOrder);
484         // redo filter
485         filterLabels();
486 }
487
488
489 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
490 {
491         switch (buttonBox->standardButton(button)) {
492         case QDialogButtonBox::Open:
493                 slotOK();
494                 break;
495         case QDialogButtonBox::Cancel:
496                 slotClose();
497                 break;
498         default:
499                 break;
500         }
501 }
502
503
504 void GuiLyXFiles::filterLabels()
505 {
506         Qt::CaseSensitivity cs = csFindCB->isChecked() ?
507                 Qt::CaseSensitive : Qt::CaseInsensitive;
508         QTreeWidgetItemIterator it(filesLW);
509         while (*it) {
510                 (*it)->setHidden(
511                         (*it)->childCount() == 0
512                         && !(*it)->text(0).contains(filter_->text(), cs)
513                 );
514                 ++it;
515         }
516 }
517
518
519 void GuiLyXFiles::resetFilter()
520 {
521         filter_->setText(QString());
522         filterLabels();
523 }
524
525 QString const GuiLyXFiles::getRealPath(QString relpath)
526 {
527         if (relpath.isEmpty() && filesLW->currentItem() != nullptr)
528                 relpath = filesLW->currentItem()->data(0, Qt::UserRole).toString();
529         QString const language = languageCO->itemData(languageCO->currentIndex()).toString();
530         if (localizations_.contains(relpath)) {
531                 if (localizations_.find(relpath).value().contains(language))
532                         return localizations_.find(relpath).value().find(language).value();
533                 else if (localizations_.find(relpath).value().contains(guilang_))
534                         return localizations_.find(relpath).value().find(guilang_).value();
535                 else if (localizations_.find(relpath).value().contains(toqstr("en")))
536                         return localizations_.find(relpath).value().find(toqstr("en")).value();
537         }
538         return QString();
539 }
540
541
542 void GuiLyXFiles::applyView()
543 {
544         file_ = getRealPath();
545 }
546
547
548 bool GuiLyXFiles::isValid()
549 {
550         return filesLW->currentItem() && filesLW->currentItem()->isSelected();
551 }
552
553
554 bool GuiLyXFiles::initialiseParams(string const & type)
555 {
556         type_ = type.empty() ? toqstr("templates") : toqstr(type);
557         paramsToDialog();
558         return true;
559 }
560
561
562 void GuiLyXFiles::passParams(string const & data)
563 {
564         initialiseParams(data);
565         updateContents();
566 }
567
568
569 void GuiLyXFiles::selectItem(QString const & item)
570 {
571         /* Using an intermediary variable flags is needed up to at least
572          * Qt 5.5 because of a subtle namespace issue. See:
573          *   https://stackoverflow.com/questions/10755058/qflags-enum-type-conversion-fails-all-of-a-sudden
574          * for details.*/
575         Qt::MatchFlags const flags(Qt::MatchExactly|Qt::MatchRecursive);
576         QList<QTreeWidgetItem *> twi = filesLW->findItems(item, flags);
577         if (!twi.isEmpty())
578                 twi.first()->setSelected(true);
579 }
580
581
582 void GuiLyXFiles::paramsToDialog()
583 {
584         if (type_ == "examples")
585                 setTitle(qt_("Open Example File"));
586         else if (type_ == "templates")
587                 setTitle(qt_("New File From Template"));
588         else
589                 setTitle(qt_("Open File"));
590
591         bc().setValid(isValid());
592 }
593
594
595 void GuiLyXFiles::dispatchParams()
596 {
597         if (file_.isEmpty())
598                 return;
599
600         string arg;
601         if (type_ == "templates")
602                 arg = "newfile ";
603         arg += fromqstr(file_);
604         FuncCode const lfun = getLfun();
605
606         if (lfun == LFUN_NOACTION)
607                 // emit signal
608                 fileSelected(file_);
609         else
610                 dispatch(FuncRequest(lfun, arg));
611 }
612
613
614 FuncCode GuiLyXFiles::getLfun() const
615 {
616         if (type_ == "examples")
617                 return LFUN_FILE_OPEN;
618         else if (type_ == "templates")
619                 return LFUN_BUFFER_NEW_TEMPLATE;
620         return LFUN_NOACTION;
621 }
622
623 Dialog * createGuiLyXFiles(GuiView & lv) { return new GuiLyXFiles(lv); }
624
625
626 } // namespace frontend
627 } // namespace lyx
628
629 #include "moc_GuiLyXFiles.cpp"