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