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