1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <vcl/svapp.hxx>
21
22 #include "kde5_filepicker.hxx"
23
24 #include <KWindowSystem>
25 #include <KFileWidget>
26
27 #include <QtCore/QDebug>
28 #include <QtCore/QUrl>
29 #include <QtWidgets/QCheckBox>
30 #include <QtWidgets/QFileDialog>
31 #include <QtWidgets/QGridLayout>
32 #include <QtWidgets/QWidget>
33 #include <QtWidgets/QApplication>
34
35 // KDE5FilePicker
36
KDE5FilePicker(QObject * parent)37 KDE5FilePicker::KDE5FilePicker(QObject* parent)
38 : QObject(parent)
39 , _dialog(new QFileDialog(nullptr, {}, QDir::homePath()))
40 , _extraControls(new QWidget)
41 , _layout(new QGridLayout(_extraControls))
42 , _winId(0)
43 {
44 _dialog->setSupportedSchemes({
45 QStringLiteral("file"), QStringLiteral("ftp"), QStringLiteral("http"),
46 QStringLiteral("https"), QStringLiteral("webdav"), QStringLiteral("webdavs"),
47 QStringLiteral("smb"),
48 QStringLiteral(""), // this makes removable devices shown
49 });
50
51 setMultiSelectionMode(false);
52
53 connect(_dialog, &QFileDialog::filterSelected, this, &KDE5FilePicker::filterChanged);
54 connect(_dialog, &QFileDialog::fileSelected, this, &KDE5FilePicker::selectionChanged);
55
56 setupCustomWidgets();
57 }
58
enableFolderMode()59 void KDE5FilePicker::enableFolderMode()
60 {
61 _dialog->setOption(QFileDialog::ShowDirsOnly, true);
62 // Workaround for https://bugs.kde.org/show_bug.cgi?id=406464 :
63 // Don't set file mode to QFileDialog::Directory when native KDE Plasma 5
64 // file dialog is used, since clicking on directory "bar" inside directory "foo"
65 // and then confirming would return "foo" rather than "foo/bar";
66 // on the other hand, non-native file dialog needs 'QFileDialog::Directory'
67 // and doesn't allow folder selection otherwise
68 if (Application::GetDesktopEnvironment() != "PLASMA5")
69 {
70 _dialog->setFileMode(QFileDialog::Directory);
71 }
72 }
73
~KDE5FilePicker()74 KDE5FilePicker::~KDE5FilePicker()
75 {
76 delete _extraControls;
77 delete _dialog;
78 }
79
setTitle(const QString & title)80 void KDE5FilePicker::setTitle(const QString& title) { _dialog->setWindowTitle(title); }
81
execute()82 bool KDE5FilePicker::execute()
83 {
84 if (!_filters.isEmpty())
85 _dialog->setNameFilters(_filters);
86 if (!_currentFilter.isEmpty())
87 _dialog->selectNameFilter(_currentFilter);
88
89 _dialog->show();
90 //block and wait for user input
91 return _dialog->exec() == QFileDialog::Accepted;
92 }
93
setMultiSelectionMode(bool multiSelect)94 void KDE5FilePicker::setMultiSelectionMode(bool multiSelect)
95 {
96 if (_dialog->acceptMode() == QFileDialog::AcceptSave)
97 return;
98
99 _dialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
100 }
101
setDefaultName(const QString & name)102 void KDE5FilePicker::setDefaultName(const QString& name) { _dialog->selectFile(name); }
103
setDisplayDirectory(const QString & dir)104 void KDE5FilePicker::setDisplayDirectory(const QString& dir)
105 {
106 _dialog->setDirectoryUrl(QUrl(dir));
107 }
108
getDisplayDirectory() const109 QString KDE5FilePicker::getDisplayDirectory() const { return _dialog->directoryUrl().url(); }
110
getSelectedFiles() const111 QList<QUrl> KDE5FilePicker::getSelectedFiles() const { return _dialog->selectedUrls(); }
112
appendFilter(const QString & title,const QString & filter)113 void KDE5FilePicker::appendFilter(const QString& title, const QString& filter)
114 {
115 QString t = title;
116 QString f = filter;
117 // '/' need to be escaped else they are assumed to be mime types by kfiledialog
118 //see the docs
119 t.replace("/", "\\/");
120
121 // openoffice gives us filters separated by ';' qt dialogs just want space separated
122 f.replace(";", " ");
123
124 // make sure "*.*" is not used as "all files"
125 f.replace("*.*", "*");
126
127 _filters << QStringLiteral("%1 (%2)").arg(t, f);
128 _titleToFilters[t] = _filters.constLast();
129 }
130
setCurrentFilter(const QString & title)131 void KDE5FilePicker::setCurrentFilter(const QString& title)
132 {
133 _currentFilter = _titleToFilters.value(title);
134 }
135
getCurrentFilter() const136 QString KDE5FilePicker::getCurrentFilter() const
137 {
138 QString filter = _titleToFilters.key(_dialog->selectedNameFilter());
139
140 //default if not found
141 if (filter.isEmpty())
142 filter = "ODF Text Document (.odt)";
143
144 return filter;
145 }
146
setValue(sal_Int16 controlId,sal_Int16,bool value)147 void KDE5FilePicker::setValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/, bool value)
148 {
149 if (_customWidgets.contains(controlId))
150 {
151 QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
152 if (cb)
153 cb->setChecked(value);
154 }
155 else
156 qWarning() << "set value on unknown control" << controlId;
157 }
158
getValue(sal_Int16 controlId,sal_Int16) const159 bool KDE5FilePicker::getValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/) const
160 {
161 bool ret = false;
162 if (_customWidgets.contains(controlId))
163 {
164 QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
165 if (cb)
166 ret = cb->isChecked();
167 }
168 else
169 qWarning() << "get value on unknown control" << controlId;
170
171 return ret;
172 }
173
enableControl(sal_Int16 controlId,bool enable)174 void KDE5FilePicker::enableControl(sal_Int16 controlId, bool enable)
175 {
176 if (_customWidgets.contains(controlId))
177 _customWidgets.value(controlId)->setEnabled(enable);
178 else
179 qWarning() << "enable on unknown control" << controlId;
180 }
181
setLabel(sal_Int16 controlId,const QString & label)182 void KDE5FilePicker::setLabel(sal_Int16 controlId, const QString& label)
183 {
184 if (_customWidgets.contains(controlId))
185 {
186 QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
187 if (cb)
188 cb->setText(label);
189 }
190 else
191 qWarning() << "set label on unknown control" << controlId;
192 }
193
getLabel(sal_Int16 controlId) const194 QString KDE5FilePicker::getLabel(sal_Int16 controlId) const
195 {
196 QString label;
197 if (_customWidgets.contains(controlId))
198 {
199 QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
200 if (cb)
201 label = cb->text();
202 }
203 else
204 qWarning() << "get label on unknown control" << controlId;
205
206 return label;
207 }
208
addCheckBox(sal_Int16 controlId,const QString & label,bool hidden)209 void KDE5FilePicker::addCheckBox(sal_Int16 controlId, const QString& label, bool hidden)
210 {
211 auto resString = label;
212 resString.replace('~', '&');
213
214 auto widget = new QCheckBox(resString, _extraControls);
215 widget->setHidden(hidden);
216 if (!hidden)
217 {
218 _layout->addWidget(widget);
219 }
220 _customWidgets.insert(controlId, widget);
221 }
222
initialize(bool saveDialog)223 void KDE5FilePicker::initialize(bool saveDialog)
224 {
225 //default is opening
226 QFileDialog::AcceptMode operationMode
227 = saveDialog ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen;
228
229 _dialog->setAcceptMode(operationMode);
230
231 if (saveDialog)
232 {
233 _dialog->setConfirmOverwrite(true);
234 _dialog->setFileMode(QFileDialog::AnyFile);
235 }
236 }
237
setWinId(sal_uIntPtr winId)238 void KDE5FilePicker::setWinId(sal_uIntPtr winId) { _winId = winId; }
239
setupCustomWidgets()240 void KDE5FilePicker::setupCustomWidgets()
241 {
242 // When using the platform-native Plasma/KDE5 file picker, we currently rely on KFileWidget
243 // being present to add the custom controls visible (s. 'eventFilter' method).
244 // Since this doesn't work for other desktop environments, use a non-native
245 // dialog there in order not to lose the custom controls and insert the custom
246 // widget in the layout returned by QFileDialog::layout()
247 // (which returns nullptr for native file dialogs)
248 if (Application::GetDesktopEnvironment() == "PLASMA5")
249 {
250 qApp->installEventFilter(this);
251 }
252 else
253 {
254 _dialog->setOption(QFileDialog::DontUseNativeDialog);
255 QGridLayout* pLayout = static_cast<QGridLayout*>(_dialog->layout());
256 assert(pLayout);
257 const int row = pLayout->rowCount();
258 pLayout->addWidget(_extraControls, row, 1);
259 }
260 }
261
eventFilter(QObject * o,QEvent * e)262 bool KDE5FilePicker::eventFilter(QObject* o, QEvent* e)
263 {
264 if (e->type() == QEvent::Show && o->isWidgetType())
265 {
266 auto* w = static_cast<QWidget*>(o);
267 if (!w->parentWidget() && w->isModal())
268 {
269 /*
270 To replace when baseline will include kwindowsystem >= 5.62 with:
271 w->setAttribute(Qt::WA_NativeWindow, true);
272 KWindowSystem::setMainWindow(w->windowHandle(), _winId);
273 */
274 SAL_WNODEPRECATED_DECLARATIONS_PUSH
275 KWindowSystem::setMainWindow(w, _winId);
276 SAL_WNODEPRECATED_DECLARATIONS_POP
277 if (auto* fileWidget = w->findChild<KFileWidget*>({}, Qt::FindDirectChildrenOnly))
278 {
279 fileWidget->setCustomWidget(_extraControls);
280 // remove event filter again; the only purpose was to set the custom widget here
281 qApp->removeEventFilter(this);
282 }
283 }
284 }
285 return QObject::eventFilter(o, e);
286 }
287
288 #include <kde5_filepicker.moc>
289
290 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
291