]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiLyXFiles.cpp
Amend 0c8eea0fe526e
[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 <QFileIconProvider>
34 #include <QTreeWidget>
35
36 using namespace std;
37 using namespace lyx::support;
38
39 namespace lyx {
40 namespace frontend {
41
42
43 void GuiLyXFiles::getFiles(QMap<QString, QString> & in, QString const type)
44 {
45         // We look for lyx files in the subdirectory dir of
46         //   1) user_lyxdir
47         //   2) build_lyxdir (if not empty)
48         //   3) system_lyxdir
49         // in this order. Files with a given sub-hierarchy will
50         // only be listed once.
51         // We also consider i18n subdirectories and prefer them.
52         QStringList dirs;
53         QStringList relpaths;
54
55         // The three locations to look at.
56         string const user = addPath(package().user_support().absFileName(), fromqstr(type));
57         string const build = addPath(package().build_support().absFileName(), fromqstr(type));
58         string const system = addPath(package().system_support().absFileName(), fromqstr(type));
59
60         // First, query the current language subdir (except for English)
61         QString const lang = languageCO->itemData(languageCO->currentIndex()).toString();
62         if (!lang.startsWith("en")) {
63                 // First try with the full code
64                 dirs << toqstr(addPath(user, fromqstr(lang)));
65                 dirs << toqstr(addPath(build, fromqstr(lang)));
66                 dirs << toqstr(addPath(system, fromqstr(lang)));
67                 // Then the name without country code
68                 QString const shortl = lang.left(lang.indexOf('_'));
69                 if (shortl != lang) {
70                         dirs << toqstr(addPath(user, fromqstr(shortl)));
71                         dirs << toqstr(addPath(build, fromqstr(shortl)));
72                         dirs << toqstr(addPath(system, fromqstr(shortl)));
73                 }
74         }
75
76         // Next, search in the base path
77         dirs << toqstr(user)
78              << 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 const 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                         if (s != -1) {
94                                 // <cat>/<subcat>/
95                                 cat = relpath.left(s);
96                                 int sc = relpath.indexOf('/', s + 1);
97                                 QString const subcat = (sc == -1) ?
98                                                         QString() : relpath.mid(s + 1, sc - s - 1);
99                                 if (langcodes_.contains(cat)
100                                     && !langcodes_.contains(dir.right(dir.lastIndexOf('/'))))
101                                         // Skip i18n dir
102                                         continue;
103                                 if (!subcat.isEmpty())
104                                         cat += '/' + subcat;
105                         }
106                         if (!relpaths.contains(relpath)) {
107                                 relpaths.append(relpath);
108                                 in.insert(fn, cat);
109                         }
110                 }
111         }
112 }
113
114
115 GuiLyXFiles::GuiLyXFiles(GuiView & lv)
116         : GuiDialog(lv, "lyxfiles", qt_("New File From Template"))
117 {
118         setupUi(this);
119
120         // Get all supported languages (by code) in order to exclude those
121         // dirs later.
122         QAbstractItemModel * language_model = guiApp->languageModel();
123         language_model->sort(0);
124         for (int i = 0; i != language_model->rowCount(); ++i) {
125                 QModelIndex index = language_model->index(i, 0);
126                 Language const * lang =
127                         languages.getLanguage(fromqstr(index.data(Qt::UserRole).toString()));
128                 if (!lang)
129                         continue;
130                 string const code = lang->code();
131                 languageCO->addItem(qt_(lang->display()), toqstr(code));
132                 langcodes_ << toqstr(code);
133                 // Also store code without country code
134                 string const shortcode = token(code, '_', 0);
135                 if (shortcode != code)
136                         langcodes_ << toqstr(shortcode);
137         }
138         // Preset to GUI language
139         string lang = getGuiMessages().language();
140         string const language = getEnv("LANGUAGE");
141         if (!language.empty())
142                 lang += ":" + language;
143
144         for (auto const & l : getVectorFromString(lang, ":")) {
145                 // First try with the full name
146                 // `en' files are not in a subdirectory
147                 int i = languageCO->findData(toqstr(l));
148                 if (i != -1) {
149                         languageCO->setCurrentIndex(i);
150                         break;
151                 }
152                 // Then the name without country code
153                 string const shortl = token(l, '_', 0);
154                 i = languageCO->findData(toqstr(l));
155                 if (i != -1) {
156                         languageCO->setCurrentIndex(i);
157                         break;
158                 }
159         }
160
161         // The filter bar
162         filter_ = new FancyLineEdit(this);
163         filter_->setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
164         filter_->setButtonVisible(FancyLineEdit::Right, true);
165         filter_->setButtonToolTip(FancyLineEdit::Right, qt_("Clear text"));
166         filter_->setAutoHideButton(FancyLineEdit::Right, true);
167         filter_->setPlaceholderText(qt_("All available files"));
168         filter_->setToolTip(qt_("Enter string to filter the list of available files"));
169 #if (QT_VERSION < 0x050000)
170         connect(filter_, SIGNAL(downPressed()),
171                 filesLW, SLOT(setFocus()));
172 #else
173         connect(filter_, &FancyLineEdit::downPressed,
174                 filesLW, [=](){ focusAndHighlight(filesLW); });
175 #endif
176
177         filterBarL->addWidget(filter_, 0);
178         findKeysLA->setBuddy(filter_);
179
180         connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
181                 this, SLOT(slotButtonBox(QAbstractButton *)));
182
183         connect(filesLW, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
184                 this, SLOT(changed_adaptor()));
185         connect(filesLW, SIGNAL(itemSelectionChanged()),
186                 this, SLOT(changed_adaptor()));
187         connect(filter_, SIGNAL(textEdited(QString)),
188                 this, SLOT(filterLabels()));
189         connect(filter_, SIGNAL(rightButtonClicked()),
190                 this, SLOT(resetFilter()));
191
192         bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
193         bc().setOK(buttonBox->button(QDialogButtonBox::Open));
194         bc().setCancel(buttonBox->button(QDialogButtonBox::Cancel));
195
196         //filesLW->setViewMode(QListView::ListMode);
197         filesLW->setIconSize(QSize(22, 22));
198
199         setFocusProxy(filter_);
200 }
201
202
203 QString const GuiLyXFiles::getSuffix()
204 {
205         if (type_ == "bind" || type_ == "ui")
206                 return toqstr(".") + type_;
207         
208         return ".lyx";
209 }
210
211
212 bool GuiLyXFiles::translateName() const
213 {
214         return (type_ == "templates" || type_ == "examples");
215 }
216
217
218 void GuiLyXFiles::changed_adaptor()
219 {
220         changed();
221 }
222
223
224 void GuiLyXFiles::on_fileTypeCO_activated(int)
225 {
226         updateContents();
227 }
228
229
230 void GuiLyXFiles::on_languageCO_activated(int)
231 {
232         updateContents();
233 }
234
235
236 void GuiLyXFiles::on_filesLW_itemDoubleClicked(QTreeWidgetItem *, int)
237 {
238         applyView();
239         dispatchParams();
240         close();
241 }
242
243
244 void GuiLyXFiles::on_browsePB_pressed()
245 {
246         bool const examples = (type_ == "examples");
247         FileDialog dlg(qt_("Select template file"));
248         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
249         if (examples)
250                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
251         else
252                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
253
254         FileDialog::Result result = dlg.open(examples ? toqstr(lyxrc.example_path)
255                                                       : toqstr(lyxrc.template_path),
256                                  QStringList(qt_("LyX Documents (*.lyx)")));
257
258         if (result.first != FileDialog::Later && !result.second.isEmpty()) {
259                 file_ = toqstr(FileName(fromqstr(result.second)).absFileName());
260                 dispatchParams();
261                 close();
262         }
263 }
264
265
266 void GuiLyXFiles::updateContents()
267 {
268         QString type = fileTypeCO->itemData(fileTypeCO->currentIndex()).toString();
269         QMap<QString, QString> files;
270         getFiles(files, type);
271
272         filesLW->clear();
273         QFileIconProvider iconprovider;
274         QStringList cats;
275         QMap<QString, QString>::const_iterator it = files.constBegin();
276         QFont capfont;
277         capfont.setBold(true);
278         while (it != files.constEnd()) {
279                 QFileInfo const info = QFileInfo(it.key());
280                 QString cat = it.value();
281                 QString subcat;
282                 QString catsave;
283                 if (cat.contains('/')) {
284                         catsave = cat;
285                         cat = catsave.left(catsave.indexOf('/'));
286                         subcat = toqstr(translateIfPossible(
287                                         qstring_to_ucs4(catsave.mid(
288                                                 catsave.indexOf('/') + 1).replace('_', ' '))));
289                 }
290                 cat =  toqstr(translateIfPossible(qstring_to_ucs4(cat.replace('_', ' '))));
291                 QTreeWidgetItem * catItem = new QTreeWidgetItem();
292                 if (!cats.contains(cat)) {
293                         catItem->setText(0, cat);
294                         catItem->setFont(0, capfont);
295                         filesLW->insertTopLevelItem(0, catItem);
296                         catItem->setExpanded(true);
297                         cats << cat;
298                 } else
299                         catItem = filesLW->findItems(cat, Qt::MatchExactly).first();
300                 QTreeWidgetItem * item = new QTreeWidgetItem();
301                 QString const filename = info.fileName();
302                 QString guiname = filename.left(filename.lastIndexOf(getSuffix())).replace('_', ' ');
303                 if (translateName())
304                         guiname = toqstr(translateIfPossible(qstring_to_ucs4(guiname)));
305                 item->setIcon(0, iconprovider.icon(info));
306                 item->setData(0, Qt::UserRole, info.filePath());
307                 item->setData(0, Qt::DisplayRole, guiname);
308                 item->setData(0, Qt::ToolTipRole, info.filePath());
309                 if (subcat.isEmpty())
310                         catItem->addChild(item);
311                 else {
312                         QTreeWidgetItem * subcatItem = new QTreeWidgetItem();
313                         if (cats.contains(catsave)) {
314                                 QList<QTreeWidgetItem *> pcats = filesLW->findItems(cat, Qt::MatchExactly);
315                                 for (int iit = 0; iit < pcats.size(); ++iit) {
316                                         for (int cit = 0; cit < pcats.at(iit)->childCount(); ++cit) {
317                                                 if (pcats.at(iit)->child(cit)->text(0) == subcat) {
318                                                         subcatItem = pcats.at(iit)->child(cit);
319                                                         break;
320                                                 }
321                                         }
322                                 }
323                         } else {
324                                 subcatItem->setText(0, subcat);
325                                 cats << catsave;
326                         }
327                         subcatItem->addChild(item);
328                         catItem->addChild(subcatItem);
329                 }
330                 ++it;
331         }
332         filesLW->sortItems(0, Qt::AscendingOrder);
333         // redo filter
334         filterLabels();
335 }
336
337
338 void GuiLyXFiles::slotButtonBox(QAbstractButton * button)
339 {
340         switch (buttonBox->standardButton(button)) {
341         case QDialogButtonBox::Open:
342                 slotOK();
343                 break;
344         case QDialogButtonBox::Cancel:
345                 slotClose();
346                 break;
347         default:
348                 break;
349         }
350 }
351
352
353 void GuiLyXFiles::filterLabels()
354 {
355         Qt::CaseSensitivity cs = csFindCB->isChecked() ?
356                 Qt::CaseSensitive : Qt::CaseInsensitive;
357         QTreeWidgetItemIterator it(filesLW);
358         while (*it) {
359                 (*it)->setHidden(
360                         (*it)->childCount() == 0
361                         && !(*it)->text(0).contains(filter_->text(), cs)
362                 );
363                 ++it;
364         }
365 }
366
367
368 void GuiLyXFiles::resetFilter()
369 {
370         filter_->setText(QString());
371         filterLabels();
372 }
373
374
375 void GuiLyXFiles::applyView()
376 {
377         file_ = filesLW->currentItem()->data(0, Qt::UserRole).toString();
378 }
379
380
381 bool GuiLyXFiles::isValid()
382 {
383         return filesLW->currentItem() && filesLW->currentItem()->isSelected();
384 }
385
386
387 bool GuiLyXFiles::initialiseParams(string const & type)
388 {
389         type_ = type.empty() ? toqstr("templates") : toqstr(type);
390         paramsToDialog();
391         return true;
392 }
393
394
395 void GuiLyXFiles::paramsToDialog()
396 {
397         fileTypeCO->clear();
398         if (type_ == "examples" || type_ == "templates") {
399                 fileTypeCO->addItem(qt_("Templates"), toqstr("templates"));
400                 fileTypeCO->addItem(qt_("Examples"), toqstr("examples"));
401         } else if (type_ == "ui")
402                 fileTypeCO->addItem(qt_("User Interface Files"), toqstr("ui"));
403         else if (type_ == "bind")
404                 fileTypeCO->addItem(qt_("Key Binding Files"), toqstr("bind"));
405
406         if (!type_.isEmpty()) {
407                 int i = fileTypeCO->findData(type_);
408                 if (i != -1)
409                         fileTypeCO->setCurrentIndex(i);
410         }
411         if (type_ == "examples")
412                 setTitle(qt_("Open Example File"));
413         else if (type_ == "templates")
414                 setTitle(qt_("New File From Template"));
415         else
416                 setTitle(qt_("Open File"));
417
418         bc().setValid(isValid());
419 }
420
421
422 void GuiLyXFiles::dispatchParams()
423 {
424         if (file_.isEmpty())
425                 return;
426
427         string arg;
428         if (type_ == "templates")
429                 arg = "newfile ";
430         arg += fromqstr(file_);
431         FuncCode const lfun = getLfun();
432
433         dispatch(FuncRequest(lfun, arg));
434 }
435
436
437 FuncCode GuiLyXFiles::getLfun() const
438 {
439         if (type_ == "examples")
440                 return LFUN_FILE_OPEN;
441         else if (type_ == "templates")
442                 return LFUN_BUFFER_NEW_TEMPLATE;
443         return LFUN_NOACTION;
444 }
445
446 Dialog * createGuiLyXFiles(GuiView & lv) { return new GuiLyXFiles(lv); }
447
448
449 } // namespace frontend
450 } // namespace lyx
451
452 #include "moc_GuiLyXFiles.cpp"