1 /* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
22 #include <sal/config.h>
23 #include <sal/log.hxx>
25 #include <com/sun/star/lang/DisposedException.hpp>
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
28 #include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
29 #include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
30 #include <cppuhelper/interfacecontainer.h>
31 #include <cppuhelper/supportsservice.hxx>
32 #include <osl/diagnose.h>
33 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
34 #include <com/sun/star/ui/dialogs/ControlActions.hpp>
35 #include <com/sun/star/uno/Any.hxx>
36 #include "FPServiceInfo.hxx"
37 #include <osl/mutex.hxx>
38 #include <vcl/svapp.hxx>
40 #include "resourceprovider.hxx"
42 #include <osl/file.hxx>
43 #include "CFStringUtilities.hxx"
44 #include "NSString_OOoAdditions.hxx"
45 #include "NSURL_OOoAdditions.hxx"
49 #include "SalAquaFilePicker.hxx"
51 #include <objc/objc-runtime.h>
55 using namespace ::com::sun::star;
56 using namespace ::com::sun::star::ui::dialogs;
57 using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
58 using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
59 using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
60 using namespace ::com::sun::star::lang;
61 using namespace ::com::sun::star::beans;
62 using namespace ::com::sun::star::uno;
66 uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
68 return { "com.sun.star.ui.dialogs.FilePicker",
69 "com.sun.star.ui.dialogs.SystemFilePicker",
70 "com.sun.star.ui.dialogs.AquaFilePicker" };
74 #pragma mark Constructor
76 SalAquaFilePicker::SalAquaFilePicker()
77 : SalAquaFilePicker_Base( m_rbHelperMtx )
78 , m_pFilterHelper( nullptr )
80 m_pDelegate = [[AquaFilePickerDelegate alloc] initWithFilePicker:this];
81 m_pControlHelper->setFilePickerDelegate(m_pDelegate);
84 SalAquaFilePicker::~SalAquaFilePicker()
86 if (nullptr != m_pFilterHelper)
87 delete m_pFilterHelper;
89 [m_pDelegate release];
93 #pragma mark XFilePickerNotifier
95 void SAL_CALL SalAquaFilePicker::addFilePickerListener( const uno::Reference<XFilePickerListener>& xListener )
97 SolarMutexGuard aGuard;
98 m_xListener = xListener;
101 void SAL_CALL SalAquaFilePicker::removeFilePickerListener( const uno::Reference<XFilePickerListener>& )
103 SolarMutexGuard aGuard;
107 #pragma mark XAsynchronousExecutableDialog
109 void SAL_CALL SalAquaFilePicker::setTitle( const OUString& aTitle )
111 SolarMutexGuard aGuard;
112 implsetTitle(aTitle);
115 sal_Int16 SAL_CALL SalAquaFilePicker::execute()
117 SolarMutexGuard aGuard;
119 sal_Int16 retVal = 0;
123 // if m_pDialog is nil after initialization, something must have gone wrong before
124 // or there was no initialization (see issue https://bz.apache.org/ooo/show_bug.cgi?id=100214)
125 if (m_pDialog == nil) {
126 m_nDialogType = NAVIGATIONSERVICES_OPEN;
129 if (m_pFilterHelper) {
130 m_pFilterHelper->SetFilters();
133 if (m_nDialogType == NAVIGATIONSERVICES_SAVE) {
134 if (m_sSaveFileName.getLength() == 0) {
135 //if no filename is set, NavigationServices will set the name to "untitled". We don't want this!
136 //So let's try to get the window title to get the real untitled name
137 NSWindow *frontWindow = [NSApp keyWindow];
138 if (nullptr != frontWindow) {
139 NSString *windowTitle = [frontWindow title];
140 if (windowTitle != nil) {
141 OUString ouName = [windowTitle OUString];
142 //a window title will typically be something like "Untitled1 - OpenOffice.org Writer"
143 //but we only want the "Untitled1" part of it
144 sal_Int32 indexOfDash = ouName.indexOf(" - ");
145 if (indexOfDash > -1) {
146 m_sSaveFileName = ouName.copy(0,indexOfDash);
147 if (m_sSaveFileName.getLength() > 0) {
148 setDefaultName(m_sSaveFileName);
156 //Set the delegate to be notified of certain events
158 // I don't know why, but with gcc 4.2.1, this line results in the warning:
159 // class 'AquaFilePickerDelegate' does not implement the 'NSOpenSavePanelDelegate' protocol
161 // [m_pDialog setDelegate:m_pDelegate];
163 reinterpret_cast<id (*)(id, SEL, ...)>(objc_msgSend)(
164 m_pDialog, @selector(setDelegate:), m_pDelegate);
166 int nStatus = runandwaitforresult();
168 [m_pDialog setDelegate:nil];
172 case NSModalResponseOK:
173 retVal = ExecutableDialogResults::OK;
176 case NSModalResponseCancel:
177 retVal = ExecutableDialogResults::CANCEL;
181 throw uno::RuntimeException(
182 "The dialog returned with an unknown result!",
183 static_cast<XFilePicker*>( static_cast<XFilePicker3*>( this ) ));
191 #pragma mark XFilePicker
193 void SAL_CALL SalAquaFilePicker::setMultiSelectionMode( sal_Bool /* bMode */ )
195 SolarMutexGuard aGuard;
197 if (m_nDialogType == NAVIGATIONSERVICES_OPEN) {
198 [static_cast<NSOpenPanel*>(m_pDialog) setAllowsMultipleSelection:YES];
202 void SAL_CALL SalAquaFilePicker::setDefaultName( const OUString& aName )
204 SolarMutexGuard aGuard;
206 m_sSaveFileName = aName;
209 void SAL_CALL SalAquaFilePicker::setDisplayDirectory( const OUString& rDirectory )
211 SolarMutexGuard aGuard;
213 implsetDisplayDirectory(rDirectory);
216 OUString SAL_CALL SalAquaFilePicker::getDisplayDirectory()
218 OUString retVal = implgetDisplayDirectory();
223 uno::Sequence<OUString> SAL_CALL SalAquaFilePicker::getFiles()
225 uno::Sequence< OUString > aSelectedFiles = getSelectedFiles();
226 // multiselection doesn't really work with getFiles
227 // so just retrieve the first url
228 if (aSelectedFiles.getLength() > 1)
229 aSelectedFiles.realloc(1);
231 return aSelectedFiles;
234 uno::Sequence<OUString> SAL_CALL SalAquaFilePicker::getSelectedFiles()
236 SolarMutexGuard aGuard;
238 #if HAVE_FEATURE_MACOSX_SANDBOX
239 static NSUserDefaults *userDefaults;
240 static bool triedUserDefaults = false;
242 if (!triedUserDefaults)
244 userDefaults = [NSUserDefaults standardUserDefaults];
245 triedUserDefaults = true;
249 NSArray *files = nil;
250 if (m_nDialogType == NAVIGATIONSERVICES_OPEN) {
251 files = [static_cast<NSOpenPanel*>(m_pDialog) URLs];
253 else if (m_nDialogType == NAVIGATIONSERVICES_SAVE) {
254 files = [NSArray arrayWithObjects:[m_pDialog URL], nil];
257 long nFiles = [files count];
258 SAL_INFO("fpicker.aqua", "# of items: " << nFiles);
260 uno::Sequence< OUString > aSelectedFiles(nFiles);
262 for(long nIndex = 0; nIndex < nFiles; nIndex += 1)
264 NSURL *url = [files objectAtIndex:nIndex];
266 #if HAVE_FEATURE_MACOSX_SANDBOX
267 if (userDefaults != NULL &&
268 [url respondsToSelector:@selector(bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:)])
270 // In the case of "Save As" when the user has input a new
271 // file name, this call will return nil, as bookmarks can
272 // (naturally) only be created for existing file system
273 // objects. In that case, code at a much lower level, in
274 // sal, takes care of creating a bookmark when a new file
275 // has been created outside the sandbox.
276 NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
277 includingResourceValuesForKeys:nil
282 [userDefaults setObject:data
283 forKey:[@"bookmarkFor:" stringByAppendingString:[url absoluteString]]];
288 OUString sFileOrDirURL = [url OUStringForInfo:FULLPATH];
290 aSelectedFiles[nIndex] = sFileOrDirURL;
293 return aSelectedFiles;
296 #pragma mark XFilterManager
298 void SAL_CALL SalAquaFilePicker::appendFilter( const OUString& aTitle, const OUString& aFilter )
300 SolarMutexGuard aGuard;
302 ensureFilterHelper();
303 m_pFilterHelper->appendFilter( aTitle, aFilter );
304 m_pControlHelper->setFilterControlNeeded(true);
307 void SAL_CALL SalAquaFilePicker::setCurrentFilter( const OUString& aTitle )
309 SolarMutexGuard aGuard;
311 ensureFilterHelper();
312 m_pFilterHelper->setCurrentFilter(aTitle);
315 updateSaveFileNameExtension();
318 OUString SAL_CALL SalAquaFilePicker::getCurrentFilter()
320 SolarMutexGuard aGuard;
322 ensureFilterHelper();
324 return m_pFilterHelper->getCurrentFilter();
327 #pragma mark XFilterGroupManager
329 void SAL_CALL SalAquaFilePicker::appendFilterGroup( const OUString& sGroupTitle, const uno::Sequence<beans::StringPair>& aFilters )
331 SolarMutexGuard aGuard;
333 ensureFilterHelper();
334 m_pFilterHelper->appendFilterGroup(sGroupTitle, aFilters);
335 m_pControlHelper->setFilterControlNeeded(true);
338 #pragma mark XFilePickerControlAccess
340 void SAL_CALL SalAquaFilePicker::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue )
342 SolarMutexGuard aGuard;
344 m_pControlHelper->setValue(nControlId, nControlAction, rValue);
346 if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION && m_nDialogType == NAVIGATIONSERVICES_SAVE) {
347 updateSaveFileNameExtension();
351 uno::Any SAL_CALL SalAquaFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction )
353 uno::Any aValue = m_pControlHelper->getValue(nControlId, nControlAction);
358 void SAL_CALL SalAquaFilePicker::enableControl( sal_Int16 nControlId, sal_Bool bEnable )
360 m_pControlHelper->enableControl(nControlId, bEnable);
363 void SAL_CALL SalAquaFilePicker::setLabel( sal_Int16 nControlId, const OUString& aLabel )
365 SolarMutexGuard aGuard;
367 NSString* sLabel = [NSString stringWithOUString:aLabel];
368 m_pControlHelper->setLabel( nControlId, sLabel ) ;
371 OUString SAL_CALL SalAquaFilePicker::getLabel( sal_Int16 nControlId )
373 return m_pControlHelper->getLabel(nControlId);
376 #pragma mark XInitialization
378 void SAL_CALL SalAquaFilePicker::initialize( const uno::Sequence<uno::Any>& aArguments )
380 SolarMutexGuard aGuard;
382 // parameter checking
384 if( 0 == aArguments.getLength() )
385 throw lang::IllegalArgumentException("no arguments",
386 static_cast<XFilePicker*>( static_cast<XFilePicker3*>(this) ), 1 );
388 aAny = aArguments[0];
390 if( ( aAny.getValueType() != ::cppu::UnoType<sal_Int16>::get() ) &&
391 (aAny.getValueType() != ::cppu::UnoType<sal_Int8>::get() ) )
392 throw lang::IllegalArgumentException("invalid argument type",
393 static_cast<XFilePicker*>( static_cast<XFilePicker3*>(this) ), 1 );
395 sal_Int16 templateId = -1;
400 case FILEOPEN_SIMPLE:
401 m_nDialogType = NAVIGATIONSERVICES_OPEN;
403 case FILESAVE_SIMPLE:
404 m_nDialogType = NAVIGATIONSERVICES_SAVE;
406 case FILESAVE_AUTOEXTENSION_PASSWORD:
407 m_nDialogType = NAVIGATIONSERVICES_SAVE;
409 case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
410 m_nDialogType = NAVIGATIONSERVICES_SAVE;
412 case FILESAVE_AUTOEXTENSION_SELECTION:
413 m_nDialogType = NAVIGATIONSERVICES_SAVE;
415 case FILESAVE_AUTOEXTENSION_TEMPLATE:
416 m_nDialogType = NAVIGATIONSERVICES_SAVE;
418 case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
419 m_nDialogType = NAVIGATIONSERVICES_OPEN;
421 case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
422 m_nDialogType = NAVIGATIONSERVICES_OPEN;
425 m_nDialogType = NAVIGATIONSERVICES_OPEN;
427 case FILEOPEN_LINK_PLAY:
428 m_nDialogType = NAVIGATIONSERVICES_OPEN;
430 case FILEOPEN_READONLY_VERSION:
431 m_nDialogType = NAVIGATIONSERVICES_OPEN;
433 case FILEOPEN_LINK_PREVIEW:
434 m_nDialogType = NAVIGATIONSERVICES_OPEN;
436 case FILESAVE_AUTOEXTENSION:
437 m_nDialogType = NAVIGATIONSERVICES_SAVE;
439 case FILEOPEN_PREVIEW:
440 m_nDialogType = NAVIGATIONSERVICES_OPEN;
443 throw lang::IllegalArgumentException("Unknown template",
444 static_cast<XFilePicker*>( static_cast<XFilePicker3*>(this) ),
448 m_pControlHelper->initialize(templateId);
453 #pragma mark XCancellable
455 void SAL_CALL SalAquaFilePicker::cancel()
457 SolarMutexGuard aGuard;
459 if (m_pDialog != nil) {
460 [m_pDialog cancel:nil];
464 #pragma mark XEventListener
466 void SalAquaFilePicker::disposing( const lang::EventObject& aEvent )
468 SolarMutexGuard aGuard;
470 uno::Reference<XFilePickerListener> xFilePickerListener( aEvent.Source, css::uno::UNO_QUERY );
472 if( xFilePickerListener.is() )
473 removeFilePickerListener( xFilePickerListener );
476 #pragma mark XServiceInfo
478 OUString SAL_CALL SalAquaFilePicker::getImplementationName()
480 return FILE_PICKER_IMPL_NAME;
483 sal_Bool SAL_CALL SalAquaFilePicker::supportsService( const OUString& sServiceName )
485 return cppu::supportsService(this, sServiceName);
488 uno::Sequence<OUString> SAL_CALL SalAquaFilePicker::getSupportedServiceNames()
490 return FilePicker_getSupportedServiceNames();
493 #pragma mark Misc/Private
495 void SalAquaFilePicker::fileSelectionChanged( FilePickerEvent aEvent )
497 if (m_xListener.is())
498 m_xListener->fileSelectionChanged( aEvent );
501 void SalAquaFilePicker::directoryChanged( FilePickerEvent aEvent )
503 if (m_xListener.is())
504 m_xListener->directoryChanged( aEvent );
507 void SalAquaFilePicker::controlStateChanged( FilePickerEvent aEvent )
509 if (m_xListener.is())
510 m_xListener->controlStateChanged( aEvent );
513 void SalAquaFilePicker::dialogSizeChanged()
515 if (m_xListener.is())
516 m_xListener->dialogSizeChanged();
522 void SalAquaFilePicker::ensureFilterHelper()
524 SolarMutexGuard aGuard;
526 if (nullptr == m_pFilterHelper) {
527 m_pFilterHelper = new FilterHelper;
528 m_pControlHelper->setFilterHelper(m_pFilterHelper);
529 [m_pDelegate setFilterHelper:m_pFilterHelper];
533 void SalAquaFilePicker::updateFilterUI()
535 m_pControlHelper->updateFilterUI();
538 void SalAquaFilePicker::updateSaveFileNameExtension()
540 if (m_nDialogType != NAVIGATIONSERVICES_SAVE) {
544 // we need to set this here again because initial setting does
545 //[m_pDialog setExtensionHidden:YES];
547 SolarMutexGuard aGuard;
549 if (!m_pControlHelper->isAutoExtensionEnabled()) {
550 [m_pDialog setAllowedFileTypes:nil];
551 [m_pDialog setAllowsOtherFileTypes:YES];
553 ensureFilterHelper();
555 OUStringList aStringList = m_pFilterHelper->getCurrentFilterSuffixList();
556 if( aStringList.empty()) // #i9328#
559 OUString suffix = (*(aStringList.begin())).copy(1);
560 NSString *requiredFileType = [NSString stringWithOUString:suffix];
562 [m_pDialog setAllowedFileTypes:[NSArray arrayWithObjects:requiredFileType, nil]];
564 [m_pDialog setAllowsOtherFileTypes:NO];
568 void SalAquaFilePicker::filterControlChanged()
570 if (m_pDialog == nil) {
574 SolarMutexGuard aGuard;
576 updateSaveFileNameExtension();
578 [m_pDialog validateVisibleColumns];
581 evt.ElementId = LISTBOX_FILTER;
582 controlStateChanged( evt );
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */