From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- fpicker/source/aqua/AquaFilePickerDelegate.hxx | 48 ++ fpicker/source/aqua/AquaFilePickerDelegate.mm | 115 +++ fpicker/source/aqua/ControlHelper.hxx | 184 +++++ fpicker/source/aqua/ControlHelper.mm | 937 +++++++++++++++++++++++++ fpicker/source/aqua/FilterHelper.hxx | 124 ++++ fpicker/source/aqua/FilterHelper.mm | 443 ++++++++++++ fpicker/source/aqua/NSString_OOoAdditions.hxx | 33 + fpicker/source/aqua/NSString_OOoAdditions.mm | 47 ++ fpicker/source/aqua/NSURL_OOoAdditions.hxx | 38 + fpicker/source/aqua/NSURL_OOoAdditions.mm | 80 +++ fpicker/source/aqua/SalAquaConstants.h | 54 ++ fpicker/source/aqua/SalAquaFilePicker.hxx | 160 +++++ fpicker/source/aqua/SalAquaFilePicker.mm | 590 ++++++++++++++++ fpicker/source/aqua/SalAquaFolderPicker.hxx | 95 +++ fpicker/source/aqua/SalAquaFolderPicker.mm | 179 +++++ fpicker/source/aqua/SalAquaPicker.hxx | 81 +++ fpicker/source/aqua/SalAquaPicker.mm | 209 ++++++ fpicker/source/aqua/fps_aqua.component | 30 + fpicker/source/aqua/resourceprovider.hxx | 46 ++ fpicker/source/aqua/resourceprovider.mm | 116 +++ 20 files changed, 3609 insertions(+) create mode 100644 fpicker/source/aqua/AquaFilePickerDelegate.hxx create mode 100644 fpicker/source/aqua/AquaFilePickerDelegate.mm create mode 100644 fpicker/source/aqua/ControlHelper.hxx create mode 100644 fpicker/source/aqua/ControlHelper.mm create mode 100644 fpicker/source/aqua/FilterHelper.hxx create mode 100644 fpicker/source/aqua/FilterHelper.mm create mode 100644 fpicker/source/aqua/NSString_OOoAdditions.hxx create mode 100644 fpicker/source/aqua/NSString_OOoAdditions.mm create mode 100644 fpicker/source/aqua/NSURL_OOoAdditions.hxx create mode 100644 fpicker/source/aqua/NSURL_OOoAdditions.mm create mode 100644 fpicker/source/aqua/SalAquaConstants.h create mode 100644 fpicker/source/aqua/SalAquaFilePicker.hxx create mode 100644 fpicker/source/aqua/SalAquaFilePicker.mm create mode 100644 fpicker/source/aqua/SalAquaFolderPicker.hxx create mode 100644 fpicker/source/aqua/SalAquaFolderPicker.mm create mode 100644 fpicker/source/aqua/SalAquaPicker.hxx create mode 100644 fpicker/source/aqua/SalAquaPicker.mm create mode 100644 fpicker/source/aqua/fps_aqua.component create mode 100644 fpicker/source/aqua/resourceprovider.hxx create mode 100644 fpicker/source/aqua/resourceprovider.mm (limited to 'fpicker/source/aqua') diff --git a/fpicker/source/aqua/AquaFilePickerDelegate.hxx b/fpicker/source/aqua/AquaFilePickerDelegate.hxx new file mode 100644 index 000000000..51c34b71b --- /dev/null +++ b/fpicker/source/aqua/AquaFilePickerDelegate.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include +#include + +class SalAquaFilePicker; +class FilterHelper; + +@interface AquaFilePickerDelegate : NSObject +{ + SalAquaFilePicker* filePicker; + FilterHelper* filterHelper; +} + +- (id)initWithFilePicker:(SalAquaFilePicker*)fPicker; + +- (void)setFilterHelper:(FilterHelper*)filterHelper; + +- (BOOL)panel:(id)sender shouldShowFilename:(NSString*)filename; +- (void)panelSelectionDidChange:(id)sender; +- (void)panel:(id)sender directoryDidChange:(NSString*)path; + +- (void)filterSelectedAtIndex:(id)sender; +- (void)autoextensionChanged:(id)sender; + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/AquaFilePickerDelegate.mm b/fpicker/source/aqua/AquaFilePickerDelegate.mm new file mode 100644 index 000000000..d9506c2c7 --- /dev/null +++ b/fpicker/source/aqua/AquaFilePickerDelegate.mm @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include +#include + +#include "SalAquaFilePicker.hxx" +#include "FilterHelper.hxx" +#include "AquaFilePickerDelegate.hxx" + +@implementation AquaFilePickerDelegate + +- (id)initWithFilePicker:(SalAquaFilePicker*)fPicker +{ + if ((self = [super init])) { + filePicker = fPicker; + filterHelper = nullptr; + return self; + } + return nil; +} + +- (void)setFilterHelper:(FilterHelper*)helper +{ + filterHelper = helper; +} + +#pragma mark NSSavePanel delegate methods + +- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename +{ + (void)sender; + if( filterHelper == nullptr ) + return true; + if( filename == nil ) + return false; + return filterHelper->filenameMatchesFilter(filename); +} + +- (void)panelSelectionDidChange:(id)sender +{ + (void)sender; + if (filePicker != nullptr) { + css::ui::dialogs::FilePickerEvent evt; + filePicker->fileSelectionChanged(evt); + } +} + +- (void)panel:(id)sender directoryDidChange:(NSString *)path +{ + (void)sender; + (void)path; + if (filePicker != nullptr) { + css::ui::dialogs::FilePickerEvent evt; + filePicker->directoryChanged(evt); + } +} + + +#pragma mark UIActions +- (void)filterSelectedAtIndex:(id)sender +{ + if (sender == nil) { + return; + } + + if ([sender class] != [NSPopUpButton class]) { + return; + } + + if (filterHelper == nullptr) { + return; + } + + NSPopUpButton *popup = static_cast(sender); + unsigned int selectedIndex = [popup indexOfSelectedItem]; + + filterHelper->SetFilterAtIndex(selectedIndex); + + filePicker->filterControlChanged(); +} + +- (void)autoextensionChanged:(id)sender +{ + if (sender == nil) { + return; + } + + if ([sender class] != [NSButton class]) { + return; + } + uno::Any aValue; + aValue <<= ([static_cast(sender) state] == NSControlStateValueOn); + + filePicker->setValue(css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, 0, aValue); +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/ControlHelper.hxx b/fpicker/source/aqua/ControlHelper.hxx new file mode 100644 index 000000000..5da540df6 --- /dev/null +++ b/fpicker/source/aqua/ControlHelper.hxx @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include +#include "SalAquaConstants.h" +#include "FilterHelper.hxx" +#include "AquaFilePickerDelegate.hxx" + +using namespace com::sun::star; + +class ControlHelper { + +public: + + + // Constructor / Destructor + + ControlHelper(); + virtual ~ControlHelper(); + + + // XInitialization delegate + + void initialize( sal_Int16 templateId ); + + + // XFilePickerControlAccess function delegates + + void setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue ); + uno::Any getValue( sal_Int16 nControlId, sal_Int16 nControlAction ) const; + void enableControl( sal_Int16 nControlId, bool bEnable ) const; + OUString getLabel( sal_Int16 nControlId ); + void setLabel( sal_Int16 nControlId, NSString* aLabel ); + + + // other stuff + + void updateFilterUI(); + + + // Type definitions + + enum ToggleType { + AUTOEXTENSION, //but autoextension is handled differently on MacOSX + PASSWORD, + FILTEROPTIONS, + READONLY, + LINK, + PREVIEW, + SELECTION, + TOGGLE_LAST + }; + + enum ListType { + VERSION, + TEMPLATE, + IMAGE_TEMPLATE, + IMAGE_ANCHOR, + LIST_LAST + }; + + + // inline functions + + NSView* getUserPane() { + if (!m_bIsUserPaneLaidOut) { + createUserPane(); + } + return m_pUserPane; + } + + bool getVisibility(ToggleType tToggle) { + return m_bToggleVisibility[tToggle]; + } + + void setFilterControlNeeded(bool bNeeded) { + m_bIsFilterControlNeeded = bNeeded; + if (bNeeded) { + m_bUserPaneNeeded = true; + } + } + + void setFilterHelper(FilterHelper* pFilterHelper) { + m_pFilterHelper = pFilterHelper; + } + + void setFilePickerDelegate(AquaFilePickerDelegate* pDelegate) { + m_pDelegate = pDelegate; + } + + bool isAutoExtensionEnabled() { + return ([static_cast(m_pToggles[AUTOEXTENSION]) state] == NSControlStateValueOn); + } + +private: + + // private member variables + + + /** the native view object */ + NSView* m_pUserPane; + + /** the checkbox controls */ + NSControl* m_pToggles[ TOGGLE_LAST ]; + + /** the visibility flags for the checkboxes */ + bool m_bToggleVisibility[TOGGLE_LAST]; + + /** the special filter control */ + NSPopUpButton *m_pFilterControl; + + /** the popup menu controls (except for the filter control) */ + NSControl* m_pListControls[ LIST_LAST ]; + + /** a map to store a control's label text */ + ::std::map m_aMapListLabels; + + /** a map to store a popup menu's label text field */ + ::std::map m_aMapListLabelFields; + + /** the visibility flags for the popup menus */ + bool m_bListVisibility[ LIST_LAST ]; + + /** indicates if a user pane is needed */ + bool m_bUserPaneNeeded; + + /** indicates if the user pane was laid out already */ + bool m_bIsUserPaneLaidOut; + + /** indicates if a filter control is needed */ + bool m_bIsFilterControlNeeded; + + /** a list with all actively used controls */ + ::std::list m_aActiveControls; + + /** the filter helper */ + FilterHelper *m_pFilterHelper; + + /** the save or open panel's delegate */ + AquaFilePickerDelegate *m_pDelegate; + + + // private methods + + void HandleSetListValue(const NSControl* pControl, const sal_Int16 nControlAction, const uno::Any& rValue); + + void createControls(); + void createFilterControl(); + void createUserPane(); + + static int getControlElementName(const Class clazz, const int nControlId); + NSControl* getControl( const sal_Int16 nControlId ) const; + static int getVerticalDistance(const NSControl* first, const NSControl* second); + + void layoutControls(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/ControlHelper.mm b/fpicker/source/aqua/ControlHelper.mm new file mode 100644 index 000000000..88f0b655c --- /dev/null +++ b/fpicker/source/aqua/ControlHelper.mm @@ -0,0 +1,937 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include "resourceprovider.hxx" +#include "NSString_OOoAdditions.hxx" +#include + +#include "ControlHelper.hxx" + +#pragma mark DEFINES +#define POPUP_WIDTH_MIN 200 +#define POPUP_WIDTH_MAX 350 + +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::ui::dialogs::TemplateDescription; +using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds; + +namespace { + +uno::Any HandleGetListValue(const NSControl* pControl, const sal_Int16 nControlAction) +{ + uno::Any aAny; + + if ([pControl class] != [NSPopUpButton class]) { + SAL_INFO("fpicker.aqua","not a popup button"); + return aAny; + } + + NSPopUpButton *pButton = static_cast(pControl); + NSMenu *rMenu = [pButton menu]; + if (nil == rMenu) { + SAL_INFO("fpicker.aqua","button has no menu"); + return aAny; + } + + switch (nControlAction) + { + case ControlActions::GET_ITEMS: + { + SAL_INFO("fpicker.aqua","GET_ITEMS"); + uno::Sequence< OUString > aItemList; + + int nItems = [rMenu numberOfItems]; + if (nItems > 0) { + aItemList.realloc(nItems); + OUString* pItemList = aItemList.getArray(); + for (int i = 0; i < nItems; i++) { + NSString* sCFItem = [pButton itemTitleAtIndex:i]; + if (nil != sCFItem) { + pItemList[i] = [sCFItem OUString]; + SAL_INFO("fpicker.aqua","Return value[" << (i - 1) << "]: " << aItemList[i - 1]); + } + } + } + + aAny <<= aItemList; + } + break; + case ControlActions::GET_SELECTED_ITEM: + { + SAL_INFO("fpicker.aqua","GET_SELECTED_ITEM"); + NSString* sCFItem = [pButton titleOfSelectedItem]; + if (nil != sCFItem) { + OUString sString = [sCFItem OUString]; + SAL_INFO("fpicker.aqua","Return value: " << sString); + aAny <<= sString; + } + } + break; + case ControlActions::GET_SELECTED_ITEM_INDEX: + { + SAL_INFO("fpicker.aqua","GET_SELECTED_ITEM_INDEX"); + sal_Int32 nActive = [pButton indexOfSelectedItem]; + SAL_INFO("fpicker.aqua","Return value: " << nActive); + aAny <<= nActive; + } + break; + default: + SAL_INFO("fpicker.aqua","undocumented/unimplemented ControlAction for a list"); + break; + } + + return aAny; +} + +NSTextField* createLabelWithString(NSString* labelString) +{ + NSTextField *textField = [NSTextField new]; + [textField setEditable:NO]; + [textField setSelectable:NO]; + [textField setDrawsBackground:NO]; + [textField setBordered:NO]; + [[textField cell] setTitle:labelString]; + + return textField; +} + +} + +#pragma mark Constructor / Destructor + +// Constructor / Destructor + +ControlHelper::ControlHelper() +: m_pUserPane(nullptr) +, m_pFilterControl(nil) +, m_bUserPaneNeeded( false ) +, m_bIsUserPaneLaidOut(false) +, m_bIsFilterControlNeeded(false) +, m_pFilterHelper(nullptr) +{ + int i; + + for( i = 0; i < TOGGLE_LAST; i++ ) { + m_bToggleVisibility[i] = false; + } + + for( i = 0; i < LIST_LAST; i++ ) { + m_bListVisibility[i] = false; + } +} + +ControlHelper::~ControlHelper() +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + if (nullptr != m_pUserPane) { + [m_pUserPane release]; + } + + if (m_pFilterControl != nullptr) { + [m_pFilterControl setTarget:nil]; + } + + for (auto const& activeControl : m_aActiveControls) + { + NSString* sLabelName = m_aMapListLabels[activeControl]; + if (sLabelName != nil) { + [sLabelName release]; + } + if ([activeControl class] == [NSPopUpButton class]) { + NSTextField* pField = m_aMapListLabelFields[static_cast(activeControl)]; + if (pField != nil) { + [pField release]; + } + } + [activeControl release]; + } + + [pool release]; +} + +#pragma mark XInitialization delegate + +// XInitialization delegate + +void ControlHelper::initialize( sal_Int16 nTemplateId ) +{ + switch( nTemplateId ) + { + case FILESAVE_AUTOEXTENSION_PASSWORD: + m_bToggleVisibility[AUTOEXTENSION] = true; + m_bToggleVisibility[PASSWORD] = true; + break; + case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS: + m_bToggleVisibility[AUTOEXTENSION] = true; + m_bToggleVisibility[PASSWORD] = true; + m_bToggleVisibility[FILTEROPTIONS] = true; + break; + case FILESAVE_AUTOEXTENSION_SELECTION: + m_bToggleVisibility[AUTOEXTENSION] = true; + m_bToggleVisibility[SELECTION] = true; + break; + case FILESAVE_AUTOEXTENSION_TEMPLATE: + m_bToggleVisibility[AUTOEXTENSION] = true; + m_bListVisibility[TEMPLATE] = true; + break; + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + m_bToggleVisibility[LINK] = true; + m_bToggleVisibility[PREVIEW] = true; + m_bListVisibility[IMAGE_TEMPLATE] = true; + break; + case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR: + m_bToggleVisibility[LINK] = true; + m_bToggleVisibility[PREVIEW] = true; + m_bListVisibility[IMAGE_ANCHOR] = true; + break; + case FILEOPEN_READONLY_VERSION: + m_bToggleVisibility[READONLY] = true; + m_bListVisibility[VERSION] = true; + break; + case FILEOPEN_LINK_PREVIEW: + m_bToggleVisibility[LINK] = true; + m_bToggleVisibility[PREVIEW] = true; + break; + case FILESAVE_AUTOEXTENSION: + m_bToggleVisibility[AUTOEXTENSION] = true; + break; + case FILEOPEN_PREVIEW: + m_bToggleVisibility[PREVIEW] = true; + break; + case FILEOPEN_LINK_PLAY: + m_bToggleVisibility[LINK] = true; + } + + createControls(); +} + +#pragma mark XFilePickerControlAccess delegates + +// XFilePickerControlAccess functions + + +void ControlHelper::enableControl( const sal_Int16 nControlId, const bool bEnable ) const +{ + SolarMutexGuard aGuard; + + if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_PREVIEW) { + SAL_INFO("fpicker.aqua"," preview checkbox cannot be changed"); + return; + } + + NSControl* pControl = getControl(nControlId); + + if( pControl != nil ) { + if( bEnable ) { + SAL_INFO("fpicker.aqua", "enable" ); + } else { + SAL_INFO("fpicker.aqua", "disable" ); + } + [pControl setEnabled:bEnable]; + } else { + SAL_INFO("fpicker.aqua","enable unknown control " << nControlId ); + } +} + +OUString ControlHelper::getLabel( sal_Int16 nControlId ) +{ + SolarMutexGuard aGuard; + + NSControl* pControl = getControl( nControlId ); + + if( pControl == nil ) { + SAL_INFO("fpicker.aqua","Get label for unknown control " << nControlId); + return OUString(); + } + + OUString retVal; + if ([pControl class] == [NSPopUpButton class]) { + NSString *temp = m_aMapListLabels[pControl]; + if (temp != nil) + retVal = [temp OUString]; + } + else { + NSString* sLabel = [[pControl cell] title]; + retVal = [sLabel OUString]; + } + + return retVal; +} + +void ControlHelper::setLabel( sal_Int16 nControlId, NSString* aLabel ) +{ + SolarMutexGuard aGuard; + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + NSControl* pControl = getControl(nControlId); + + if (nil != pControl) { + if ([pControl class] == [NSPopUpButton class]) { + NSString *sOldName = m_aMapListLabels[pControl]; + if (sOldName != nullptr && sOldName != aLabel) { + [sOldName release]; + } + + m_aMapListLabels[pControl] = [aLabel retain]; + } else if ([pControl class] == [NSButton class]) { + [[pControl cell] setTitle:aLabel]; + } + } else { + SAL_INFO("fpicker.aqua","Control not found to set label for"); + } + + layoutControls(); + + [pool release]; +} + +void ControlHelper::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue ) +{ + SolarMutexGuard aGuard; + + if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_PREVIEW) { + SAL_INFO("fpicker.aqua"," value for preview is unchangeable"); + } + else { + NSControl* pControl = getControl( nControlId ); + + if( pControl == nil ) { + SAL_INFO("fpicker.aqua","enable unknown control " << nControlId); + } else { + if( [pControl class] == [NSPopUpButton class] ) { + HandleSetListValue(pControl, nControlAction, rValue); + } else if( [pControl class] == [NSButton class] ) { + bool bChecked = false; + rValue >>= bChecked; + SAL_INFO("fpicker.aqua"," value is a bool: " << bChecked); + [static_cast(pControl) setState:(bChecked ? NSControlStateValueOn : NSControlStateValueOff)]; + } else + { + SAL_INFO("fpicker.aqua","Can't set value on button / list " << nControlId << " " << nControlAction); + } + } + } +} + +uno::Any ControlHelper::getValue( sal_Int16 nControlId, sal_Int16 nControlAction ) const +{ + SolarMutexGuard aGuard; + uno::Any aRetval; + + NSControl* pControl = getControl( nControlId ); + + if( pControl == nil ) { + SAL_INFO("fpicker.aqua","get value for unknown control " << nControlId); + } else { + if( [pControl class] == [NSPopUpButton class] ) { + aRetval = HandleGetListValue(pControl, nControlAction); + } else if( [pControl class] == [NSButton class] ) { + //NSLog(@"control: %@", [[pControl cell] title]); + bool bValue = [static_cast(pControl) state] == NSControlStateValueOn; + aRetval <<= bValue; + SAL_INFO("fpicker.aqua","value is a bool (checkbox): " << bValue); + } + } + + return aRetval; +} + +void ControlHelper::createUserPane() +{ + if (!m_bUserPaneNeeded) { + SAL_INFO("fpicker.aqua","no user pane needed"); + return; + } + + if (nil != m_pUserPane) { + SAL_INFO("fpicker.aqua","user pane already exists"); + return; + } + + if (m_bIsFilterControlNeeded && m_pFilterControl == nil) { + createFilterControl(); + } + + NSRect minRect = NSMakeRect(0,0,300,33); + m_pUserPane = [[NSView alloc] initWithFrame:minRect]; + + int currentHeight = kAquaSpaceBoxFrameViewDiffTop + kAquaSpaceBoxFrameViewDiffBottom; + int currentWidth = 300; + + bool bPopupControlPresent = false; + bool bButtonControlPresent = false; + + int nCheckboxMaxWidth = 0; + int nPopupMaxWidth = 0; + int nPopupLabelMaxWidth = 0; + + size_t nLoop = 0; + for (auto const& activeControl : m_aActiveControls) + { + SAL_INFO("fpicker.aqua","currentHeight: " << currentHeight); + + //let the control calculate its size + [activeControl sizeToFit]; + + NSRect frame = [activeControl frame]; + SAL_INFO("fpicker.aqua","frame for control " << [[activeControl description] UTF8String] << " is {" << frame.origin.x << ", " << frame.origin.y << ", " << frame.size.width << ", " << frame.size.height << "}"); + + int nControlHeight = frame.size.height; + int nControlWidth = frame.size.width; + + // Note: controls are grouped by kind, first all popup menus, then checkboxes + if ([activeControl class] == [NSPopUpButton class]) { + if (bPopupControlPresent) { + //this is not the first popup + currentHeight += kAquaSpaceBetweenPopupMenus; + } + else if (nLoop) + { + currentHeight += kAquaSpaceBetweenControls; + } + + bPopupControlPresent = true; + + // we have to add the label text width + NSString *label = m_aMapListLabels[activeControl]; + + NSTextField *textField = createLabelWithString(label); + [textField sizeToFit]; + m_aMapListLabelFields[static_cast(activeControl)] = textField; + [m_pUserPane addSubview:textField]; + + NSRect tfRect = [textField frame]; + SAL_INFO("fpicker.aqua","frame for textfield " << [[textField description] UTF8String] << " is {" << tfRect.origin.x << ", " << tfRect.origin.y << ", " << tfRect.size.width << ", " << tfRect.size.height << "}"); + + int tfWidth = tfRect.size.width; + + if (nPopupLabelMaxWidth < tfWidth) { + nPopupLabelMaxWidth = tfWidth; + } + + frame.origin.x += (kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft) + tfWidth; + + if (nControlWidth < POPUP_WIDTH_MIN) { + nControlWidth = POPUP_WIDTH_MIN; + frame.size.width = nControlWidth; + [activeControl setFrame:frame]; + } + + if (nControlWidth > POPUP_WIDTH_MAX) { + nControlWidth = POPUP_WIDTH_MAX; + frame.size.width = nControlWidth; + [activeControl setFrame:frame]; + } + + //set the max size + if (nPopupMaxWidth < nControlWidth) { + nPopupMaxWidth = nControlWidth; + } + + nControlWidth += tfWidth + kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft; + if (nControlHeight < kAquaPopupButtonDefaultHeight) { + //maybe the popup has no menu item yet, so set a default height + nControlHeight = kAquaPopupButtonDefaultHeight; + } + + nControlHeight -= kAquaSpacePopupMenuFrameBoundsDiffV; + } + else if ([activeControl class] == [NSButton class]) { + if (nLoop) + { + currentHeight += kAquaSpaceBetweenControls; + } + + if (nCheckboxMaxWidth < nControlWidth) { + nCheckboxMaxWidth = nControlWidth; + } + + bButtonControlPresent = true; + nControlWidth -= 2 * kAquaSpaceSwitchButtonFrameBoundsDiff; + nControlHeight -= 2 * kAquaSpaceSwitchButtonFrameBoundsDiff; + } + + // if ((nControlWidth + 2 * kAquaSpaceInsideGroupH) > currentWidth) { + // currentWidth = nControlWidth + 2 * kAquaSpaceInsideGroupH; + // } + + currentHeight += nControlHeight; + + [m_pUserPane addSubview:activeControl]; + ++nLoop; + } + + SAL_INFO("fpicker.aqua","height after adding all controls: " << currentHeight); + + if (bPopupControlPresent && bButtonControlPresent) + { + //after a popup button (array) and before a different kind of control we need some extra space instead of the standard + currentHeight -= kAquaSpaceBetweenControls; + currentHeight += kAquaSpaceAfterPopupButtonsV; + SAL_INFO("fpicker.aqua","popup extra space added, currentHeight: " << currentHeight); + } + + int nLongestPopupWidth = nPopupMaxWidth + nPopupLabelMaxWidth + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH; + + currentWidth = nLongestPopupWidth > nCheckboxMaxWidth ? nLongestPopupWidth : nCheckboxMaxWidth; + SAL_INFO("fpicker.aqua","longest control width: " << currentWidth); + + currentWidth += 2* kAquaSpaceInsideGroupH; + + if (currentWidth < minRect.size.width) + currentWidth = minRect.size.width; + + if (currentHeight < minRect.size.height) + currentHeight = minRect.size.height; + + NSRect upRect = NSMakeRect(0, 0, currentWidth, currentHeight ); + SAL_INFO("fpicker.aqua","setting user pane rect to {" << upRect.origin.x << ", " << upRect.origin.y << ", " << upRect.size.width << ", " << upRect.size.height << "}"); + + [m_pUserPane setFrame:upRect]; + + layoutControls(); +} + +#pragma mark Private / Misc + +// Private / Misc + +void ControlHelper::createControls() +{ + for (int i = 0; i < LIST_LAST; i++) { + if (m_bListVisibility[i]) { + m_bUserPaneNeeded = true; + + int elementName = getControlElementName([NSPopUpButton class], i); + NSString* sLabel = CResourceProvider::getResString(elementName); + + m_pListControls[i] = [NSPopUpButton new]; + +#define MAP_LIST_( elem ) \ + case elem: \ + setLabel(ExtendedFilePickerElementIds::LISTBOX_##elem, sLabel); \ + break + + switch(i) { + MAP_LIST_(VERSION); + MAP_LIST_(TEMPLATE); + MAP_LIST_(IMAGE_TEMPLATE); + MAP_LIST_(IMAGE_ANCHOR); + } + + m_aActiveControls.push_back(m_pListControls[i]); + } else { + m_pListControls[i] = nil; + } + } + + for (int i = 0/*#i102102*/; i < TOGGLE_LAST; i++) { + if (m_bToggleVisibility[i]) { + m_bUserPaneNeeded = true; + + int elementName = getControlElementName([NSButton class], i); + NSString* sLabel = CResourceProvider::getResString(elementName); + + NSButton *button = [NSButton new]; + [button setTitle:sLabel]; + + [button setButtonType:NSButtonTypeSwitch]; + + [button setState:NSControlStateValueOff]; + + if (i == AUTOEXTENSION) { + [button setTarget:m_pDelegate]; + [button setAction:@selector(autoextensionChanged:)]; + } + + m_pToggles[i] = button; + + m_aActiveControls.push_back(m_pToggles[i]); + } else { + m_pToggles[i] = nil; + } + } + + //preview is always on with macOS + NSControl *pPreviewBox = m_pToggles[PREVIEW]; + if (pPreviewBox != nil) { + [pPreviewBox setEnabled:NO]; + [static_cast(pPreviewBox) setState:NSControlStateValueOn]; + } +} + +#define TOGGLE_ELEMENT( elem ) \ +case elem: \ + nReturn = CHECKBOX_##elem; \ + return nReturn +#define LIST_ELEMENT( elem ) \ +case elem: \ + nReturn = LISTBOX_##elem##_LABEL; \ + return nReturn + +int ControlHelper::getControlElementName(const Class aClazz, const int nControlId) +{ + int nReturn = -1; + if (aClazz == [NSButton class]) + { + switch (nControlId) { + TOGGLE_ELEMENT( AUTOEXTENSION ); + TOGGLE_ELEMENT( PASSWORD ); + TOGGLE_ELEMENT( FILTEROPTIONS ); + TOGGLE_ELEMENT( READONLY ); + TOGGLE_ELEMENT( LINK ); + TOGGLE_ELEMENT( PREVIEW ); + TOGGLE_ELEMENT( SELECTION ); + } + } + else if (aClazz == [NSPopUpButton class]) + { + switch (nControlId) { + LIST_ELEMENT( VERSION ); + LIST_ELEMENT( TEMPLATE ); + LIST_ELEMENT( IMAGE_TEMPLATE ); + LIST_ELEMENT( IMAGE_ANCHOR ); + } + } + + return nReturn; +} + +void ControlHelper::HandleSetListValue(const NSControl* pControl, const sal_Int16 nControlAction, const uno::Any& rValue) +{ + if ([pControl class] != [NSPopUpButton class]) { + SAL_INFO("fpicker.aqua","not a popup menu"); + return; + } + + NSPopUpButton *pButton = static_cast(pControl); + NSMenu *rMenu = [pButton menu]; + if (nil == rMenu) { + SAL_INFO("fpicker.aqua","button has no menu"); + return; + } + + switch (nControlAction) + { + case ControlActions::ADD_ITEM: + { + SAL_INFO("fpicker.aqua","ADD_ITEMS"); + OUString sItem; + rValue >>= sItem; + + NSString* sCFItem = [NSString stringWithOUString:sItem]; + SAL_INFO("fpicker.aqua","Adding menu item: " << sItem); + [pButton addItemWithTitle:sCFItem]; + } + break; + case ControlActions::ADD_ITEMS: + { + SAL_INFO("fpicker.aqua","ADD_ITEMS"); + uno::Sequence< OUString > aStringList; + rValue >>= aStringList; + sal_Int32 nItemCount = aStringList.getLength(); + for (sal_Int32 i = 0; i < nItemCount; ++i) + { + NSString* sCFItem = [NSString stringWithOUString:aStringList[i]]; + SAL_INFO("fpicker.aqua","Adding menu item: " << aStringList[i]); + [pButton addItemWithTitle:sCFItem]; + } + } + break; + case ControlActions::DELETE_ITEM: + { + SAL_INFO("fpicker.aqua","DELETE_ITEM"); + sal_Int32 nPos = -1; + rValue >>= nPos; + SAL_INFO("fpicker.aqua","Deleting item at position " << (nPos)); + [rMenu removeItemAtIndex:nPos]; + } + break; + case ControlActions::DELETE_ITEMS: + { + SAL_INFO("fpicker.aqua","DELETE_ITEMS"); + int nItems = [rMenu numberOfItems]; + if (nItems == 0) { + SAL_INFO("fpicker.aqua","no menu items to delete"); + return; + } + for(sal_Int32 i = 0; i < nItems; i++) { + [rMenu removeItemAtIndex:i]; + } + } + break; + case ControlActions::SET_SELECT_ITEM: + { + sal_Int32 nPos = -1; + rValue >>= nPos; + SAL_INFO("fpicker.aqua","Selecting item at position " << nPos); + [pButton selectItemAtIndex:nPos]; + } + break; + default: + SAL_INFO("fpicker.aqua","undocumented/unimplemented ControlAction for a list"); + break; + } + + layoutControls(); +} + +// cf. offapi/com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.idl +NSControl* ControlHelper::getControl( const sal_Int16 nControlId ) const +{ + NSControl* pWidget = nil; + +#define MAP_TOGGLE( elem ) \ +case ExtendedFilePickerElementIds::CHECKBOX_##elem: \ + pWidget = m_pToggles[elem]; \ + break + +#define MAP_LIST( elem ) \ +case ExtendedFilePickerElementIds::LISTBOX_##elem: \ + pWidget = m_pListControls[elem]; \ + break + +#define MAP_LIST_LABEL( elem ) \ +case ExtendedFilePickerElementIds::LISTBOX_##elem##_LABEL: \ + pWidget = m_pListControls[elem]; \ + break + + switch( nControlId ) + { + MAP_TOGGLE( AUTOEXTENSION ); + MAP_TOGGLE( PASSWORD ); + MAP_TOGGLE( FILTEROPTIONS ); + MAP_TOGGLE( READONLY ); + MAP_TOGGLE( LINK ); + MAP_TOGGLE( PREVIEW ); + MAP_TOGGLE( SELECTION ); + //MAP_BUTTON( PLAY ); + MAP_LIST( VERSION ); + MAP_LIST( TEMPLATE ); + MAP_LIST( IMAGE_TEMPLATE ); + MAP_LIST( IMAGE_ANCHOR ); + MAP_LIST_LABEL( VERSION ); + MAP_LIST_LABEL( TEMPLATE ); + MAP_LIST_LABEL( IMAGE_TEMPLATE ); + MAP_LIST_LABEL( IMAGE_ANCHOR ); + default: + SAL_INFO("fpicker.aqua","Handle unknown control " << nControlId); + break; + } +#undef MAP + + return pWidget; +} + +void ControlHelper::layoutControls() +{ + SolarMutexGuard aGuard; + + if (nil == m_pUserPane) { + SAL_INFO("fpicker.aqua","no user pane to layout"); + return; + } + + if (m_bIsUserPaneLaidOut) { + SAL_INFO("fpicker.aqua","user pane already laid out"); + return; + } + + NSRect userPaneRect = [m_pUserPane frame]; + SAL_INFO("fpicker.aqua","userPane frame: {" << userPaneRect.origin.x << ", " << userPaneRect.origin.y << ", " << userPaneRect.size.width << ", " << userPaneRect.size.height << "}"); + + int nUsableWidth = userPaneRect.size.width; + + //NOTE: NSView's coordinate system starts in the lower left hand corner but we start adding controls from the top, + // so we subtract from the vertical position as we make our way down the pane. + int currenttop = userPaneRect.size.height; + int nCheckboxMaxWidth = 0; + int nPopupMaxWidth = 0; + int nPopupLabelMaxWidth = 0; + + //first loop to determine max sizes + for (auto const& activeControl : m_aActiveControls) + { + + NSRect controlRect = [activeControl frame]; + int nControlWidth = controlRect.size.width; + + Class aSubType = [activeControl class]; + if (aSubType == [NSPopUpButton class]) { + if (nPopupMaxWidth < nControlWidth) { + nPopupMaxWidth = nControlWidth; + } + NSTextField *label = m_aMapListLabelFields[static_cast(activeControl)]; + NSRect labelFrame = [label frame]; + int nLabelWidth = labelFrame.size.width; + if (nPopupLabelMaxWidth < nLabelWidth) { + nPopupLabelMaxWidth = nLabelWidth; + } + } else { + if (nCheckboxMaxWidth < nControlWidth) { + nCheckboxMaxWidth = nControlWidth; + } + } + } + + int nLongestPopupWidth = nPopupMaxWidth + nPopupLabelMaxWidth + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH; + SAL_INFO("fpicker.aqua","longest popup width: " << nLongestPopupWidth); + + NSControl* previousControl = nil; + + int nDistBetweenControls = 0; + + for (auto const& activeControl : m_aActiveControls) + { + //get the control's bounds + NSRect controlRect = [activeControl frame]; + int nControlHeight = controlRect.size.height; + + //subtract the height from the current vertical position, because the control's bounds origin rect will be its lower left hand corner + currenttop -= nControlHeight; + + Class aSubType = [activeControl class]; + + //add space between the previous control and this control according to Apple's HIG + nDistBetweenControls = getVerticalDistance(previousControl, activeControl); + SAL_INFO("fpicker.aqua","vertical distance: " << nDistBetweenControls); + currenttop -= nDistBetweenControls; + + previousControl = activeControl; + + if (aSubType == [NSPopUpButton class]) { + //move vertically up some pixels to space the controls between their real (visual) bounds + currenttop += kAquaSpacePopupMenuFrameBoundsDiffTop;//from top + + //get the corresponding popup label + NSTextField *label = m_aMapListLabelFields[static_cast(activeControl)]; + NSRect labelFrame = [label frame]; + int totalWidth = nPopupMaxWidth + labelFrame.size.width + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH; + SAL_INFO("fpicker.aqua","totalWidth: " << totalWidth); + //let's center popups + int left = (nUsableWidth + nLongestPopupWidth) / 2 - totalWidth; + SAL_INFO("fpicker.aqua","left: " << left); + labelFrame.origin.x = left; + labelFrame.origin.y = currenttop + kAquaSpaceLabelPopupDiffV; + SAL_INFO("fpicker.aqua","setting label at: {" << labelFrame.origin.x << ", " << labelFrame.origin.y << ", " << labelFrame.size.width << ", " << labelFrame.size.height << "}"); + [label setFrame:labelFrame]; + + controlRect.origin.x = left + labelFrame.size.width + kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft; + controlRect.origin.y = currenttop; + controlRect.size.width = nPopupMaxWidth; + SAL_INFO("fpicker.aqua","setting popup at: {" << controlRect.origin.x << ", " << controlRect.origin.y << ", " << controlRect.size.width << ", " << controlRect.size.height << "}"); + [activeControl setFrame:controlRect]; + + //add some space to place the vertical position right below the popup's visual bounds + currenttop += kAquaSpacePopupMenuFrameBoundsDiffBottom; + } else { + currenttop += kAquaSpaceSwitchButtonFrameBoundsDiff;//from top + + int left = (nUsableWidth - nCheckboxMaxWidth) / 2; + controlRect.origin.x = left; + controlRect.origin.y = currenttop; + controlRect.size.width = nPopupMaxWidth; + [activeControl setFrame:controlRect]; + SAL_INFO("fpicker.aqua","setting checkbox at: {" << controlRect.origin.x << ", " << controlRect.origin.y << ", " << controlRect.size.width << ", " << controlRect.size.height << "}"); + + currenttop += kAquaSpaceSwitchButtonFrameBoundsDiff; + } + } + + m_bIsUserPaneLaidOut = true; +} + +void ControlHelper::createFilterControl() +{ + NSString* sLabel = CResourceProvider::getResString(CommonFilePickerElementIds::LISTBOX_FILTER_LABEL); + + m_pFilterControl = [NSPopUpButton new]; + + [m_pFilterControl setAction:@selector(filterSelectedAtIndex:)]; + [m_pFilterControl setTarget:m_pDelegate]; + + NSMenu *menu = [m_pFilterControl menu]; + + for (auto const& filterName : *m_pFilterHelper->getFilterNames()) + { + SAL_INFO("fpicker.aqua","adding filter name: " << [filterName UTF8String]); + if ([filterName isEqualToString:@"-"]) { + [menu addItem:[NSMenuItem separatorItem]]; + } + else { + [m_pFilterControl addItemWithTitle:filterName]; + } + } + + // always add the filter as first item + m_aActiveControls.push_front(m_pFilterControl); + m_aMapListLabels[m_pFilterControl] = [sLabel retain]; +} + +int ControlHelper::getVerticalDistance(const NSControl* first, const NSControl* second) +{ + if (first == nil) { + return kAquaSpaceBoxFrameViewDiffTop; + } + else if (second == nil) { + return kAquaSpaceBoxFrameViewDiffBottom; + } + else { + Class firstClass = [first class]; + Class secondClass = [second class]; + + if (firstClass == [NSPopUpButton class]) { + if (secondClass == [NSPopUpButton class]) { + return kAquaSpaceBetweenPopupMenus; + } + else { + return kAquaSpaceAfterPopupButtonsV; + } + } + + return kAquaSpaceBetweenControls; + } +} + +void ControlHelper::updateFilterUI() +{ + if (!m_bIsFilterControlNeeded || m_pFilterHelper == nullptr) { + SAL_INFO("fpicker.aqua","no filter control needed or no filter helper present"); + return; + } + + int index = m_pFilterHelper->getCurrentFilterIndex(); + + if (m_pFilterControl == nil) { + createFilterControl(); + } + + [m_pFilterControl selectItemAtIndex:index]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/FilterHelper.hxx b/fpicker/source/aqua/FilterHelper.hxx new file mode 100644 index 000000000..21cb9c4b4 --- /dev/null +++ b/fpicker/source/aqua/FilterHelper.hxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +typedef css::beans::StringPair UnoFilterEntry; +typedef css::uno::Sequence< UnoFilterEntry > UnoFilterList; // can be transported more effectively +typedef ::std::list NSStringList; +typedef ::std::list OUStringList; + +struct FilterEntry +{ +protected: + OUString m_sTitle; + OUStringList m_sFilterSuffixList; + UnoFilterList m_aSubFilters; + +public: + FilterEntry( const OUString& _rTitle, const OUStringList _rFilter ) + : m_sTitle( _rTitle ) + , m_sFilterSuffixList( _rFilter ) + { + } + + FilterEntry( const OUString& _rTitle, const UnoFilterList& _rSubFilters ); + + OUString const & getTitle() const { return m_sTitle; } + OUStringList const & getFilterSuffixList() const { return m_sFilterSuffixList; } + + /// determines if the filter has sub filter (i.e., the filter is a filter group in real) + bool hasSubFilters( ) const; + + /** retrieves the filters belonging to the entry + @return + the number of sub filters + */ + sal_Int32 getSubFilters( UnoFilterList& _rSubFilterList ); + + // helpers for iterating the sub filters + const UnoFilterEntry* beginSubFilters() const { return m_aSubFilters.getConstArray(); } + const UnoFilterEntry* endSubFilters() const { return m_aSubFilters.getConstArray() + m_aSubFilters.getLength(); } +}; + +typedef ::std::vector < FilterEntry > FilterList; + +class FilterHelper { + +public: + FilterHelper(); + virtual ~FilterHelper(); + + //XFilterManager delegates + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void appendFilter( const OUString& aTitle, std::u16string_view aFilter ); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void setCurrentFilter( const OUString& aTitle ); + + /// @throws css::uno::RuntimeException + OUString getCurrentFilter( ); + + //XFilterGroupManager delegates + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void appendFilterGroup( const css::uno::Sequence< css::beans::StringPair >& aFilters ); + + + //accessor + FilterList* getFilterList(); + NSStringList* getFilterNames(); + + //misc + void SetCurFilter( const OUString& rFilter ); + void SetFilterAtIndex(unsigned index); + OUStringList getCurrentFilterSuffixList(); + int getCurrentFilterIndex(); + void SetFilters(); + bool filenameMatchesFilter(NSString * sFilename); + +private: + FilterList *m_pFilterList; + OUString m_aCurrentFilter; + NSStringList *m_pFilterNames; + + bool FilterNameExists( const OUString& rTitle ); + bool FilterNameExists( const UnoFilterList& _rGroupedFilters ); + + void ensureFilterList( const OUString& _rInitialCurrentFilter ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/FilterHelper.mm b/fpicker/source/aqua/FilterHelper.mm new file mode 100644 index 000000000..f11d8447d --- /dev/null +++ b/fpicker/source/aqua/FilterHelper.mm @@ -0,0 +1,443 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "NSString_OOoAdditions.hxx" +#include "NSURL_OOoAdditions.hxx" + +#include "FilterHelper.hxx" + +namespace { + +void fillSuffixList(OUStringList& aSuffixList, std::u16string_view suffixString) { + std::size_t nIndex = 0; + do { + std::u16string_view aToken = o3tl::getToken( suffixString, u';', nIndex ); + aSuffixList.push_back(OUString(aToken.substr(1))); + } while ( nIndex != std::u16string_view::npos ); +} + +} + +#pragma mark DEFINES + +#pragma mark FilterEntry + +FilterEntry::FilterEntry( const OUString& _rTitle, const UnoFilterList& _rSubFilters ) +:m_sTitle( _rTitle ) +,m_aSubFilters( _rSubFilters ) +{ +} + + +bool FilterEntry::hasSubFilters() const +{ + bool bReturn = ( 0 < m_aSubFilters.getLength() ); + + return bReturn; +} + + +sal_Int32 FilterEntry::getSubFilters( UnoFilterList& _rSubFilterList ) +{ + _rSubFilterList = m_aSubFilters; + sal_Int32 nReturn = m_aSubFilters.getLength(); + + return nReturn; +} + +#pragma mark statics +static bool +isFilterString( std::u16string_view rFilterString, std::u16string_view pMatch ) +{ + std::size_t nIndex = 0; + std::u16string_view aToken; + bool bIsFilter = true; + + do + { + aToken = o3tl::getToken( rFilterString, u';', nIndex ); + if( !o3tl::starts_with( aToken, pMatch ) ) + { + bIsFilter = false; + break; + } + } + while( nIndex != std::u16string_view::npos ); + + return bIsFilter; +} + + + +static OUString +shrinkFilterName( const OUString& aFilterName, bool bAllowNoStar = false ) +{ + sal_Int32 nBracketEnd = -1; + OUString aRealName(aFilterName); + + for( sal_Int32 i = aRealName.getLength() - 1; i > 0; i-- ) + { + if( aFilterName[i] == ')' ) + nBracketEnd = i; + else if( aFilterName[i] == '(' ) + { + sal_Int32 nBracketLen = nBracketEnd - i; + if( nBracketEnd <= 0 ) + continue; + if( isFilterString( aFilterName.subView( i + 1, nBracketLen - 1 ), u"*." ) ) + aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" ); + else if (bAllowNoStar) + { + if( isFilterString( aFilterName.subView( i + 1, nBracketLen - 1 ), u".") ) + aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" ); + } + } + } + + return aRealName; +} + + +namespace { + + struct FilterTitleMatch + { +protected: + const OUString rTitle; + +public: + FilterTitleMatch( const OUString& _rTitle ) : rTitle( _rTitle ) { } + + + bool operator () ( const FilterEntry& _rEntry ) + { + bool bMatch; + if( !_rEntry.hasSubFilters() ) { + //first try the complete filter name + OUString title = _rEntry.getTitle(); + bMatch = title.equals(rTitle); + if (!bMatch) { + //we didn't find a match using the full name, let's give it another + //try using the shrunk version + OUString aShrunkName = shrinkFilterName( _rEntry.getTitle() ).trim(); + bMatch = aShrunkName.equals(rTitle); + } + } + else + // a filter group -> search the sub filters + bMatch = + ::std::any_of(_rEntry.beginSubFilters(), + _rEntry.endSubFilters(), + *this); + + return bMatch; + } + + bool operator () ( const UnoFilterEntry& _rEntry ) + { + OUString aShrunkName = shrinkFilterName( _rEntry.First ); + bool retVal = aShrunkName.equals(rTitle); + return retVal; + } + }; +} + +FilterHelper::FilterHelper() +: m_pFilterList(nullptr) +, m_pFilterNames(nullptr) +{ +} + +FilterHelper::~FilterHelper() +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + if (nullptr != m_pFilterList) { + delete m_pFilterList; + } + + if (nullptr != m_pFilterNames) { + //we called retain when we added the strings to the list, so we should release them now + for (NSStringList::iterator iter = m_pFilterNames->begin(); iter != m_pFilterNames->end(); ++iter) { + [*iter release]; + } + delete m_pFilterNames; + } + + [pool release]; +} + + +bool FilterHelper::FilterNameExists( const OUString& rTitle ) +{ + bool bRet = false; + + if( m_pFilterList ) + bRet = + ::std::any_of(m_pFilterList->begin(), + m_pFilterList->end(), + FilterTitleMatch( rTitle )); + + return bRet; +} + + +bool FilterHelper::FilterNameExists( const UnoFilterList& _rGroupedFilters ) +{ + bool bRet = false; + + if( m_pFilterList ) + { + const UnoFilterEntry* pStart = _rGroupedFilters.getConstArray(); + const UnoFilterEntry* pEnd = pStart + _rGroupedFilters.getLength(); + for( ; pStart != pEnd; ++pStart ) + if( ::std::any_of(m_pFilterList->begin(), + m_pFilterList->end(), + FilterTitleMatch( pStart->First ) ) ) + break; + + bRet = (pStart != pEnd); + } + + return bRet; +} + + +void FilterHelper::ensureFilterList( const OUString& _rInitialCurrentFilter ) +{ + if( nullptr == m_pFilterList ) + { + m_pFilterList = new FilterList; + + // set the first filter to the current filter + m_aCurrentFilter = _rInitialCurrentFilter; + } +} + +void FilterHelper::SetCurFilter( const OUString& rFilter ) +{ + SolarMutexGuard aGuard; + + if(!m_aCurrentFilter.equals(rFilter)) + { + m_aCurrentFilter = rFilter; + } + +} + +void FilterHelper::SetFilters() +{ + // set the default filter + if( m_aCurrentFilter.getLength() > 0 ) + { + SetCurFilter( m_aCurrentFilter ); + } +} + +void FilterHelper::appendFilter(const OUString& aTitle, std::u16string_view aFilterString) +{ + SolarMutexGuard aGuard; + + if( FilterNameExists( aTitle ) ) { + throw css::lang::IllegalArgumentException(); + } + + // ensure that we have a filter list + ensureFilterList( aTitle ); + + // append the filter + OUStringList suffixList; + fillSuffixList(suffixList, aFilterString); + m_pFilterList->push_back(FilterEntry( aTitle, suffixList ) ); +} + +void FilterHelper::setCurrentFilter( const OUString& aTitle ) +{ + SetCurFilter(aTitle); +} + +OUString FilterHelper::getCurrentFilter( ) +{ + OUString sReturn = m_aCurrentFilter; + + return sReturn; +} + +void FilterHelper::appendFilterGroup( const css::uno::Sequence< css::beans::StringPair >& aFilters ) +{ + SolarMutexGuard aGuard; + + //add a separator if this is not the first group to be added + bool bPrependSeparator = m_pFilterList != nullptr; + + // ensure that we have a filter list + OUString sInitialCurrentFilter; + if( aFilters.getLength() > 0) + sInitialCurrentFilter = aFilters[0].First; + ensureFilterList( sInitialCurrentFilter ); + + // append the filter + if (bPrependSeparator) { + OUStringList emptyList; + m_pFilterList->push_back(FilterEntry("-", emptyList)); + } + + const css::beans::StringPair* pSubFilters = aFilters.getConstArray(); + const css::beans::StringPair* pSubFiltersEnd = pSubFilters + aFilters.getLength(); + for( ; pSubFilters != pSubFiltersEnd; ++pSubFilters ) { + appendFilter(pSubFilters->First, pSubFilters->Second); + } +} + +bool FilterHelper::filenameMatchesFilter(NSString* sFilename) +{ + if (m_aCurrentFilter.isEmpty()) { + SAL_WARN("fpicker", "filter name is empty"); + return true; + } + + NSFileManager *manager = [NSFileManager defaultManager]; + NSDictionary* pAttribs = [manager attributesOfItemAtPath: sFilename error: nil]; + if( pAttribs ) + { + NSObject* pType = [pAttribs objectForKey: NSFileType]; + if( pType && [pType isKindOfClass: [NSString class]] ) + { + NSString* pT = static_cast(pType); + if( [pT isEqualToString: NSFileTypeDirectory] || + [pT isEqualToString: NSFileTypeSymbolicLink] ) + return true; + } + } + + FilterList::iterator filter = ::std::find_if(m_pFilterList->begin(), m_pFilterList->end(), FilterTitleMatch(m_aCurrentFilter)); + if (filter == m_pFilterList->end()) { + SAL_WARN("fpicker", "filter not found in list"); + return true; + } + + OUStringList suffixList = filter->getFilterSuffixList(); + + { + OUString aName = [sFilename OUString]; + for(OUStringList::iterator iter = suffixList.begin(); iter != suffixList.end(); ++iter) { + if (*iter == ".*" || aName.endsWithIgnoreAsciiCase(*iter)) { + return true; + } + } + } + + // might be an alias + NSString* pResolved = resolveAlias( sFilename ); + if( pResolved ) + { + bool bResult = filenameMatchesFilter( pResolved ); + [pResolved autorelease]; + if( bResult ) + return true; + } + + return false; +} + +FilterList* FilterHelper::getFilterList() +{ + return m_pFilterList; +} + +NSStringList* FilterHelper::getFilterNames() +{ + if (nullptr == m_pFilterList) + return nullptr; + if (nullptr == m_pFilterNames) { + //build filter names list + m_pFilterNames = new NSStringList; + for (FilterList::iterator iter = m_pFilterList->begin(); iter != m_pFilterList->end(); ++iter) { + m_pFilterNames->push_back([[NSString stringWithOUString:iter->getTitle()] retain]); + } + } + + return m_pFilterNames; +} + +void FilterHelper::SetFilterAtIndex(unsigned index) +{ + if (m_pFilterList->size() <= index) { + index = 0; + } + FilterEntry entry = m_pFilterList->at(index); + SetCurFilter(entry.getTitle()); +} + +int FilterHelper::getCurrentFilterIndex() +{ + int result = 0;//default to first filter + if (m_aCurrentFilter.getLength() > 0) { + int i = 0; + for (FilterList::iterator iter = m_pFilterList->begin(); iter != m_pFilterList->end(); ++iter, ++i) { + OUString aTitle = iter->getTitle(); + if (m_aCurrentFilter.equals(aTitle)) { + result = i; + break; + } else { + aTitle = shrinkFilterName(aTitle).trim(); + if (m_aCurrentFilter.equals(aTitle)) { + result = i; + break; + } + } + } + } + + return result; +} + +OUStringList FilterHelper::getCurrentFilterSuffixList() +{ + OUStringList retVal; + if (m_aCurrentFilter.getLength() > 0) { + for (FilterList::iterator iter = m_pFilterList->begin(); iter != m_pFilterList->end(); ++iter) { + OUString aTitle = iter->getTitle(); + if (m_aCurrentFilter.equals(aTitle)) { + retVal = iter->getFilterSuffixList(); + break; + } else { + aTitle = shrinkFilterName(aTitle).trim(); + if (m_aCurrentFilter.equals(aTitle)) { + retVal = iter->getFilterSuffixList(); + break; + } + } + } + } + + return retVal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/NSString_OOoAdditions.hxx b/fpicker/source/aqua/NSString_OOoAdditions.hxx new file mode 100644 index 000000000..eac18d46f --- /dev/null +++ b/fpicker/source/aqua/NSString_OOoAdditions.hxx @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#import +#include +#include + +//for Cocoa types +@interface NSString (OOoAdditions) ++ (id)stringWithOUString:(const OUString&)ouString; +- (OUString)OUString; +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/NSString_OOoAdditions.mm b/fpicker/source/aqua/NSString_OOoAdditions.mm new file mode 100644 index 000000000..23ae6bc5c --- /dev/null +++ b/fpicker/source/aqua/NSString_OOoAdditions.mm @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "NSString_OOoAdditions.hxx" + +@implementation NSString (OOoAdditions) + ++ (id) stringWithOUString:(const OUString&)ouString +{ + NSString *string = [[NSString alloc] initWithCharacters:reinterpret_cast(ouString.getStr()) length:ouString.getLength()]; + + return [string autorelease]; +} + +- (OUString) OUString +{ + unsigned int nFileNameLength = [self length]; + + UniChar unichars[nFileNameLength+1]; + + //'close' the string buffer correctly + unichars[nFileNameLength] = '\0'; + + [self getCharacters:unichars]; + + return OUString(reinterpret_cast(unichars)); +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/NSURL_OOoAdditions.hxx b/fpicker/source/aqua/NSURL_OOoAdditions.hxx new file mode 100644 index 000000000..f63ccfdcc --- /dev/null +++ b/fpicker/source/aqua/NSURL_OOoAdditions.hxx @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include +#include +#include + +@interface NSURL (OOoAdditions) +- (OUString)OUString; +@end + +/* + returns the resolved string if there was an alias + if there was no alias, nil is returned +*/ + +NSString* resolveAlias(NSString* i_pSystemPath); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/NSURL_OOoAdditions.mm b/fpicker/source/aqua/NSURL_OOoAdditions.mm new file mode 100644 index 000000000..5a3737e9b --- /dev/null +++ b/fpicker/source/aqua/NSURL_OOoAdditions.mm @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "NSString_OOoAdditions.hxx" +#include "NSURL_OOoAdditions.hxx" +#include + +@implementation NSURL (OOoAdditions) +- (OUString) OUString +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + NSString *sURLString = nil; + + SAL_INFO("fpicker.aqua","Extracting the full path of an item"); + sURLString = [self absoluteString]; + [sURLString retain]; + + OUString sResult = [sURLString OUString]; + [sURLString release]; + + [pool release]; + + return sResult; +} +@end + +NSString* resolveAlias( NSString* i_pSystemPath ) +{ + NSString* pResolvedPath = nil; + CFURLRef rUrl = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, + reinterpret_cast(i_pSystemPath), + kCFURLPOSIXPathStyle, false); + if( rUrl != nullptr ) + { + CFErrorRef rError; + CFDataRef rBookmark = CFURLCreateBookmarkDataFromFile( nullptr, rUrl, &rError ); + CFRelease( rUrl ); + if( rBookmark == nullptr ) + { + CFRelease( rError ); + } + else + { + Boolean bIsStale; + CFURLRef rResolvedUrl = CFURLCreateByResolvingBookmarkData( kCFAllocatorDefault, rBookmark, kCFBookmarkResolutionWithoutUIMask, + nullptr, nullptr, &bIsStale, &rError ); + CFRelease( rBookmark ); + if( rResolvedUrl == nullptr ) + { + CFRelease( rError ); + } + else + { + pResolvedPath = const_cast(reinterpret_cast(CFURLCopyFileSystemPath( rResolvedUrl, kCFURLPOSIXPathStyle ))); + CFRelease( rResolvedUrl ); + } + } + } + + return pResolvedPath; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaConstants.h b/fpicker/source/aqua/SalAquaConstants.h new file mode 100644 index 000000000..3fd14d536 --- /dev/null +++ b/fpicker/source/aqua/SalAquaConstants.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#define kAppFourCharCode 'LibO' +#define kControlPropertyTracking 'Trck' +#define kControlPropertyLastPartCode 'LsPc' +#define kControlPropertySubType 'SuTy' +#define kPopupControlPropertyTitleWidth 'PoTW' + +#define kAquaSpaceBetweenControls (8) +#define kAquaSpaceBetweenPopupMenus (10) + +#define kAquaSpaceInsideGroupH (16) +#define kAquaSpaceInsideGroupV (11) + +#define kAquaSpaceBoxFrameViewDiffTop (7) +#define kAquaSpaceBoxFrameViewDiffLeft (7) +#define kAquaSpaceBoxFrameViewDiffBottom (9) +#define kAquaSpaceBoxFrameViewDiffRight (7) + +#define kAquaSpaceButtonFrameBoundsDiff (6) +#define kAquaSpaceSwitchButtonFrameBoundsDiff (2) + +#define kAquaSpacePopupMenuFrameBoundsDiffTop (2) +#define kAquaSpacePopupMenuFrameBoundsDiffBottom (4) +#define kAquaSpacePopupMenuFrameBoundsDiffV \ + (kAquaSpacePopupMenuFrameBoundsDiffTop + kAquaSpacePopupMenuFrameBoundsDiffBottom) +#define kAquaSpacePopupMenuFrameBoundsDiffLeft (3) + +#define kAquaSpaceLabelFrameBoundsDiffH (3) +#define kAquaSpaceLabelPopupDiffV (6) +#define kAquaSpaceAfterPopupButtonsV (20) + +#define kAquaPopupButtonDefaultHeight (26) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaFilePicker.hxx b/fpicker/source/aqua/SalAquaFilePicker.hxx new file mode 100644 index 000000000..3cf13fedb --- /dev/null +++ b/fpicker/source/aqua/SalAquaFilePicker.hxx @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "SalAquaPicker.hxx" + +#include +#include "FilterHelper.hxx" +#include "AquaFilePickerDelegate.hxx" + +// Implementation class for the XFilePicker Interface + +typedef ::cppu::WeakComponentImplHelper < + css::ui::dialogs::XFilePicker3, + css::ui::dialogs::XFilePickerControlAccess, + css::lang::XInitialization, + css::lang::XServiceInfo > SalAquaFilePicker_Base; + +class SalAquaFilePicker : + public SalAquaPicker, + public SalAquaFilePicker_Base +{ +public: + + // constructor + SalAquaFilePicker(); + + // XFilePickerNotifier + + virtual void SAL_CALL addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override; + virtual void SAL_CALL removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override; + + // XExecutableDialog functions + + virtual void SAL_CALL setTitle( const OUString& aTitle ) override; + + virtual sal_Int16 SAL_CALL execute( ) override; + + // XFilePicker functions + + virtual void SAL_CALL setMultiSelectionMode( sal_Bool bMode ) override; + + virtual void SAL_CALL setDefaultName( const OUString& aName ) override; + + virtual void SAL_CALL setDisplayDirectory( const OUString& aDirectory ) override; + + virtual OUString SAL_CALL getDisplayDirectory( ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getFiles( ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSelectedFiles( ) override; + + // XFilterManager functions + + virtual void SAL_CALL appendFilter( const OUString& aTitle, const OUString& aFilter ) override; + + virtual void SAL_CALL setCurrentFilter( const OUString& aTitle ) override; + + virtual OUString SAL_CALL getCurrentFilter( ) override; + + // XFilterGroupManager functions + + virtual void SAL_CALL appendFilterGroup( const OUString& sGroupTitle, const css::uno::Sequence< css::beans::StringPair >& aFilters ) override; + + // XFilePickerControlAccess functions + + virtual void SAL_CALL setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const css::uno::Any& aValue ) override; + + virtual css::uno::Any SAL_CALL getValue( sal_Int16 aControlId, sal_Int16 aControlAction ) override; + + virtual void SAL_CALL enableControl( sal_Int16 nControlId, sal_Bool bEnable ) override; + + virtual void SAL_CALL setLabel( sal_Int16 nControlId, const OUString& aLabel ) override; + + virtual OUString SAL_CALL getLabel( sal_Int16 nControlId ) override; + + // XInitialization + + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XCancellable + + virtual void SAL_CALL cancel( ) override; + + // XEventListener + + using cppu::WeakComponentImplHelperBase::disposing; + /// @throws css::uno::RuntimeException + virtual void disposing( const css::lang::EventObject& aEvent ); + + // XServiceInfo + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // FilePicker Event functions + + void fileSelectionChanged( css::ui::dialogs::FilePickerEvent aEvent ); + void directoryChanged( css::ui::dialogs::FilePickerEvent aEvent ); + // OUString SAL_CALL helpRequested( css::ui::dialogs::FilePickerEvent aEvent ) const; + void controlStateChanged( css::ui::dialogs::FilePickerEvent aEvent ); + void dialogSizeChanged( ); + + AquaFilePickerDelegate * getDelegate() { + return m_pDelegate; + } + + OUString const & getSaveFileName() { + return m_sSaveFileName; + } + +private: + SalAquaFilePicker( const SalAquaFilePicker& ) = delete; + SalAquaFilePicker& operator=( const SalAquaFilePicker& ) = delete; + + virtual void ensureFilterHelper(); + + css::uno::Reference< css::ui::dialogs::XFilePickerListener > m_xListener; + FilterHelper *m_pFilterHelper; + OUString m_sSaveFileName; + AquaFilePickerDelegate *m_pDelegate; + + void updateFilterUI(); + void updateSaveFileNameExtension(); + +public: + + virtual ~SalAquaFilePicker() override; + + void filterControlChanged(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaFilePicker.mm b/fpicker/source/aqua/SalAquaFilePicker.mm new file mode 100644 index 000000000..01e6f80e9 --- /dev/null +++ b/fpicker/source/aqua/SalAquaFilePicker.mm @@ -0,0 +1,590 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "resourceprovider.hxx" + +#include +#include "NSString_OOoAdditions.hxx" +#include "NSURL_OOoAdditions.hxx" + +#include + +#include "SalAquaFilePicker.hxx" + +#include + +#pragma mark DEFINES + +using namespace ::com::sun::star; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::ui::dialogs::TemplateDescription; +using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; + +namespace +{ + uno::Sequence FilePicker_getSupportedServiceNames() + { + return { "com.sun.star.ui.dialogs.FilePicker", + "com.sun.star.ui.dialogs.SystemFilePicker", + "com.sun.star.ui.dialogs.AquaFilePicker" }; + } +} + +#pragma mark Constructor + +SalAquaFilePicker::SalAquaFilePicker() + : SalAquaFilePicker_Base( m_rbHelperMtx ) + , m_pFilterHelper( nullptr ) +{ + m_pDelegate = [[AquaFilePickerDelegate alloc] initWithFilePicker:this]; + m_pControlHelper->setFilePickerDelegate(m_pDelegate); +} + +SalAquaFilePicker::~SalAquaFilePicker() +{ + if (nullptr != m_pFilterHelper) + delete m_pFilterHelper; + + [m_pDelegate release]; +} + + +#pragma mark XFilePickerNotifier + +void SAL_CALL SalAquaFilePicker::addFilePickerListener( const uno::Reference& xListener ) +{ + SolarMutexGuard aGuard; + m_xListener = xListener; +} + +void SAL_CALL SalAquaFilePicker::removeFilePickerListener( const uno::Reference& ) +{ + SolarMutexGuard aGuard; + m_xListener.clear(); +} + +#pragma mark XAsynchronousExecutableDialog + +void SAL_CALL SalAquaFilePicker::setTitle( const OUString& aTitle ) +{ + SolarMutexGuard aGuard; + implsetTitle(aTitle); +} + +sal_Int16 SAL_CALL SalAquaFilePicker::execute() +{ + SolarMutexGuard aGuard; + + sal_Int16 retVal = 0; + + implInitialize(); + + // if m_pDialog is nil after initialization, something must have gone wrong before + // or there was no initialization (see issue https://bz.apache.org/ooo/show_bug.cgi?id=100214) + if (m_pDialog == nil) { + m_nDialogType = NAVIGATIONSERVICES_OPEN; + } + + if (m_pFilterHelper) { + m_pFilterHelper->SetFilters(); + } + + if (m_nDialogType == NAVIGATIONSERVICES_SAVE) { + if (m_sSaveFileName.getLength() == 0) { + //if no filename is set, NavigationServices will set the name to "untitled". We don't want this! + //So let's try to get the window title to get the real untitled name + NSWindow *frontWindow = [NSApp keyWindow]; + if (nullptr != frontWindow) { + NSString *windowTitle = [frontWindow title]; + if (windowTitle != nil) { + OUString ouName = [windowTitle OUString]; + //a window title will typically be something like "Untitled1 - OpenOffice.org Writer" + //but we only want the "Untitled1" part of it + sal_Int32 indexOfDash = ouName.indexOf(" - "); + if (indexOfDash > -1) { + m_sSaveFileName = ouName.copy(0,indexOfDash); + if (m_sSaveFileName.getLength() > 0) { + setDefaultName(m_sSaveFileName); + } + } + } + } + } + } + + //Set the delegate to be notified of certain events + + [m_pDialog setDelegate:m_pDelegate]; + + int nStatus = runandwaitforresult(); + + [m_pDialog setDelegate:nil]; + + switch( nStatus ) + { + case NSModalResponseOK: + retVal = ExecutableDialogResults::OK; + break; + + case NSModalResponseCancel: + retVal = ExecutableDialogResults::CANCEL; + break; + + default: + throw uno::RuntimeException( + "The dialog returned with an unknown result!", + static_cast( static_cast( this ) )); + break; + } + + return retVal; +} + + +#pragma mark XFilePicker + +void SAL_CALL SalAquaFilePicker::setMultiSelectionMode( sal_Bool /* bMode */ ) +{ + SolarMutexGuard aGuard; + + if (m_nDialogType == NAVIGATIONSERVICES_OPEN) { + [static_cast(m_pDialog) setAllowsMultipleSelection:YES]; + } +} + +void SAL_CALL SalAquaFilePicker::setDefaultName( const OUString& aName ) +{ + SolarMutexGuard aGuard; + + m_sSaveFileName = aName; +} + +void SAL_CALL SalAquaFilePicker::setDisplayDirectory( const OUString& rDirectory ) +{ + SolarMutexGuard aGuard; + + implsetDisplayDirectory(rDirectory); +} + +OUString SAL_CALL SalAquaFilePicker::getDisplayDirectory() +{ + OUString retVal = implgetDisplayDirectory(); + + return retVal; +} + +uno::Sequence SAL_CALL SalAquaFilePicker::getFiles() +{ + uno::Sequence< OUString > aSelectedFiles = getSelectedFiles(); + // multiselection doesn't really work with getFiles + // so just retrieve the first url + if (aSelectedFiles.getLength() > 1) + aSelectedFiles.realloc(1); + + return aSelectedFiles; +} + +uno::Sequence SAL_CALL SalAquaFilePicker::getSelectedFiles() +{ + SolarMutexGuard aGuard; + +#if HAVE_FEATURE_MACOSX_SANDBOX + static NSUserDefaults *userDefaults; + static bool triedUserDefaults = false; + + if (!triedUserDefaults) + { + userDefaults = [NSUserDefaults standardUserDefaults]; + triedUserDefaults = true; + } +#endif + + NSArray *files = nil; + if (m_nDialogType == NAVIGATIONSERVICES_OPEN) { + files = [static_cast(m_pDialog) URLs]; + } + else if (m_nDialogType == NAVIGATIONSERVICES_SAVE) { + files = [NSArray arrayWithObjects:[m_pDialog URL], nil]; + } + + NSUInteger nFiles = [files count]; + SAL_INFO("fpicker.aqua", "# of items: " << nFiles); + + uno::Sequence< OUString > aSelectedFiles(nFiles); + OUString* pSelectedFiles = aSelectedFiles.getArray(); + + for(NSUInteger nIndex = 0; nIndex < nFiles; nIndex += 1) + { + NSURL *url = [files objectAtIndex:nIndex]; + +#if HAVE_FEATURE_MACOSX_SANDBOX + if (userDefaults != NULL && + [url respondsToSelector:@selector(bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:)]) + { + // In the case of "Save As" when the user has input a new + // file name, this call will return nil, as bookmarks can + // (naturally) only be created for existing file system + // objects. In that case, code at a much lower level, in + // sal, takes care of creating a bookmark when a new file + // has been created outside the sandbox. + NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope + includingResourceValuesForKeys:nil + relativeToURL:nil + error:nil]; + if (data != NULL) + { + [userDefaults setObject:data + forKey:[@"bookmarkFor:" stringByAppendingString:[url absoluteString]]]; + } + } +#endif + + OUString sFileOrDirURL = [url OUString]; + + pSelectedFiles[nIndex] = sFileOrDirURL; + } + + return aSelectedFiles; +} + +#pragma mark XFilterManager + +void SAL_CALL SalAquaFilePicker::appendFilter( const OUString& aTitle, const OUString& aFilter ) +{ + SolarMutexGuard aGuard; + + ensureFilterHelper(); + m_pFilterHelper->appendFilter( aTitle, aFilter ); + m_pControlHelper->setFilterControlNeeded(true); +} + +void SAL_CALL SalAquaFilePicker::setCurrentFilter( const OUString& aTitle ) +{ + SolarMutexGuard aGuard; + + ensureFilterHelper(); + m_pFilterHelper->setCurrentFilter(aTitle); + updateFilterUI(); + + updateSaveFileNameExtension(); +} + +OUString SAL_CALL SalAquaFilePicker::getCurrentFilter() +{ + SolarMutexGuard aGuard; + + ensureFilterHelper(); + + return m_pFilterHelper->getCurrentFilter(); +} + +#pragma mark XFilterGroupManager + +void SAL_CALL SalAquaFilePicker::appendFilterGroup( const OUString&, const uno::Sequence& aFilters ) +{ + SolarMutexGuard aGuard; + + ensureFilterHelper(); + m_pFilterHelper->appendFilterGroup(aFilters); + m_pControlHelper->setFilterControlNeeded(true); +} + +#pragma mark XFilePickerControlAccess + +void SAL_CALL SalAquaFilePicker::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue ) +{ + SolarMutexGuard aGuard; + + m_pControlHelper->setValue(nControlId, nControlAction, rValue); + + if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION && m_nDialogType == NAVIGATIONSERVICES_SAVE) { + updateSaveFileNameExtension(); + } +} + +uno::Any SAL_CALL SalAquaFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction ) +{ + uno::Any aValue = m_pControlHelper->getValue(nControlId, nControlAction); + + return aValue; +} + +void SAL_CALL SalAquaFilePicker::enableControl( sal_Int16 nControlId, sal_Bool bEnable ) +{ + m_pControlHelper->enableControl(nControlId, bEnable); +} + +void SAL_CALL SalAquaFilePicker::setLabel( sal_Int16 nControlId, const OUString& aLabel ) +{ + SolarMutexGuard aGuard; + + NSString* sLabel = [NSString stringWithOUString:aLabel]; + m_pControlHelper->setLabel( nControlId, sLabel ) ; +} + +OUString SAL_CALL SalAquaFilePicker::getLabel( sal_Int16 nControlId ) +{ + return m_pControlHelper->getLabel(nControlId); +} + +#pragma mark XInitialization + +void SAL_CALL SalAquaFilePicker::initialize( const uno::Sequence& aArguments ) +{ + SolarMutexGuard aGuard; + + // parameter checking + uno::Any aAny; + if( 0 == aArguments.getLength() ) + throw lang::IllegalArgumentException("no arguments", + static_cast( static_cast(this) ), 1 ); + + aAny = aArguments[0]; + + if( ( aAny.getValueType() != ::cppu::UnoType::get() ) && + (aAny.getValueType() != ::cppu::UnoType::get() ) ) + throw lang::IllegalArgumentException("invalid argument type", + static_cast( static_cast(this) ), 1 ); + + sal_Int16 templateId = -1; + aAny >>= templateId; + + switch( templateId ) + { + case FILEOPEN_SIMPLE: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILESAVE_SIMPLE: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILESAVE_AUTOEXTENSION_PASSWORD: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILESAVE_AUTOEXTENSION_SELECTION: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILESAVE_AUTOEXTENSION_TEMPLATE: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILEOPEN_PLAY: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILEOPEN_LINK_PLAY: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILEOPEN_READONLY_VERSION: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILEOPEN_LINK_PREVIEW: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + case FILESAVE_AUTOEXTENSION: + m_nDialogType = NAVIGATIONSERVICES_SAVE; + break; + case FILEOPEN_PREVIEW: + m_nDialogType = NAVIGATIONSERVICES_OPEN; + break; + default: + throw lang::IllegalArgumentException("Unknown template", + static_cast( static_cast(this) ), + 1 ); + } + + m_pControlHelper->initialize(templateId); + + implInitialize(); +} + +#pragma mark XCancellable + +void SAL_CALL SalAquaFilePicker::cancel() +{ + SolarMutexGuard aGuard; + + if (m_pDialog != nil) { + [m_pDialog cancel:nil]; + } +} + +#pragma mark XEventListener + +void SalAquaFilePicker::disposing( const lang::EventObject& aEvent ) +{ + SolarMutexGuard aGuard; + + uno::Reference xFilePickerListener( aEvent.Source, css::uno::UNO_QUERY ); + + if( xFilePickerListener.is() ) + removeFilePickerListener( xFilePickerListener ); +} + +#pragma mark XServiceInfo + +OUString SAL_CALL SalAquaFilePicker::getImplementationName() +{ + return "com.sun.star.ui.dialogs.SalAquaFilePicker"; +} + +sal_Bool SAL_CALL SalAquaFilePicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +uno::Sequence SAL_CALL SalAquaFilePicker::getSupportedServiceNames() +{ + return FilePicker_getSupportedServiceNames(); +} + +#pragma mark Misc/Private + +void SalAquaFilePicker::fileSelectionChanged( FilePickerEvent aEvent ) +{ + if (m_xListener.is()) + m_xListener->fileSelectionChanged( aEvent ); +} + +void SalAquaFilePicker::directoryChanged( FilePickerEvent aEvent ) +{ + if (m_xListener.is()) + m_xListener->directoryChanged( aEvent ); +} + +void SalAquaFilePicker::controlStateChanged( FilePickerEvent aEvent ) +{ + if (m_xListener.is()) + m_xListener->controlStateChanged( aEvent ); +} + +void SalAquaFilePicker::dialogSizeChanged() +{ + if (m_xListener.is()) + m_xListener->dialogSizeChanged(); +} + + +// Misc + +void SalAquaFilePicker::ensureFilterHelper() +{ + SolarMutexGuard aGuard; + + if (nullptr == m_pFilterHelper) { + m_pFilterHelper = new FilterHelper; + m_pControlHelper->setFilterHelper(m_pFilterHelper); + [m_pDelegate setFilterHelper:m_pFilterHelper]; + } +} + +void SalAquaFilePicker::updateFilterUI() +{ + m_pControlHelper->updateFilterUI(); +} + +void SalAquaFilePicker::updateSaveFileNameExtension() +{ + if (m_nDialogType != NAVIGATIONSERVICES_SAVE) { + return; + } + + // we need to set this here again because initial setting does + //[m_pDialog setExtensionHidden:YES]; + + SolarMutexGuard aGuard; + + if (!m_pControlHelper->isAutoExtensionEnabled()) { + SAL_WNODEPRECATED_DECLARATIONS_PUSH // setAllowedFileTypes (12.0) + [m_pDialog setAllowedFileTypes:nil]; + SAL_WNODEPRECATED_DECLARATIONS_POP + [m_pDialog setAllowsOtherFileTypes:YES]; + } else { + ensureFilterHelper(); + + OUStringList aStringList = m_pFilterHelper->getCurrentFilterSuffixList(); + if( aStringList.empty()) // #i9328# + return; + + OUString suffix = (*(aStringList.begin())).copy(1); + NSString *requiredFileType = [NSString stringWithOUString:suffix]; + + SAL_WNODEPRECATED_DECLARATIONS_PUSH // setAllowedFileTypes (12.0) + [m_pDialog setAllowedFileTypes:[NSArray arrayWithObjects:requiredFileType, nil]]; + SAL_WNODEPRECATED_DECLARATIONS_POP + + [m_pDialog setAllowsOtherFileTypes:NO]; + } +} + +void SalAquaFilePicker::filterControlChanged() +{ + if (m_pDialog == nil) { + return; + } + + SolarMutexGuard aGuard; + + updateSaveFileNameExtension(); + + [m_pDialog validateVisibleColumns]; + + FilePickerEvent evt; + evt.ElementId = LISTBOX_FILTER; + controlStateChanged( evt ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_SalAquaFilePicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence const&) +{ + return cppu::acquire(new SalAquaFilePicker()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaFolderPicker.hxx b/fpicker/source/aqua/SalAquaFolderPicker.hxx new file mode 100644 index 000000000..f563b1983 --- /dev/null +++ b/fpicker/source/aqua/SalAquaFolderPicker.hxx @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "SalAquaPicker.hxx" + +#include + + + + +class SalAquaFolderPicker : + public SalAquaPicker, + public cppu::WeakImplHelper< + css::ui::dialogs::XFolderPicker2, + css::lang::XServiceInfo, + css::lang::XEventListener > +{ +public: + + // constructor + SalAquaFolderPicker(); + + + // XExecutableDialog functions + + + virtual void SAL_CALL setTitle( const OUString& aTitle ) override; + + virtual sal_Int16 SAL_CALL execute( ) override; + + + // XFolderPicker functions + + + virtual void SAL_CALL setDisplayDirectory( const OUString& rDirectory ) override; + + virtual OUString SAL_CALL getDisplayDirectory( ) override; + + virtual OUString SAL_CALL getDirectory( ) override; + + virtual void SAL_CALL setDescription( const OUString& rDescription ) override; + + + // XServiceInfo + + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + + // XCancellable + + + virtual void SAL_CALL cancel( ) override; + + + // XEventListener + + + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; + +private: + SalAquaFolderPicker( const SalAquaFolderPicker& ) = delete; + SalAquaFolderPicker& operator=( const SalAquaFolderPicker& ) = delete; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaFolderPicker.mm b/fpicker/source/aqua/SalAquaFolderPicker.mm new file mode 100644 index 000000000..3dabf488a --- /dev/null +++ b/fpicker/source/aqua/SalAquaFolderPicker.mm @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SalAquaFolderPicker.hxx" + +#include + +#include "resourceprovider.hxx" + +#include +#include "NSString_OOoAdditions.hxx" +#include "NSURL_OOoAdditions.hxx" + +#pragma mark DEFINES + +using namespace ::com::sun::star; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; + +SalAquaFolderPicker::SalAquaFolderPicker() +{ + m_nDialogType = NAVIGATIONSERVICES_DIRECTORY; +} + +// XExecutableDialog + +void SAL_CALL SalAquaFolderPicker::setTitle( const OUString& aTitle ) +{ + SolarMutexGuard aGuard; + + implsetTitle(aTitle); +} + +sal_Int16 SAL_CALL SalAquaFolderPicker::execute() +{ + SolarMutexGuard aGuard; + + sal_Int16 retVal = 0; + + int nResult = runandwaitforresult(); + + switch( nResult ) + { + case NSModalResponseOK: + retVal = ExecutableDialogResults::OK; + break; + + case NSModalResponseCancel: + retVal = ExecutableDialogResults::CANCEL; + break; + + default: + throw uno::RuntimeException("The dialog returned with an unknown result!", static_cast< cppu::OWeakObject * >( this )); + break; + } + + return retVal; +} + +// XFolderPicker + +void SAL_CALL SalAquaFolderPicker::setDisplayDirectory( const OUString& aDirectory ) +{ + SolarMutexGuard aGuard; + + implsetDisplayDirectory(aDirectory); +} + +OUString SAL_CALL SalAquaFolderPicker::getDisplayDirectory() +{ + SolarMutexGuard aGuard; + + OUString aDirectory = implgetDisplayDirectory(); + + return aDirectory; +} + +OUString SAL_CALL SalAquaFolderPicker::getDirectory() +{ + SolarMutexGuard aGuard; + + NSArray *files = nil; + if (m_nDialogType == NAVIGATIONSERVICES_DIRECTORY) { + files = [static_cast(m_pDialog) URLs]; + } + + NSUInteger nFiles = [files count]; + SAL_INFO("fpicker.aqua", "# of items: " << nFiles); + + if (nFiles < 1) { + throw uno::RuntimeException("no directory selected", static_cast< cppu::OWeakObject * >( this )); + } + + OUString aDirectory; + + NSURL *url = [files objectAtIndex:0]; + + aDirectory = [url OUString]; + + implsetDisplayDirectory(aDirectory); + + return aDirectory; +} + +void SAL_CALL SalAquaFolderPicker::setDescription( const OUString& rDescription ) +{ + [m_pDialog setMessage:[NSString stringWithOUString:rDescription]]; +} + +// XServiceInfo + +OUString SAL_CALL SalAquaFolderPicker::getImplementationName() +{ + return "com.sun.star.ui.dialogs.SalAquaFolderPicker"; +} + +sal_Bool SAL_CALL SalAquaFolderPicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +uno::Sequence SAL_CALL SalAquaFolderPicker::getSupportedServiceNames() +{ + return { "com.sun.star.ui.dialogs.SystemFolderPicker", "com.sun.star.ui.dialogs.AquaFolderPicker" }; +} + +// XCancellable + +void SAL_CALL SalAquaFolderPicker::cancel() +{ + SolarMutexGuard aGuard; + + [m_pDialog cancel:nil]; +} + +// XEventListener + +void SAL_CALL SalAquaFolderPicker::disposing( const lang::EventObject& ) +{ +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_SalAquaFolderPicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence const&) +{ + return cppu::acquire(new SalAquaFolderPicker()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaPicker.hxx b/fpicker/source/aqua/SalAquaPicker.hxx new file mode 100644 index 000000000..4eab78c52 --- /dev/null +++ b/fpicker/source/aqua/SalAquaPicker.hxx @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include + +#include + +#include + +#include +#include "ControlHelper.hxx" + +#include +#import +#include + +class SalAquaPicker +{ +public: + // constructor + SalAquaPicker(); + virtual ~SalAquaPicker(); + + int run(); + int runandwaitforresult(); + + OUString const& getDisplayDirectory() { return m_sDisplayDirectory; } + + ControlHelper* getControlHelper() const { return m_pControlHelper; } + +protected: + OUString m_sDisplayDirectory; + + NSSavePanel* m_pDialog; + + ControlHelper* m_pControlHelper; + + osl::Mutex m_rbHelperMtx; + + // The type of dialog + enum NavigationServices_DialogType + { + NAVIGATIONSERVICES_OPEN, + NAVIGATIONSERVICES_SAVE, + NAVIGATIONSERVICES_DIRECTORY + }; + + NavigationServices_DialogType m_nDialogType; + + /// @throws css::uno::RuntimeException + void implsetTitle(const OUString& aTitle); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void implsetDisplayDirectory(const OUString& rDirectory); + + /// @throws css::uno::RuntimeException + OUString const& implgetDisplayDirectory(); + + void implInitialize(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/SalAquaPicker.mm b/fpicker/source/aqua/SalAquaPicker.mm new file mode 100644 index 000000000..c2cf497f5 --- /dev/null +++ b/fpicker/source/aqua/SalAquaPicker.mm @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "SalAquaPicker.hxx" +#include +#include "NSString_OOoAdditions.hxx" + +#include "NSURL_OOoAdditions.hxx" + +#include "SalAquaFilePicker.hxx" + +#include + +#pragma mark DEFINES +#define kSetHideExtensionStateKey @"NSNavLastUserSetHideExtensionButtonState" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; + +SalAquaPicker::SalAquaPicker() +: m_pDialog(nullptr) +, m_pControlHelper(new ControlHelper()) +{ +} + +SalAquaPicker::~SalAquaPicker() +{ + SolarMutexGuard aGuard; + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + if (nullptr != m_pControlHelper) + delete m_pControlHelper; + + if (nullptr != m_pDialog) + [m_pDialog release]; + + [pool release]; +} + +void SalAquaPicker::implInitialize() +{ + SolarMutexGuard aGuard; + + if (m_pDialog != nil) { + return; + } + + switch (m_nDialogType) + { + case NAVIGATIONSERVICES_OPEN: + m_pDialog = [NSOpenPanel openPanel]; + [static_cast(m_pDialog) setCanChooseDirectories:NO]; + [static_cast(m_pDialog) setCanChooseFiles:YES]; + break; + + case NAVIGATIONSERVICES_SAVE: + m_pDialog = [NSSavePanel savePanel]; + [m_pDialog setCanSelectHiddenExtension:NO]; //changed for issue #102102 + /* I would have loved to use + * [(NSSavePanel*)m_pDialog setExtensionHidden:YES]; + * here but unfortunately this + * a) only works when the dialog is already displayed because it seems to act on the corresponding checkbox (that we don't show but that doesn't matter) + * b) macOS saves this setting on an application-based level which means that the last state is always being restored again when the app runs for the next time + * + * So the only reliable way seems to be using the NSUserDefaults object because that is where that value is stored and + * to just overwrite it if it has the wrong value. + */ + { + NSUserDefaults *pDefaults = [NSUserDefaults standardUserDefaults]; + NSNumber *pExtn = [pDefaults objectForKey:kSetHideExtensionStateKey]; + if(pExtn == nil || [pExtn boolValue] == NO) { + [pDefaults setBool:YES forKey:kSetHideExtensionStateKey]; + } + } + break; + + case NAVIGATIONSERVICES_DIRECTORY: + m_pDialog = [NSOpenPanel openPanel]; + [static_cast(m_pDialog) setCanChooseDirectories:YES]; + [static_cast(m_pDialog) setCanChooseFiles:NO]; + break; + + default: + break; + } + + if (m_pDialog != nil) { + [static_cast(m_pDialog) setCanCreateDirectories:YES]; + //Retain the dialog instance or it will go away immediately + [m_pDialog retain]; + } +} + +int SalAquaPicker::run() +{ + SolarMutexGuard aGuard; + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + if (m_pDialog == nullptr) { + //this is the case e.g. for the folder picker at this stage + implInitialize(); + } + + NSView *userPane = m_pControlHelper->getUserPane(); + if (userPane != nullptr) { + [m_pDialog setAccessoryView:userPane]; + } + + int retVal = 0; + + NSURL *startDirectory; + if (m_sDisplayDirectory.getLength() > 0) { + NSString *temp = [NSString stringWithOUString:m_sDisplayDirectory]; + startDirectory = [NSURL URLWithString:temp]; + + SAL_INFO("fpicker.aqua", "start dir: " << [startDirectory path]); + } + else { + startDirectory = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES]; + } + + switch(m_nDialogType) { + case NAVIGATIONSERVICES_DIRECTORY: + case NAVIGATIONSERVICES_OPEN: + [m_pDialog setDirectoryURL:startDirectory]; + retVal = [static_cast(m_pDialog) runModal]; + break; + case NAVIGATIONSERVICES_SAVE: + [m_pDialog setDirectoryURL:startDirectory]; + [m_pDialog setNameFieldStringValue:[NSString stringWithOUString:static_cast(this)->getSaveFileName()]]; + retVal = [m_pDialog runModal]; + break; + default: + break; + } + + if (retVal == NSModalResponseOK) { + NSURL* pDir = [m_pDialog directoryURL]; + if (pDir) { + implsetDisplayDirectory([pDir OUString]); + } + } + + [pool release]; + + return retVal; +} + +int SalAquaPicker::runandwaitforresult() +{ + SolarMutexGuard aGuard; + + int status = run(); + + return status; +} + +void SalAquaPicker::implsetDisplayDirectory( const OUString& aDirectory ) +{ + SolarMutexGuard aGuard; + + if (aDirectory != m_sDisplayDirectory) { + m_sDisplayDirectory = aDirectory; + } +} + +OUString const & SalAquaPicker::implgetDisplayDirectory() +{ + return m_sDisplayDirectory; +} + +void SalAquaPicker::implsetTitle( const OUString& aTitle ) +{ + SolarMutexGuard aGuard; + + if (m_pDialog != nil) { + [m_pDialog setTitle:[NSString stringWithOUString:aTitle]]; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/fps_aqua.component b/fpicker/source/aqua/fps_aqua.component new file mode 100644 index 000000000..bf143911a --- /dev/null +++ b/fpicker/source/aqua/fps_aqua.component @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/fpicker/source/aqua/resourceprovider.hxx b/fpicker/source/aqua/resourceprovider.hxx new file mode 100644 index 000000000..ba581389f --- /dev/null +++ b/fpicker/source/aqua/resourceprovider.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include + +#include + +#include + +#include +#include +#include + +#define FOLDERPICKER_TITLE 500 +#define FOLDER_PICKER_DEF_DESCRIPTION 501 +#define FILE_PICKER_TITLE_OPEN 502 +#define FILE_PICKER_TITLE_SAVE 503 +#define FILE_PICKER_FILE_TYPE 504 +#define FILE_PICKER_OVERWRITE 505 + +namespace CResourceProvider { + +NSString* getResString( sal_Int32 aId ); + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/aqua/resourceprovider.mm b/fpicker/source/aqua/resourceprovider.mm new file mode 100644 index 000000000..951833ae2 --- /dev/null +++ b/fpicker/source/aqua/resourceprovider.mm @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NSString_OOoAdditions.hxx" +#include +#include "resourceprovider.hxx" + +using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds; + +// we have to translate control ids to resource ids + +namespace { + +struct Entry +{ + sal_Int32 ctrlId; + TranslateId resId; +}; + +} + +Entry const CtrlIdToResIdTable[] = { + { CHECKBOX_AUTOEXTENSION, STR_SVT_FILEPICKER_AUTO_EXTENSION }, + { CHECKBOX_PASSWORD, STR_SVT_FILEPICKER_PASSWORD }, + { CHECKBOX_FILTEROPTIONS, STR_SVT_FILEPICKER_FILTER_OPTIONS }, + { CHECKBOX_READONLY, STR_SVT_FILEPICKER_READONLY }, + { CHECKBOX_LINK, STR_SVT_FILEPICKER_INSERT_AS_LINK }, + { CHECKBOX_PREVIEW, STR_SVT_FILEPICKER_SHOW_PREVIEW }, + { PUSHBUTTON_PLAY, STR_SVT_FILEPICKER_PLAY }, + { LISTBOX_VERSION_LABEL, STR_SVT_FILEPICKER_VERSION }, + { LISTBOX_TEMPLATE_LABEL, STR_SVT_FILEPICKER_TEMPLATES }, + { LISTBOX_IMAGE_TEMPLATE_LABEL, STR_SVT_FILEPICKER_IMAGE_TEMPLATE }, + { LISTBOX_IMAGE_ANCHOR_LABEL, STR_SVT_FILEPICKER_IMAGE_ANCHOR }, + { CHECKBOX_SELECTION, STR_SVT_FILEPICKER_SELECTION }, + { FOLDERPICKER_TITLE, STR_SVT_FOLDERPICKER_DEFAULT_TITLE }, + { FOLDER_PICKER_DEF_DESCRIPTION, STR_SVT_FOLDERPICKER_DEFAULT_DESCRIPTION }, + { FILE_PICKER_OVERWRITE, STR_SVT_ALREADYEXISTOVERWRITE }, + { LISTBOX_FILTER_LABEL, STR_SVT_FILEPICKER_FILTER_TITLE}, + { FILE_PICKER_TITLE_OPEN, STR_FILEDLG_OPEN }, + { FILE_PICKER_TITLE_SAVE, STR_FILEDLG_SAVE }, + { FILE_PICKER_FILE_TYPE, STR_FILEDLG_TYPE } +}; + +const sal_Int32 SIZE_TABLE = SAL_N_ELEMENTS( CtrlIdToResIdTable ); + +static TranslateId CtrlIdToResId(sal_Int32 aControlId) +{ + TranslateId pResId; + + for ( sal_Int32 i = 0; i < SIZE_TABLE; i++ ) + { + if ( CtrlIdToResIdTable[i].ctrlId == aControlId ) + { + pResId = CtrlIdToResIdTable[i].resId; + break; + } + } + + return pResId; +} + +namespace CResourceProvider_Impl +{ + static NSString* getResString(sal_Int16 aId) + { + OUString aResString; + + // translate the control id to a resource id + TranslateId pResId = CtrlIdToResId(aId); + if (pResId) + aResString = FpsResId(pResId); + + return [NSString stringWithOUString:aResString]; + } +}; + +NSString* CResourceProvider::getResString( sal_Int32 aId ) +{ + NSString* sImmutable = CResourceProvider_Impl::getResString(aId); + NSMutableString *sMutableString = [NSMutableString stringWithString:sImmutable]; + [sMutableString replaceOccurrencesOfString:@"~" withString:@"" options:0 range:NSMakeRange(0, [sMutableString length])]; + + NSString *result = [NSString stringWithString:sMutableString]; + + return result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3