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 <Qt5FilePicker.hxx>
21 #include <Qt5FilePicker.moc>
22
23 #include <Qt5Frame.hxx>
24 #include <Qt5Tools.hxx>
25 #include <Qt5Widget.hxx>
26 #include <Qt5Instance.hxx>
27
28 #include <com/sun/star/awt/SystemDependentXWindow.hpp>
29 #include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
30 #include <com/sun/star/awt/XWindow.hpp>
31 #include <com/sun/star/frame/Desktop.hpp>
32 #include <com/sun/star/frame/TerminationVetoException.hpp>
33 #include <com/sun/star/frame/XDesktop.hpp>
34 #include <com/sun/star/lang/DisposedException.hpp>
35 #include <com/sun/star/lang/IllegalArgumentException.hpp>
36 #include <com/sun/star/lang/SystemDependent.hpp>
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
38 #include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
39 #include <com/sun/star/ui/dialogs/ControlActions.hpp>
40 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
41 #include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
42 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
43 #include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
44 #include <cppuhelper/interfacecontainer.h>
45 #include <cppuhelper/supportsservice.hxx>
46 #include <rtl/process.h>
47 #include <sal/log.hxx>
48
49 #include <QtCore/QDebug>
50 #include <QtCore/QRegularExpression>
51 #include <QtCore/QThread>
52 #include <QtCore/QUrl>
53 #include <QtGui/QClipboard>
54 #include <QtGui/QWindow>
55 #include <QtWidgets/QApplication>
56 #include <QtWidgets/QCheckBox>
57 #include <QtWidgets/QComboBox>
58 #include <QtWidgets/QGridLayout>
59 #include <QtWidgets/QHBoxLayout>
60 #include <QtWidgets/QLabel>
61 #include <QtWidgets/QMessageBox>
62 #include <QtWidgets/QPushButton>
63 #include <QtWidgets/QWidget>
64
65 #include <unx/geninst.h>
66 #include <strings.hrc>
67
68 using namespace ::com::sun::star;
69 using namespace ::com::sun::star::ui::dialogs;
70 using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
71 using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
72 using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
73 using namespace ::com::sun::star::lang;
74 using namespace ::com::sun::star::beans;
75 using namespace ::com::sun::star::uno;
76
77 namespace
78 {
FilePicker_getSupportedServiceNames()79 uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
80 {
81 return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
82 "com.sun.star.ui.dialogs.Qt5FilePicker" };
83 }
84 }
85
Qt5FilePicker(css::uno::Reference<css::uno::XComponentContext> const & context,QFileDialog::FileMode eMode,bool bUseNative)86 Qt5FilePicker::Qt5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
87 QFileDialog::FileMode eMode, bool bUseNative)
88 : Qt5FilePicker_Base(m_aHelperMutex)
89 , m_context(context)
90 , m_bIsFolderPicker(eMode == QFileDialog::Directory)
91 , m_pParentWidget(nullptr)
92 , m_pFileDialog(new QFileDialog(nullptr, {}, QDir::homePath()))
93 , m_pExtraControls(new QWidget())
94 {
95 m_pFileDialog->setOption(QFileDialog::DontUseNativeDialog, !bUseNative);
96
97 m_pFileDialog->setFileMode(eMode);
98 m_pFileDialog->setWindowModality(Qt::ApplicationModal);
99
100 if (m_bIsFolderPicker)
101 {
102 m_pFileDialog->setOption(QFileDialog::ShowDirsOnly, true);
103 m_pFileDialog->setWindowTitle(toQString(VclResId(STR_FPICKER_FOLDER_DEFAULT_TITLE)));
104 }
105
106 m_pLayout = dynamic_cast<QGridLayout*>(m_pFileDialog->layout());
107
108 setMultiSelectionMode(false);
109
110 // XFilePickerListener notifications
111 connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
112 SLOT(filterSelected(const QString&)));
113 connect(m_pFileDialog.get(), SIGNAL(currentChanged(const QString&)), this,
114 SLOT(currentChanged(const QString&)));
115
116 // update automatic file extension when filter is changed
117 connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
118 SLOT(updateAutomaticFileExtension()));
119 }
120
~Qt5FilePicker()121 Qt5FilePicker::~Qt5FilePicker()
122 {
123 SolarMutexGuard g;
124 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
125 assert(pSalInst);
126 pSalInst->RunInMainThread([this]() {
127 // must delete it in main thread, otherwise
128 // QSocketNotifier::setEnabled() will crash us
129 m_pFileDialog.reset();
130 });
131 }
132
133 void SAL_CALL
addFilePickerListener(const uno::Reference<XFilePickerListener> & xListener)134 Qt5FilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
135 {
136 SolarMutexGuard aGuard;
137 m_xListener = xListener;
138 }
139
removeFilePickerListener(const uno::Reference<XFilePickerListener> &)140 void SAL_CALL Qt5FilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
141 {
142 SolarMutexGuard aGuard;
143 m_xListener.clear();
144 }
145
setTitle(const OUString & title)146 void SAL_CALL Qt5FilePicker::setTitle(const OUString& title)
147 {
148 SolarMutexGuard g;
149 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
150 assert(pSalInst);
151 pSalInst->RunInMainThread(
152 [this, &title]() { m_pFileDialog->setWindowTitle(toQString(title)); });
153 }
154
execute()155 sal_Int16 SAL_CALL Qt5FilePicker::execute()
156 {
157 SolarMutexGuard g;
158 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
159 assert(pSalInst);
160 if (!pSalInst->IsMainThread())
161 {
162 sal_uInt16 ret;
163 pSalInst->RunInMainThread([&ret, this]() { ret = execute(); });
164 return ret;
165 }
166
167 QWidget* pTransientParent = m_pParentWidget;
168 if (!pTransientParent)
169 {
170 vcl::Window* pWindow = ::Application::GetActiveTopWindow();
171 if (pWindow)
172 {
173 Qt5Frame* pFrame = dynamic_cast<Qt5Frame*>(pWindow->ImplGetFrame());
174 assert(pFrame);
175 if (pFrame)
176 pTransientParent = pFrame->asChild();
177 }
178 }
179
180 if (!m_aNamedFilterList.isEmpty())
181 m_pFileDialog->setNameFilters(m_aNamedFilterList);
182 if (!m_aCurrentFilter.isEmpty())
183 m_pFileDialog->selectNameFilter(m_aCurrentFilter);
184
185 updateAutomaticFileExtension();
186
187 uno::Reference<css::frame::XDesktop> xDesktop(css::frame::Desktop::create(m_context),
188 UNO_QUERY_THROW);
189
190 // will hide the window, so do before show
191 m_pFileDialog->setParent(pTransientParent, m_pFileDialog->windowFlags());
192 m_pFileDialog->show();
193 xDesktop->addTerminateListener(this);
194 int result = m_pFileDialog->exec();
195 xDesktop->removeTerminateListener(this);
196 m_pFileDialog->setParent(nullptr, m_pFileDialog->windowFlags());
197
198 if (QFileDialog::Rejected == result)
199 return ExecutableDialogResults::CANCEL;
200 return ExecutableDialogResults::OK;
201 }
202
setMultiSelectionMode(sal_Bool multiSelect)203 void SAL_CALL Qt5FilePicker::setMultiSelectionMode(sal_Bool multiSelect)
204 {
205 SolarMutexGuard g;
206 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
207 assert(pSalInst);
208 pSalInst->RunInMainThread([this, multiSelect]() {
209 if (m_bIsFolderPicker || m_pFileDialog->acceptMode() == QFileDialog::AcceptSave)
210 return;
211
212 m_pFileDialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles
213 : QFileDialog::ExistingFile);
214 });
215 }
216
setDefaultName(const OUString & name)217 void SAL_CALL Qt5FilePicker::setDefaultName(const OUString& name)
218 {
219 SolarMutexGuard g;
220 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
221 assert(pSalInst);
222 pSalInst->RunInMainThread([this, &name]() { m_pFileDialog->selectFile(toQString(name)); });
223 }
224
setDisplayDirectory(const OUString & dir)225 void SAL_CALL Qt5FilePicker::setDisplayDirectory(const OUString& dir)
226 {
227 SolarMutexGuard g;
228 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
229 assert(pSalInst);
230 pSalInst->RunInMainThread([this, &dir]() {
231 QString qDir(toQString(dir));
232 m_pFileDialog->setDirectoryUrl(QUrl(qDir));
233 });
234 }
235
getDisplayDirectory()236 OUString SAL_CALL Qt5FilePicker::getDisplayDirectory()
237 {
238 SolarMutexGuard g;
239 OUString ret;
240 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
241 assert(pSalInst);
242 pSalInst->RunInMainThread(
243 [&ret, this]() { ret = toOUString(m_pFileDialog->directoryUrl().toString()); });
244 return ret;
245 }
246
getFiles()247 uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getFiles()
248 {
249 uno::Sequence<OUString> seq = getSelectedFiles();
250 if (seq.getLength() > 1)
251 seq.realloc(1);
252 return seq;
253 }
254
getSelectedFiles()255 uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSelectedFiles()
256 {
257 SolarMutexGuard g;
258 QList<QUrl> urls;
259 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
260 assert(pSalInst);
261 pSalInst->RunInMainThread([&urls, this]() { urls = m_pFileDialog->selectedUrls(); });
262
263 uno::Sequence<OUString> seq(urls.size());
264
265 auto const trans = css::uri::ExternalUriReferenceTranslator::create(m_context);
266 size_t i = 0;
267 for (const QUrl& aURL : urls)
268 {
269 // Unlike LO, QFileDialog (<https://doc.qt.io/qt-5/qfiledialog.html>) apparently always
270 // treats file-system pathnames as UTF-8--encoded, regardless of LANG/LC_CTYPE locale
271 // setting. And pathnames containing byte sequences that are not valid UTF-8 are apparently
272 // filtered out and not even displayed by QFileDialog, so aURL will always have a "payload"
273 // that matches the pathname's byte sequence. So the pathname's byte sequence (which
274 // happens to also be aURL's payload) in the LANG/LC_CTYPE encoding needs to be converted
275 // into LO's internal UTF-8 file URL encoding via
276 // XExternalUriReferenceTranslator::translateToInternal (which looks somewhat paradoxical as
277 // aURL.toEncoded() nominally already has a UTF-8 payload):
278 auto const extUrl = toOUString(aURL.toEncoded());
279 auto intUrl = trans->translateToInternal(extUrl);
280 if (intUrl.isEmpty())
281 {
282 // If translation failed, fall back to original URL:
283 SAL_WARN("vcl.qt5", "cannot convert <" << extUrl << "> from locale encoding to UTF-8");
284 intUrl = extUrl;
285 }
286 seq[i++] = intUrl;
287 }
288
289 return seq;
290 }
291
appendFilter(const OUString & title,const OUString & filter)292 void SAL_CALL Qt5FilePicker::appendFilter(const OUString& title, const OUString& filter)
293 {
294 SolarMutexGuard g;
295 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
296 assert(pSalInst);
297 if (!pSalInst->IsMainThread())
298 {
299 pSalInst->RunInMainThread([this, &title, &filter]() { appendFilter(title, filter); });
300 return;
301 }
302
303 // '/' need to be escaped else they are assumed to be mime types
304 QString sTitle = toQString(title).replace("/", "\\/");
305
306 QString sFilterName = sTitle;
307 // the Qt5 non-native file picker adds the extensions to the filter title, so strip them
308 if (m_pFileDialog->testOption(QFileDialog::DontUseNativeDialog))
309 {
310 int pos = sFilterName.indexOf(" (");
311 if (pos >= 0)
312 sFilterName.truncate(pos);
313 }
314
315 QString sGlobFilter = toQString(filter);
316
317 // LibreOffice gives us filters separated by ';' qt dialogs just want space separated
318 sGlobFilter.replace(";", " ");
319
320 // make sure "*.*" is not used as "all files"
321 sGlobFilter.replace("*.*", "*");
322
323 m_aNamedFilterList << QStringLiteral("%1 (%2)").arg(sFilterName, sGlobFilter);
324 m_aTitleToFilterMap[sTitle] = m_aNamedFilterList.constLast();
325 m_aNamedFilterToExtensionMap[m_aNamedFilterList.constLast()] = sGlobFilter;
326 }
327
setCurrentFilter(const OUString & title)328 void SAL_CALL Qt5FilePicker::setCurrentFilter(const OUString& title)
329 {
330 SolarMutexGuard g;
331 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
332 assert(pSalInst);
333 pSalInst->RunInMainThread([this, &title]() {
334 m_aCurrentFilter = m_aTitleToFilterMap.value(toQString(title).replace("/", "\\/"));
335 });
336 }
337
getCurrentFilter()338 OUString SAL_CALL Qt5FilePicker::getCurrentFilter()
339 {
340 SolarMutexGuard g;
341 QString filter;
342 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
343 assert(pSalInst);
344 pSalInst->RunInMainThread([&filter, this]() {
345 filter = m_aTitleToFilterMap.key(m_pFileDialog->selectedNameFilter());
346 });
347
348 if (filter.isEmpty())
349 filter = "ODF Text Document (.odt)";
350 return toOUString(filter);
351 }
352
appendFilterGroup(const OUString & rGroupTitle,const uno::Sequence<beans::StringPair> & filters)353 void SAL_CALL Qt5FilePicker::appendFilterGroup(const OUString& rGroupTitle,
354 const uno::Sequence<beans::StringPair>& filters)
355 {
356 SolarMutexGuard g;
357 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
358 assert(pSalInst);
359 if (!pSalInst->IsMainThread())
360 {
361 pSalInst->RunInMainThread(
362 [this, &rGroupTitle, &filters]() { appendFilterGroup(rGroupTitle, filters); });
363 return;
364 }
365
366 const sal_uInt16 length = filters.getLength();
367 for (sal_uInt16 i = 0; i < length; ++i)
368 {
369 beans::StringPair aPair = filters[i];
370 appendFilter(aPair.First, aPair.Second);
371 }
372 }
373
handleGetListValue(const QComboBox * pWidget,sal_Int16 nControlAction)374 uno::Any Qt5FilePicker::handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction)
375 {
376 uno::Any aAny;
377 switch (nControlAction)
378 {
379 case ControlActions::GET_ITEMS:
380 {
381 Sequence<OUString> aItemList(pWidget->count());
382 for (sal_Int32 i = 0; i < pWidget->count(); ++i)
383 aItemList[i] = toOUString(pWidget->itemText(i));
384 aAny <<= aItemList;
385 break;
386 }
387 case ControlActions::GET_SELECTED_ITEM:
388 {
389 if (!pWidget->currentText().isEmpty())
390 aAny <<= toOUString(pWidget->currentText());
391 break;
392 }
393 case ControlActions::GET_SELECTED_ITEM_INDEX:
394 {
395 if (pWidget->currentIndex() >= 0)
396 aAny <<= static_cast<sal_Int32>(pWidget->currentIndex());
397 break;
398 }
399 default:
400 SAL_WARN("vcl.qt5",
401 "undocumented/unimplemented ControlAction for a list " << nControlAction);
402 break;
403 }
404 return aAny;
405 }
406
handleSetListValue(QComboBox * pWidget,sal_Int16 nControlAction,const uno::Any & rValue)407 void Qt5FilePicker::handleSetListValue(QComboBox* pWidget, sal_Int16 nControlAction,
408 const uno::Any& rValue)
409 {
410 switch (nControlAction)
411 {
412 case ControlActions::ADD_ITEM:
413 {
414 OUString sItem;
415 rValue >>= sItem;
416 pWidget->addItem(toQString(sItem));
417 break;
418 }
419 case ControlActions::ADD_ITEMS:
420 {
421 Sequence<OUString> aStringList;
422 rValue >>= aStringList;
423 for (auto const& sItem : std::as_const(aStringList))
424 pWidget->addItem(toQString(sItem));
425 break;
426 }
427 case ControlActions::DELETE_ITEM:
428 {
429 sal_Int32 nPos = 0;
430 rValue >>= nPos;
431 pWidget->removeItem(nPos);
432 break;
433 }
434 case ControlActions::DELETE_ITEMS:
435 {
436 pWidget->clear();
437 break;
438 }
439 case ControlActions::SET_SELECT_ITEM:
440 {
441 sal_Int32 nPos = 0;
442 rValue >>= nPos;
443 pWidget->setCurrentIndex(nPos);
444 break;
445 }
446 default:
447 SAL_WARN("vcl.qt5",
448 "undocumented/unimplemented ControlAction for a list " << nControlAction);
449 break;
450 }
451
452 pWidget->setEnabled(pWidget->count() > 0);
453 }
454
setValue(sal_Int16 controlId,sal_Int16 nControlAction,const uno::Any & value)455 void SAL_CALL Qt5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
456 const uno::Any& value)
457 {
458 SolarMutexGuard g;
459 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
460 assert(pSalInst);
461 if (!pSalInst->IsMainThread())
462 {
463 pSalInst->RunInMainThread([this, controlId, nControlAction, &value]() {
464 setValue(controlId, nControlAction, value);
465 });
466 return;
467 }
468
469 if (m_aCustomWidgetsMap.contains(controlId))
470 {
471 QWidget* widget = m_aCustomWidgetsMap.value(controlId);
472 QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
473 if (cb)
474 cb->setChecked(value.get<bool>());
475 else
476 {
477 QComboBox* combo = dynamic_cast<QComboBox*>(widget);
478 if (combo)
479 handleSetListValue(combo, nControlAction, value);
480 }
481 }
482 else
483 SAL_WARN("vcl.qt5", "set value on unknown control " << controlId);
484 }
485
getValue(sal_Int16 controlId,sal_Int16 nControlAction)486 uno::Any SAL_CALL Qt5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
487 {
488 SolarMutexGuard g;
489 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
490 assert(pSalInst);
491 if (!pSalInst->IsMainThread())
492 {
493 uno::Any ret;
494 pSalInst->RunInMainThread([&ret, this, controlId, nControlAction]() {
495 ret = getValue(controlId, nControlAction);
496 });
497 return ret;
498 }
499
500 uno::Any res(false);
501 if (m_aCustomWidgetsMap.contains(controlId))
502 {
503 QWidget* widget = m_aCustomWidgetsMap.value(controlId);
504 QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
505 if (cb)
506 res <<= cb->isChecked();
507 else
508 {
509 QComboBox* combo = dynamic_cast<QComboBox*>(widget);
510 if (combo)
511 res = handleGetListValue(combo, nControlAction);
512 }
513 }
514 else
515 SAL_WARN("vcl.qt5", "get value on unknown control " << controlId);
516
517 return res;
518 }
519
enableControl(sal_Int16 controlId,sal_Bool enable)520 void SAL_CALL Qt5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
521 {
522 SolarMutexGuard g;
523 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
524 assert(pSalInst);
525 pSalInst->RunInMainThread([this, controlId, enable]() {
526 if (m_aCustomWidgetsMap.contains(controlId))
527 m_aCustomWidgetsMap.value(controlId)->setEnabled(enable);
528 else
529 SAL_WARN("vcl.qt5", "enable unknown control " << controlId);
530 });
531 }
532
setLabel(sal_Int16 controlId,const OUString & label)533 void SAL_CALL Qt5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
534 {
535 SolarMutexGuard g;
536 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
537 assert(pSalInst);
538 if (!pSalInst->IsMainThread())
539 {
540 pSalInst->RunInMainThread([this, controlId, label]() { setLabel(controlId, label); });
541 return;
542 }
543
544 if (m_aCustomWidgetsMap.contains(controlId))
545 {
546 QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
547 if (cb)
548 cb->setText(toQString(label));
549 }
550 else
551 SAL_WARN("vcl.qt5", "set label on unknown control " << controlId);
552 }
553
getLabel(sal_Int16 controlId)554 OUString SAL_CALL Qt5FilePicker::getLabel(sal_Int16 controlId)
555 {
556 SolarMutexGuard g;
557 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
558 assert(pSalInst);
559 if (!pSalInst->IsMainThread())
560 {
561 OUString ret;
562 pSalInst->RunInMainThread([&ret, this, controlId]() { ret = getLabel(controlId); });
563 return ret;
564 }
565
566 QString label;
567 if (m_aCustomWidgetsMap.contains(controlId))
568 {
569 QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
570 if (cb)
571 label = cb->text();
572 }
573 else
574 SAL_WARN("vcl.qt5", "get label on unknown control " << controlId);
575
576 return toOUString(label);
577 }
578
getResString(const char * pResId)579 QString Qt5FilePicker::getResString(const char* pResId)
580 {
581 QString aResString;
582
583 if (pResId == nullptr)
584 return aResString;
585
586 aResString = toQString(VclResId(pResId));
587
588 return aResString.replace('~', '&');
589 }
590
addCustomControl(sal_Int16 controlId)591 void Qt5FilePicker::addCustomControl(sal_Int16 controlId)
592 {
593 QWidget* widget = nullptr;
594 QLabel* label = nullptr;
595 const char* resId = nullptr;
596 QCheckBox* pCheckbox = nullptr;
597
598 switch (controlId)
599 {
600 case CHECKBOX_AUTOEXTENSION:
601 resId = STR_FPICKER_AUTO_EXTENSION;
602 break;
603 case CHECKBOX_PASSWORD:
604 resId = STR_FPICKER_PASSWORD;
605 break;
606 case CHECKBOX_FILTEROPTIONS:
607 resId = STR_FPICKER_FILTER_OPTIONS;
608 break;
609 case CHECKBOX_READONLY:
610 resId = STR_FPICKER_READONLY;
611 break;
612 case CHECKBOX_LINK:
613 resId = STR_FPICKER_INSERT_AS_LINK;
614 break;
615 case CHECKBOX_PREVIEW:
616 resId = STR_FPICKER_SHOW_PREVIEW;
617 break;
618 case CHECKBOX_SELECTION:
619 resId = STR_FPICKER_SELECTION;
620 break;
621 case CHECKBOX_GPGENCRYPTION:
622 resId = STR_FPICKER_GPGENCRYPT;
623 break;
624 case PUSHBUTTON_PLAY:
625 resId = STR_FPICKER_PLAY;
626 break;
627 case LISTBOX_VERSION:
628 resId = STR_FPICKER_VERSION;
629 break;
630 case LISTBOX_TEMPLATE:
631 resId = STR_FPICKER_TEMPLATES;
632 break;
633 case LISTBOX_IMAGE_TEMPLATE:
634 resId = STR_FPICKER_IMAGE_TEMPLATE;
635 break;
636 case LISTBOX_IMAGE_ANCHOR:
637 resId = STR_FPICKER_IMAGE_ANCHOR;
638 break;
639 case LISTBOX_VERSION_LABEL:
640 case LISTBOX_TEMPLATE_LABEL:
641 case LISTBOX_IMAGE_TEMPLATE_LABEL:
642 case LISTBOX_IMAGE_ANCHOR_LABEL:
643 case LISTBOX_FILTER_SELECTOR:
644 break;
645 }
646
647 switch (controlId)
648 {
649 case CHECKBOX_AUTOEXTENSION:
650 pCheckbox = new QCheckBox(getResString(resId), m_pExtraControls);
651 // to add/remove automatic file extension based on checkbox
652 connect(pCheckbox, SIGNAL(stateChanged(int)), this,
653 SLOT(updateAutomaticFileExtension()));
654 widget = pCheckbox;
655 break;
656 case CHECKBOX_PASSWORD:
657 case CHECKBOX_FILTEROPTIONS:
658 case CHECKBOX_READONLY:
659 case CHECKBOX_LINK:
660 case CHECKBOX_PREVIEW:
661 case CHECKBOX_SELECTION:
662 case CHECKBOX_GPGENCRYPTION:
663 widget = new QCheckBox(getResString(resId), m_pExtraControls);
664 break;
665 case PUSHBUTTON_PLAY:
666 break;
667 case LISTBOX_VERSION:
668 case LISTBOX_TEMPLATE:
669 case LISTBOX_IMAGE_ANCHOR:
670 case LISTBOX_IMAGE_TEMPLATE:
671 case LISTBOX_FILTER_SELECTOR:
672 label = new QLabel(getResString(resId), m_pExtraControls);
673 widget = new QComboBox(m_pExtraControls);
674 label->setBuddy(widget);
675 break;
676 case LISTBOX_VERSION_LABEL:
677 case LISTBOX_TEMPLATE_LABEL:
678 case LISTBOX_IMAGE_TEMPLATE_LABEL:
679 case LISTBOX_IMAGE_ANCHOR_LABEL:
680 break;
681 }
682
683 if (widget)
684 {
685 const int row = m_pLayout->rowCount();
686 if (label)
687 m_pLayout->addWidget(label, row, 0);
688 m_pLayout->addWidget(widget, row, 1);
689 m_aCustomWidgetsMap.insert(controlId, widget);
690 }
691 }
692
initialize(const uno::Sequence<uno::Any> & args)693 void SAL_CALL Qt5FilePicker::initialize(const uno::Sequence<uno::Any>& args)
694 {
695 // parameter checking
696 uno::Any arg;
697 if (args.getLength() == 0)
698 throw lang::IllegalArgumentException("no arguments", static_cast<XFilePicker2*>(this), 1);
699
700 arg = args[0];
701
702 if ((arg.getValueType() != cppu::UnoType<sal_Int16>::get())
703 && (arg.getValueType() != cppu::UnoType<sal_Int8>::get()))
704 {
705 throw lang::IllegalArgumentException("invalid argument type",
706 static_cast<XFilePicker2*>(this), 1);
707 }
708
709 SolarMutexGuard g;
710 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
711 assert(pSalInst);
712 if (!pSalInst->IsMainThread())
713 {
714 pSalInst->RunInMainThread([this, args]() { initialize(args); });
715 return;
716 }
717
718 m_aNamedFilterToExtensionMap.clear();
719 m_aNamedFilterList.clear();
720 m_aTitleToFilterMap.clear();
721 m_aCurrentFilter.clear();
722
723 sal_Int16 templateId = -1;
724 arg >>= templateId;
725
726 QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen;
727 switch (templateId)
728 {
729 case FILEOPEN_SIMPLE:
730 break;
731
732 case FILESAVE_SIMPLE:
733 acceptMode = QFileDialog::AcceptSave;
734 break;
735
736 case FILESAVE_AUTOEXTENSION:
737 acceptMode = QFileDialog::AcceptSave;
738 addCustomControl(CHECKBOX_AUTOEXTENSION);
739 break;
740
741 case FILESAVE_AUTOEXTENSION_PASSWORD:
742 acceptMode = QFileDialog::AcceptSave;
743 addCustomControl(CHECKBOX_AUTOEXTENSION);
744 addCustomControl(CHECKBOX_PASSWORD);
745 addCustomControl(CHECKBOX_GPGENCRYPTION);
746 break;
747
748 case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
749 acceptMode = QFileDialog::AcceptSave;
750 addCustomControl(CHECKBOX_AUTOEXTENSION);
751 addCustomControl(CHECKBOX_PASSWORD);
752 addCustomControl(CHECKBOX_GPGENCRYPTION);
753 addCustomControl(CHECKBOX_FILTEROPTIONS);
754 break;
755
756 case FILESAVE_AUTOEXTENSION_SELECTION:
757 acceptMode = QFileDialog::AcceptSave;
758 addCustomControl(CHECKBOX_AUTOEXTENSION);
759 addCustomControl(CHECKBOX_SELECTION);
760 break;
761
762 case FILESAVE_AUTOEXTENSION_TEMPLATE:
763 acceptMode = QFileDialog::AcceptSave;
764 addCustomControl(CHECKBOX_AUTOEXTENSION);
765 addCustomControl(LISTBOX_TEMPLATE);
766 break;
767
768 case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
769 addCustomControl(CHECKBOX_LINK);
770 addCustomControl(CHECKBOX_PREVIEW);
771 addCustomControl(LISTBOX_IMAGE_TEMPLATE);
772 break;
773
774 case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
775 addCustomControl(CHECKBOX_LINK);
776 addCustomControl(CHECKBOX_PREVIEW);
777 addCustomControl(LISTBOX_IMAGE_ANCHOR);
778 break;
779
780 case FILEOPEN_PLAY:
781 addCustomControl(PUSHBUTTON_PLAY);
782 break;
783
784 case FILEOPEN_LINK_PLAY:
785 addCustomControl(CHECKBOX_LINK);
786 addCustomControl(PUSHBUTTON_PLAY);
787 break;
788
789 case FILEOPEN_READONLY_VERSION:
790 addCustomControl(CHECKBOX_READONLY);
791 addCustomControl(LISTBOX_VERSION);
792 break;
793
794 case FILEOPEN_LINK_PREVIEW:
795 addCustomControl(CHECKBOX_LINK);
796 addCustomControl(CHECKBOX_PREVIEW);
797 break;
798
799 case FILEOPEN_PREVIEW:
800 addCustomControl(CHECKBOX_PREVIEW);
801 break;
802
803 default:
804 throw lang::IllegalArgumentException("Unknown template",
805 static_cast<XFilePicker2*>(this), 1);
806 }
807
808 const char* resId = nullptr;
809 switch (acceptMode)
810 {
811 case QFileDialog::AcceptOpen:
812 resId = STR_FPICKER_OPEN;
813 break;
814 case QFileDialog::AcceptSave:
815 resId = STR_FPICKER_SAVE;
816 m_pFileDialog->setFileMode(QFileDialog::AnyFile);
817 break;
818 }
819
820 m_pFileDialog->setAcceptMode(acceptMode);
821 m_pFileDialog->setWindowTitle(getResString(resId));
822
823 css::uno::Reference<css::awt::XWindow> xParentWindow;
824 if (args.getLength() > 1)
825 args[1] >>= xParentWindow;
826 if (xParentWindow.is())
827 {
828 css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysWinPeer(xParentWindow,
829 css::uno::UNO_QUERY);
830 if (xSysWinPeer.is())
831 {
832 // the sal_*Int8 handling is strange, but it's public API - no way around
833 css::uno::Sequence<sal_Int8> aProcessIdent(16);
834 rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
835 uno::Any aAny = xSysWinPeer->getWindowHandle(
836 aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
837 css::awt::SystemDependentXWindow xSysWin;
838 aAny >>= xSysWin;
839
840 const auto& pFrames = pSalInst->getFrames();
841 const long aWindowHandle = xSysWin.WindowHandle;
842 const auto it = std::find_if(pFrames.begin(), pFrames.end(),
843 [&aWindowHandle](auto pFrame) -> bool {
844 const SystemEnvData* pData = pFrame->GetSystemData();
845 return pData && long(pData->aWindow) == aWindowHandle;
846 });
847 if (it != pFrames.end())
848 m_pParentWidget = static_cast<Qt5Frame*>(*it)->asChild();
849 }
850 }
851 }
852
cancel()853 void SAL_CALL Qt5FilePicker::cancel() { m_pFileDialog->reject(); }
854
disposing(const lang::EventObject & rEvent)855 void SAL_CALL Qt5FilePicker::disposing(const lang::EventObject& rEvent)
856 {
857 uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
858
859 if (xFilePickerListener.is())
860 {
861 removeFilePickerListener(xFilePickerListener);
862 }
863 }
864
queryTermination(const css::lang::EventObject &)865 void SAL_CALL Qt5FilePicker::queryTermination(const css::lang::EventObject&)
866 {
867 throw css::frame::TerminationVetoException();
868 }
869
notifyTermination(const css::lang::EventObject &)870 void SAL_CALL Qt5FilePicker::notifyTermination(const css::lang::EventObject&)
871 {
872 SolarMutexGuard aGuard;
873 m_pFileDialog->reject();
874 }
875
getImplementationName()876 OUString SAL_CALL Qt5FilePicker::getImplementationName()
877 {
878 return "com.sun.star.ui.dialogs.Qt5FilePicker";
879 }
880
supportsService(const OUString & ServiceName)881 sal_Bool SAL_CALL Qt5FilePicker::supportsService(const OUString& ServiceName)
882 {
883 return cppu::supportsService(this, ServiceName);
884 }
885
getSupportedServiceNames()886 uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSupportedServiceNames()
887 {
888 return FilePicker_getSupportedServiceNames();
889 }
890
updateAutomaticFileExtension()891 void Qt5FilePicker::updateAutomaticFileExtension()
892 {
893 bool bSetAutoExtension
894 = getValue(CHECKBOX_AUTOEXTENSION, ControlActions::GET_SELECTED_ITEM).get<bool>();
895 if (bSetAutoExtension)
896 {
897 QString sSuffix = m_aNamedFilterToExtensionMap.value(m_pFileDialog->selectedNameFilter());
898 // string is "*.<SUFFIX>" if a specific filter was selected that has exactly one possible file extension
899 if (sSuffix.lastIndexOf("*.") == 0)
900 {
901 sSuffix = sSuffix.remove("*.");
902 m_pFileDialog->setDefaultSuffix(sSuffix);
903 }
904 else
905 {
906 // fall back to setting none otherwise
907 SAL_INFO(
908 "vcl.qt5",
909 "Unable to retrieve unambiguous file extension. Will not add any automatically.");
910 bSetAutoExtension = false;
911 }
912 }
913
914 if (!bSetAutoExtension)
915 m_pFileDialog->setDefaultSuffix("");
916 }
917
filterSelected(const QString &)918 void Qt5FilePicker::filterSelected(const QString&)
919 {
920 FilePickerEvent aEvent;
921 aEvent.ElementId = LISTBOX_FILTER;
922 SAL_INFO("vcl.qt5", "filter changed");
923 if (m_xListener.is())
924 m_xListener->controlStateChanged(aEvent);
925 }
926
currentChanged(const QString &)927 void Qt5FilePicker::currentChanged(const QString&)
928 {
929 FilePickerEvent aEvent;
930 SAL_INFO("vcl.qt5", "file selection changed");
931 if (m_xListener.is())
932 m_xListener->fileSelectionChanged(aEvent);
933 }
934
getDirectory()935 OUString Qt5FilePicker::getDirectory()
936 {
937 uno::Sequence<OUString> seq = getSelectedFiles();
938 if (seq.getLength() > 1)
939 seq.realloc(1);
940 return seq[0];
941 }
942
setDescription(const OUString &)943 void Qt5FilePicker::setDescription(const OUString&) {}
944
945 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
946