Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / fpicker / source / aqua / SalAquaFilePicker.mm
blobac54fca0cc98aa9651f0a9362c0a0572b12a5005
1 /* -*- Mode: ObjC; 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  */
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"
47 #include <iostream>
49 #include "SalAquaFilePicker.hxx"
51 #include <objc/objc-runtime.h>
53 #pragma mark DEFINES
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;
64 namespace
66     uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
67     {
68         return { "com.sun.star.ui.dialogs.FilePicker",
69                  "com.sun.star.ui.dialogs.SystemFilePicker",
70                  "com.sun.star.ui.dialogs.AquaFilePicker" };
71     }
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;
104     m_xListener.clear();
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;
121     implInitialize();
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;
127     }
129     if (m_pFilterHelper) {
130         m_pFilterHelper->SetFilters();
131     }
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);
149                         }
150                     }
151                 }
152             }
153         }
154     }
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
160     // So instead of:
161     // [m_pDialog setDelegate:m_pDelegate];
162     // do:
163     reinterpret_cast<id (*)(id, SEL, ...)>(objc_msgSend)(
164         m_pDialog, @selector(setDelegate:), m_pDelegate);
166     int nStatus = runandwaitforresult();
168     [m_pDialog setDelegate:nil];
170     switch( nStatus )
171     {
172         case NSModalResponseOK:
173             retVal = ExecutableDialogResults::OK;
174             break;
176         case NSModalResponseCancel:
177             retVal = ExecutableDialogResults::CANCEL;
178             break;
180         default:
181             throw uno::RuntimeException(
182                       "The dialog returned with an unknown result!",
183                       static_cast<XFilePicker*>( static_cast<XFilePicker3*>( this ) ));
184             break;
185     }
187     return retVal;
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];
199     }
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();
220     return retVal;
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)
243     {
244         userDefaults = [NSUserDefaults standardUserDefaults];
245         triedUserDefaults = true;
246     }
247 #endif
249     NSArray *files = nil;
250     if (m_nDialogType == NAVIGATIONSERVICES_OPEN) {
251         files = [static_cast<NSOpenPanel*>(m_pDialog) URLs];
252     }
253     else if (m_nDialogType == NAVIGATIONSERVICES_SAVE) {
254         files = [NSArray arrayWithObjects:[m_pDialog URL], nil];
255     }
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)
263     {
264         NSURL *url = [files objectAtIndex:nIndex];
266 #if HAVE_FEATURE_MACOSX_SANDBOX
267         if (userDefaults != NULL &&
268             [url respondsToSelector:@selector(bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:)])
269         {
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
278                                           relativeToURL:nil
279                                                   error:nil];
280             if (data != NULL)
281             {
282                 [userDefaults setObject:data
283                                  forKey:[@"bookmarkFor:" stringByAppendingString:[url absoluteString]]];
284             }
285         }
286 #endif
288         OUString sFileOrDirURL = [url OUStringForInfo:FULLPATH];
290         aSelectedFiles[nIndex] = sFileOrDirURL;
291     }
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);
313     updateFilterUI();
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();
348     }
351 uno::Any SAL_CALL SalAquaFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction )
353     uno::Any aValue = m_pControlHelper->getValue(nControlId, nControlAction);
355     return aValue;
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
383     uno::Any aAny;
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;
396     aAny >>= templateId;
398     switch( templateId )
399     {
400         case FILEOPEN_SIMPLE:
401             m_nDialogType = NAVIGATIONSERVICES_OPEN;
402             break;
403         case FILESAVE_SIMPLE:
404             m_nDialogType = NAVIGATIONSERVICES_SAVE;
405             break;
406         case FILESAVE_AUTOEXTENSION_PASSWORD:
407             m_nDialogType = NAVIGATIONSERVICES_SAVE;
408             break;
409         case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
410             m_nDialogType = NAVIGATIONSERVICES_SAVE;
411             break;
412         case FILESAVE_AUTOEXTENSION_SELECTION:
413             m_nDialogType = NAVIGATIONSERVICES_SAVE;
414             break;
415         case FILESAVE_AUTOEXTENSION_TEMPLATE:
416             m_nDialogType = NAVIGATIONSERVICES_SAVE;
417             break;
418         case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
419             m_nDialogType = NAVIGATIONSERVICES_OPEN;
420             break;
421         case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
422             m_nDialogType = NAVIGATIONSERVICES_OPEN;
423             break;
424         case FILEOPEN_PLAY:
425             m_nDialogType = NAVIGATIONSERVICES_OPEN;
426             break;
427         case FILEOPEN_LINK_PLAY:
428             m_nDialogType = NAVIGATIONSERVICES_OPEN;
429             break;
430         case FILEOPEN_READONLY_VERSION:
431             m_nDialogType = NAVIGATIONSERVICES_OPEN;
432             break;
433         case FILEOPEN_LINK_PREVIEW:
434             m_nDialogType = NAVIGATIONSERVICES_OPEN;
435             break;
436         case FILESAVE_AUTOEXTENSION:
437             m_nDialogType = NAVIGATIONSERVICES_SAVE;
438             break;
439         case FILEOPEN_PREVIEW:
440             m_nDialogType = NAVIGATIONSERVICES_OPEN;
441             break;
442         default:
443             throw lang::IllegalArgumentException("Unknown template",
444                                                  static_cast<XFilePicker*>( static_cast<XFilePicker3*>(this) ),
445                                                  1 );
446     }
448     m_pControlHelper->initialize(templateId);
450     implInitialize();
453 #pragma mark XCancellable
455 void SAL_CALL SalAquaFilePicker::cancel()
457     SolarMutexGuard aGuard;
459     if (m_pDialog != nil) {
460         [m_pDialog cancel:nil];
461     }
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();
520 // Misc
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];
530     }
533 void SalAquaFilePicker::updateFilterUI()
535     m_pControlHelper->updateFilterUI();
538 void SalAquaFilePicker::updateSaveFileNameExtension()
540     if (m_nDialogType != NAVIGATIONSERVICES_SAVE) {
541         return;
542     }
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];
552     } else {
553         ensureFilterHelper();
555         OUStringList aStringList = m_pFilterHelper->getCurrentFilterSuffixList();
556         if( aStringList.empty()) // #i9328#
557             return;
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];
565     }
568 void SalAquaFilePicker::filterControlChanged()
570     if (m_pDialog == nil) {
571         return;
572     }
574     SolarMutexGuard aGuard;
576     updateSaveFileNameExtension();
578     [m_pDialog validateVisibleColumns];
580     FilePickerEvent evt;
581     evt.ElementId = LISTBOX_FILTER;
582     controlStateChanged( evt );
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */