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