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