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