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