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