]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiLyXFiles.cpp
Fix duplicate language entry
[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 namespace {
42
43 QString const guiString(QString in)
44 {
45         // recode specially encoded chars in file names
46         return in.replace('_', ' ').replace("%26", "&").replace("%28", "(").replace("%29", ")");
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         
243         return ".lyx";
244 }
245
246
247 bool GuiLyXFiles::translateName() const
248 {
249         return (type_ == "templates" || type_ == "examples");
250 }
251
252
253 void GuiLyXFiles::changed_adaptor()
254 {
255         changed();
256 }
257
258
259 void GuiLyXFiles::on_fileTypeCO_activated(int)
260 {
261         updateContents();
262 }
263
264
265 void GuiLyXFiles::on_languageCO_activated(int i)
266 {
267         savelang_ = languageCO->itemData(i).toString();
268         if (!filesLW->currentItem())
269                 return;
270
271         filesLW->currentItem()->setData(0, Qt::ToolTipRole, getRealPath());
272         changed();
273 }
274
275
276 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem * item, int)
277 {
278         if (!item->data(0, Qt::UserRole).toString().endsWith(getSuffix()))
279                 // not a file (probably a header)
280                 return;
281
282         applyView();
283         dispatchParams();
284         close();
285 }
286
287 void GuiLyXFiles::on_filesLW_itemClicked(QTreeWidgetItem * item, int)
288 {
289         QString const data = item->data(0, Qt::UserRole).toString();
290         if (!data.endsWith(getSuffix()))
291                 // not a file (probably a header)
292                 return;
293
294         languageCO->clear();
295         QMap<QString, QString>::const_iterator i =available_languages_.constBegin();
296         while (i != available_languages_.constEnd()) {
297                 if (localizations_.contains(data)
298                     && localizations_.find(data).value().contains(i.key()))
299                         languageCO->addItem(i.value(), i.key());
300                 ++i;
301         }
302         languageLA->setText(qt_("File &Language:"));
303         languageCO->setToolTip(qt_("All available languages of the selected file are displayed here.\n"
304                                    "The selected language version will be opened."));
305         setLanguage();
306         QString const realpath = getRealPath();
307         filesLW->currentItem()->setData(0, Qt::ToolTipRole, realpath);
308         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
309         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
310         QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
311                         user_icon : system_icon;
312         item->setIcon(0, file_icon);
313 }
314
315
316 void GuiLyXFiles::setLanguage()
317 {
318         // first try last setting
319         if (!savelang_.isEmpty()) {
320                 int index = languageCO->findData(savelang_);
321                 if (index != -1) {
322                         languageCO->setCurrentIndex(index);
323                         return;
324                 }
325         }
326         // next, try GUI lang
327         if (!guilang_.isEmpty()) {
328                 int index = languageCO->findData(guilang_);
329                 if (index != -1) {
330                         languageCO->setCurrentIndex(index);
331                         return;
332                 }
333         }
334         // Finally, fall back to English (which should be always there)
335         int index = languageCO->findData(toqstr("en"));
336         if (index != -1) {
337                 languageCO->setCurrentIndex(index);
338         }
339 }
340
341
342 void GuiLyXFiles::on_browsePB_pressed()
343 {
344         bool const examples = (type_ == "examples");
345         FileDialog dlg(qt_("Select template file"));
346         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
347         if (examples)
348                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
349         else
350                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
351
352         FileDialog::Result result = dlg.open(examples ? toqstr(lyxrc.example_path)
353                                                       : toqstr(lyxrc.template_path),
354                                  QStringList(qt_("LyX Documents (*.lyx)")));
355
356         if (result.first != FileDialog::Later && !result.second.isEmpty()) {
357                 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
358                 dispatchParams();
359                 close();
360         }
361 }
362
363
364 void GuiLyXFiles::updateContents()
365 {
366         languageCO->clear();
367         QMap<QString, QString> files = getFiles();
368         languageCO->model()->sort(0);
369
370         filesLW->clear();
371         QIcon user_icon(getPixmap("images/", "lyxfiles-user", "svgz,png"));
372         QIcon system_icon(getPixmap("images/", "lyxfiles-system", "svgz,png"));
373         QStringList cats;
374         QMap<QString, QString>::const_iterator it = files.constBegin();
375         QFont capfont;
376         capfont.setBold(true);
377         while (it != files.constEnd()) {
378                 QFileInfo const info = QFileInfo(it.key());
379                 QString const realpath = getRealPath(it.key());
380                 QString cat = it.value();
381                 QString subcat;
382                 QString catsave;
383                 if (cat.contains('/')) {
384                         catsave = cat;
385                         cat = catsave.left(catsave.indexOf('/'));
386                         subcat = toqstr(translateIfPossible(
387                                         qstring_to_ucs4(guiString(catsave.mid(catsave.indexOf('/') + 1)))));
388                 }
389                 cat =  toqstr(translateIfPossible(qstring_to_ucs4(guiString(cat))));
390                 QTreeWidgetItem * catItem = new QTreeWidgetItem();
391                 if (!cats.contains(cat)) {
392                         catItem->setText(0, cat);
393                         catItem->setFont(0, capfont);
394                         filesLW->insertTopLevelItem(0, catItem);
395                         catItem->setExpanded(true);
396                         cats << cat;
397                 } else
398                         catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
399                 QTreeWidgetItem * item = new QTreeWidgetItem();
400                 QString const filename = info.fileName();
401                 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
402                 if (translateName())
403                         guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiString(guiname))));
404                 QIcon file_icon = (realpath.startsWith(toqstr(package().user_support().absFileName()))) ?
405                                 user_icon : system_icon;
406                 item->setIcon(0, file_icon);
407                 item->setData(0, Qt::UserRole, it.key());
408                 item->setData(0, Qt::DisplayRole, guiname);
409                 item->setData(0, Qt::ToolTipRole, realpath);
410                 if (subcat.isEmpty())
411                         catItem->addChild(item);
412                 else {
413                         QTreeWidgetItem * subcatItem = new QTreeWidgetItem();
414                         if (cats.contains(catsave)) {
415                                 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
416                                 for (int iit = 0; iit < pcats.size(); ++iit) {
417                                         for (int cit = 0; cit < pcats.at(iit)->childCount(); ++cit) {
418                                                 if (pcats.at(iit)->child(cit)->text(0) == subcat) {
419                                                         subcatItem = pcats.at(iit)->child(cit);
420                                                         break;
421                                                 }
422                                         }
423                                 }
424                         } else {
425                                 subcatItem->setText(0, subcat);
426                                 subcatItem->setIcon(0, file_icon);
427                                 cats << catsave;
428                         }
429                         subcatItem->addChild(item);
430                         catItem->addChild(subcatItem);
431                 }
432                 ++it;
433         }
434         filesLW->sortItems(0, Qt::AscendingOrder);
435         // redo filter
436         filterLabels();
437 }
438
439
440 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
441 {
442         switch (buttonBox->standardButton(button)) {
443         case QDialogButtonBox::Open:
444                 slotOK();
445                 break;
446         case QDialogButtonBox::Cancel:
447                 slotClose();
448                 break;
449         default:
450                 break;
451         }
452 }
453
454
455 void GuiLyXFiles::filterLabels()
456 {
457         Qt::CaseSensitivity cs = csFindCB->isChecked() ?
458                 Qt::CaseSensitive : Qt::CaseInsensitive;
459         QTreeWidgetItemIterator it(filesLW);
460         while (*it) {
461                 (*it)->setHidden(
462                         (*it)->childCount() == 0
463                         && !(*it)->text(0).contains(filter_->text(), cs)
464                 );
465                 ++it;
466         }
467 }
468
469
470 void GuiLyXFiles::resetFilter()
471 {
472         filter_->setText(QString());
473         filterLabels();
474 }
475
476 QString const GuiLyXFiles::getRealPath(QString relpath)
477 {
478         if (relpath.isEmpty())
479                 relpath = filesLW->currentItem()->data(0, Qt::UserRole).toString();
480         QString const language = languageCO->itemData(languageCO->currentIndex()).toString();
481         if (localizations_.contains(relpath)) {
482                 if (localizations_.find(relpath).value().contains(language))
483                         return localizations_.find(relpath).value().find(language).value();
484                 else if (localizations_.find(relpath).value().contains(guilang_))
485                         return localizations_.find(relpath).value().find(guilang_).value();
486                 else if (localizations_.find(relpath).value().contains(toqstr("en")))
487                         return localizations_.find(relpath).value().find(toqstr("en")).value();
488         }
489         return QString();
490 }
491
492
493 void GuiLyXFiles::applyView()
494 {
495         file_ = getRealPath();
496 }
497
498
499 bool GuiLyXFiles::isValid()
500 {
501         return filesLW->currentItem() && filesLW->currentItem()->isSelected();
502 }
503
504
505 bool GuiLyXFiles::initialiseParams(string const & type)
506 {
507         type_ = type.empty() ? toqstr("templates") : toqstr(type);
508         paramsToDialog();
509         return true;
510 }
511
512
513 void GuiLyXFiles::paramsToDialog()
514 {
515         if (type_ == "examples")
516                 setTitle(qt_("Open Example File"));
517         else if (type_ == "templates")
518                 setTitle(qt_("New File From Template"));
519         else
520                 setTitle(qt_("Open File"));
521
522         bc().setValid(isValid());
523 }
524
525
526 void GuiLyXFiles::dispatchParams()
527 {
528         if (file_.isEmpty())
529                 return;
530
531         string arg;
532         if (type_ == "templates")
533                 arg = "newfile ";
534         arg += fromqstr(file_);
535         FuncCode const lfun = getLfun();
536
537         dispatch(FuncRequest(lfun, arg));
538 }
539
540
541 FuncCode GuiLyXFiles::getLfun() const
542 {
543         if (type_ == "examples")
544                 return LFUN_FILE_OPEN;
545         else if (type_ == "templates")
546                 return LFUN_BUFFER_NEW_TEMPLATE;
547         return LFUN_NOACTION;
548 }
549
550 Dialog * createGuiLyXFiles(GuiView & lv) { return new GuiLyXFiles(lv); }
551
552
553 } // namespace frontend
554 } // namespace lyx
555
556 #include "moc_GuiLyXFiles.cpp"