diff options
Diffstat (limited to 'fpicker')
94 files changed, 22294 insertions, 0 deletions
diff --git a/fpicker/AllLangMoTarget_fps.mk b/fpicker/AllLangMoTarget_fps.mk new file mode 100644 index 000000000..cd4495b0b --- /dev/null +++ b/fpicker/AllLangMoTarget_fps.mk @@ -0,0 +1,13 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. + +$(eval $(call gb_AllLangMoTarget_AllLangMoTarget,fps)) + +$(eval $(call gb_AllLangMoTarget_set_polocation,fps,fpicker)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/CppunitTest_fpicker_dialogs_test.mk b/fpicker/CppunitTest_fpicker_dialogs_test.mk new file mode 100644 index 000000000..398150060 --- /dev/null +++ b/fpicker/CppunitTest_fpicker_dialogs_test.mk @@ -0,0 +1,63 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitScreenShot,fpicker_dialogs_test)) + +$(eval $(call gb_CppunitTest_add_exception_objects,fpicker_dialogs_test, \ + fpicker/qa/unit/fpicker-dialogs-test \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,fpicker_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_libraries,fpicker_dialogs_test, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + drawinglayer \ + editeng \ + i18nlangtag \ + i18nutil \ + msfilter \ + oox \ + sal \ + salhelper \ + sax \ + sfx \ + sot \ + svl \ + svt \ + test \ + tl \ + tk \ + ucbhelper \ + unotest \ + utl \ + vcl \ + xo \ +)) + +$(eval $(call gb_CppunitTest_use_external,fpicker_dialogs_test,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,fpicker_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_ure,fpicker_dialogs_test)) +$(eval $(call gb_CppunitTest_use_vcl_non_headless_with_windows,fpicker_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_rdb,fpicker_dialogs_test,services)) + +$(eval $(call gb_CppunitTest_use_configuration,fpicker_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,fpicker_dialogs_test,\ + fps \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/IwyuFilter_fpicker.yaml b/fpicker/IwyuFilter_fpicker.yaml new file mode 100644 index 000000000..978f04862 --- /dev/null +++ b/fpicker/IwyuFilter_fpicker.yaml @@ -0,0 +1,2 @@ +--- +assumeFilename: fpicker/source/office/iodlg.cxx diff --git a/fpicker/Library_fps.mk b/fpicker/Library_fps.mk new file mode 100644 index 000000000..3e14f03b7 --- /dev/null +++ b/fpicker/Library_fps.mk @@ -0,0 +1,65 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_Library_Library,fps)) + +$(eval $(call gb_Library_use_custom_headers,fps,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_set_componentfile,fps,fpicker/source/win32/fps,services)) + +$(eval $(call gb_Library_set_include,fps,\ + $$(INCLUDE) \ + -I$(SRCDIR)/fpicker/inc \ +)) + +$(eval $(call gb_Library_use_external,fps,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,fps)) + +$(eval $(call gb_Library_use_libraries,fps,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + i18nlangtag \ + tl \ + utl \ + vcl \ +)) + +$(eval $(call gb_Library_use_system_win32_libs,fps,\ + advapi32 \ + comdlg32 \ + gdi32 \ + kernel32 \ + ole32 \ + oleaut32 \ + shell32 \ + uuid \ +)) + +ifeq ($(COM),MSC) +$(eval $(call gb_Library_add_libs,fps,\ + Delayimp.lib /DELAYLOAD:shell32.dll \ +)) +endif + +$(eval $(call gb_Library_add_exception_objects,fps,\ + fpicker/source/win32/FilterContainer \ + fpicker/source/win32/VistaFilePicker \ + fpicker/source/win32/VistaFilePickerEventHandler \ + fpicker/source/win32/VistaFilePickerImpl \ + fpicker/source/win32/resourceprovider \ + fpicker/source/win32/WinImplHelper \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/Library_fps_aqua.mk b/fpicker/Library_fps_aqua.mk new file mode 100644 index 000000000..0b8db2e37 --- /dev/null +++ b/fpicker/Library_fps_aqua.mk @@ -0,0 +1,50 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_Library_Library,fps_aqua)) + +$(eval $(call gb_Library_set_componentfile,fps_aqua,fpicker/source/aqua/fps_aqua,services)) + +$(eval $(call gb_Library_set_include,fps_aqua,\ + $$(INCLUDE) \ + -I$(SRCDIR)/fpicker/inc \ +)) + +$(eval $(call gb_Library_use_external,fps_aqua,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,fps_aqua)) + +$(eval $(call gb_Library_use_system_darwin_frameworks,fps_aqua,\ + Cocoa \ + CoreFoundation \ +)) + +$(eval $(call gb_Library_use_libraries,fps_aqua,\ + cppu \ + cppuhelper \ + i18nlangtag \ + sal \ + utl \ + vcl \ +)) + +$(eval $(call gb_Library_add_objcxxobjects,fps_aqua,\ + fpicker/source/aqua/AquaFilePickerDelegate \ + fpicker/source/aqua/ControlHelper \ + fpicker/source/aqua/FilterHelper \ + fpicker/source/aqua/NSString_OOoAdditions \ + fpicker/source/aqua/NSURL_OOoAdditions \ + fpicker/source/aqua/resourceprovider \ + fpicker/source/aqua/SalAquaFilePicker \ + fpicker/source/aqua/SalAquaFolderPicker \ + fpicker/source/aqua/SalAquaPicker \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/Library_fps_office.mk b/fpicker/Library_fps_office.mk new file mode 100644 index 000000000..d7e41e66e --- /dev/null +++ b/fpicker/Library_fps_office.mk @@ -0,0 +1,63 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_Library_Library,fps_office)) + +$(eval $(call gb_Library_set_componentfile,fps_office,fpicker/source/office/fps_office,services)) + +$(eval $(call gb_Library_set_include,fps_office,\ + $$(INCLUDE) \ + -I$(SRCDIR)/fpicker/inc \ +)) + +$(eval $(call gb_Library_use_external,fps_office,boost_headers)) + +$(eval $(call gb_Library_use_custom_headers,fps_office,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_sdk_api,fps_office)) + +$(eval $(call gb_Library_use_libraries,fps_office,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + salhelper \ + svl \ + svt \ + tk \ + tl \ + ucbhelper \ + utl \ + vcl \ + i18nlangtag \ +)) + +$(eval $(call gb_Library_add_exception_objects,fps_office,\ + fpicker/source/office/asyncfilepicker \ + fpicker/source/office/autocmpledit \ + fpicker/source/office/breadcrumb \ + fpicker/source/office/commonpicker \ + fpicker/source/office/contentenumeration \ + fpicker/source/office/fileview \ + fpicker/source/office/foldertree \ + fpicker/source/office/fpinteraction \ + fpicker/source/office/fpsmartcontent \ + fpicker/source/office/iodlg \ + fpicker/source/office/iodlgimp \ + fpicker/source/office/OfficeControlAccess \ + fpicker/source/office/OfficeFilePicker \ + fpicker/source/office/OfficeFolderPicker \ + fpicker/source/office/PlacesListBox \ + fpicker/source/office/RemoteFilesDialog \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/Makefile b/fpicker/Makefile new file mode 100644 index 000000000..0997e6284 --- /dev/null +++ b/fpicker/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/Module_fpicker.mk b/fpicker/Module_fpicker.mk new file mode 100644 index 000000000..d05b4b20d --- /dev/null +++ b/fpicker/Module_fpicker.mk @@ -0,0 +1,39 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_Module_Module,fpicker)) + +$(eval $(call gb_Module_add_targets,fpicker,\ + Library_fps_office \ + UIConfig_fps \ +)) + +$(eval $(call gb_Module_add_l10n_targets,fpicker,\ + AllLangMoTarget_fps \ +)) + +ifeq ($(OS),MACOSX) +$(eval $(call gb_Module_add_targets,fpicker,\ + Library_fps_aqua \ +)) +endif + +ifeq ($(OS),WNT) +$(eval $(call gb_Module_add_targets,fpicker,\ + Library_fps \ +)) +endif + +# screenshots +$(eval $(call gb_Module_add_screenshot_targets,fpicker,\ + CppunitTest_fpicker_dialogs_test \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/README.md b/fpicker/README.md new file mode 100644 index 000000000..6f4bae6c4 --- /dev/null +++ b/fpicker/README.md @@ -0,0 +1,3 @@ +# Native File Picker + +Native file pickers for macOS and Windows (file open dialog). diff --git a/fpicker/UIConfig_fps.mk b/fpicker/UIConfig_fps.mk new file mode 100644 index 000000000..2c179b296 --- /dev/null +++ b/fpicker/UIConfig_fps.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_UIConfig_UIConfig,fps)) + +$(eval $(call gb_UIConfig_add_uifiles,fps,\ + fpicker/uiconfig/ui/breadcrumb \ + fpicker/uiconfig/ui/explorerfiledialog \ + fpicker/uiconfig/ui/foldernamedialog \ + fpicker/uiconfig/ui/remotefilesdialog \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/fpicker/inc/bitmaps.hlst b/fpicker/inc/bitmaps.hlst new file mode 100644 index 000000000..5771d6134 --- /dev/null +++ b/fpicker/inc/bitmaps.hlst @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +inline constexpr OUStringLiteral BMP_FILEDLG_PLACE_LOCAL = u"fpicker/res/fp015.png"; +inline constexpr OUStringLiteral BMP_FILEDLG_PLACE_REMOTE = u"fpicker/res/fp016.png"; +inline constexpr OUStringLiteral RID_BMP_FOLDER = u"svtools/res/folder.png"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/fpicker/inc/strings.hrc b/fpicker/inc/strings.hrc new file mode 100644 index 000000000..042ea35c4 --- /dev/null +++ b/fpicker/inc/strings.hrc @@ -0,0 +1,36 @@ +/* -*- 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 NC_(Context, String) TranslateId(Context, reinterpret_cast<char const *>(u8##String)) + +#define STR_EXPLORERFILE_OPEN NC_("STR_EXPLORERFILE_OPEN", "Open") +#define STR_EXPLORERFILE_SAVE NC_("STR_EXPLORERFILE_SAVE", "Save as") +#define STR_EXPLORERFILE_BUTTONSAVE NC_("STR_EXPLORERFILE_BUTTONSAVE", "~Save") +#define STR_PATHNAME NC_("STR_PATHNAME", "~Path:") +#define STR_PATHSELECT NC_("STR_PATHSELECT", "Select path") +#define STR_BUTTONSELECT NC_("STR_BUTTONSELECT", "~Select") +#define STR_PREVIEW NC_("STR_PREVIEW", "File Preview") +#define STR_DEFAULT_DIRECTORY NC_("STR_DEFAULT_DIRECTORY", "My Documents") +#define RID_FILEOPEN_NOTEXISTENTFILE NC_("RID_FILEOPEN_NOTEXISTENTFILE", "The file $name$ does not exist.\nMake sure you have entered the correct file name.") +#define STR_SVT_NEW_FOLDER NC_("STR_SVT_NEW_FOLDER", "Folder") +#define STR_SVT_NOREMOVABLEDEVICE NC_("STR_SVT_NOREMOVABLEDEVICE", "No removable storage device detected.\nMake sure it is plugged in properly and try again.") + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/qa/unit/data/fpicker-dialogs-test.txt b/fpicker/qa/unit/data/fpicker-dialogs-test.txt new file mode 100644 index 000000000..563802ee2 --- /dev/null +++ b/fpicker/qa/unit/data/fpicker-dialogs-test.txt @@ -0,0 +1,40 @@ +# -*- 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 contains all dialogs that the unit tests in the module +# will work on if it is in script mode. It will read one-by-one, +# try to open it and create a screenshot that will be saved in +# workdir/screenshots using the pattern of the ui-file name. +# +# Syntax: +# - empty lines are allowed +# - lines starting with '#' are treated as comment +# - all other lines should contain a *.ui filename in the same +# notation as in the dialog constructors (see code) + +# +# The 'known' dialogs which have a hard-coded representation +# in registerKnownDialogsByID/createDialogByID +# + +# No known dialogs in fpicker for now + +# +# Dialogs without a hard-coded representation. These will +# be visualized using a fallback based on weld::Builder +# + +# currently deactivated, leads to problems and the test to not work +# This is typically a hint that these should be hard-coded in the +# test case since they need some document and model data to work + +fps/ui/foldernamedialog.ui +fps/ui/explorerfiledialog.ui +fps/ui/explorerfiledialog.ui +fps/ui/remotefilesdialog.ui diff --git a/fpicker/qa/unit/fpicker-dialogs-test.cxx b/fpicker/qa/unit/fpicker-dialogs-test.cxx new file mode 100644 index 000000000..dfce816df --- /dev/null +++ b/fpicker/qa/unit/fpicker-dialogs-test.cxx @@ -0,0 +1,61 @@ +/* -*- 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/. + */ + +#include <sal/config.h> +#include <test/screenshot_test.hxx> +#include <vcl/abstdlg.hxx> + +using namespace ::com::sun::star; + +/// Test opening a dialog in fpicker +class FpickerDialogsTest : public ScreenshotTest +{ +private: + /// helper method to populate KnownDialogs, called in setUp(). Needs to be + /// written and has to add entries to KnownDialogs + virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override; + + /// dialog creation for known dialogs by ID. Has to be implemented for + /// each registered known dialog + virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override; + +public: + FpickerDialogsTest(); + + // try to open a dialog + void openAnyDialog(); + + CPPUNIT_TEST_SUITE(FpickerDialogsTest); + CPPUNIT_TEST(openAnyDialog); + CPPUNIT_TEST_SUITE_END(); +}; + +FpickerDialogsTest::FpickerDialogsTest() {} + +void FpickerDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/) +{ + // fill map of known dialogs +} + +VclPtr<VclAbstractDialog> FpickerDialogsTest::createDialogByID(sal_uInt32 /*nID*/) +{ + return nullptr; +} + +void FpickerDialogsTest::openAnyDialog() +{ + /// process input file containing the UXMLDescriptions of the dialogs to dump + processDialogBatchFile(u"fpicker/qa/unit/data/fpicker-dialogs-test.txt"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(FpickerDialogsTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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 <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> + +class SalAquaFilePicker; +class FilterHelper; + +@interface AquaFilePickerDelegate : NSObject <NSOpenSavePanelDelegate> +{ + 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 <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/uno/Any.hxx> + +#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<NSPopUpButton*>(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<NSButton*>(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 <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +#include <list> +#include <map> + +#include <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> +#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<NSButton*>(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<NSControl *, NSString *> m_aMapListLabels; + + /** a map to store a popup menu's label text field */ + ::std::map<NSPopUpButton *, NSTextField *> 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<NSControl*> 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 <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ControlActions.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include "resourceprovider.hxx" +#include "NSString_OOoAdditions.hxx" +#include <sal/log.hxx> + +#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<NSPopUpButton*>(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<NSPopUpButton*>(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<NSButton*>(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<NSButton*>(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<NSPopUpButton*>(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<NSButton*>(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<NSPopUpButton*>(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<NSPopUpButton*>(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<NSPopUpButton*>(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 <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <com/sun/star/uno/RuntimeException.hpp> + +#include <list> +#include <string_view> +#include <vector> + +#include <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> + +typedef css::beans::StringPair UnoFilterEntry; +typedef css::uno::Sequence< UnoFilterEntry > UnoFilterList; // can be transported more effectively +typedef ::std::list<NSString *> NSStringList; +typedef ::std::list<OUString> 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 <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <cstddef> +#include <string_view> +#include <o3tl/string_view.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> + +#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<NSString*>(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 <premac.h> +#import <Cocoa/Cocoa.h> +#include <postmac.h> +#include <rtl/ustring.hxx> + +//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<unichar const *>(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<sal_Unicode *>(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 <premac.h> +#include <CoreFoundation/CoreFoundation.h> +#include <postmac.h> +#include <rtl/ustring.hxx> + +@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 <sal/log.hxx> + +@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<CFStringRef>(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<NSString*>(reinterpret_cast<NSString const *>(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 <cppuhelper/compbase.hxx> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/beans/StringPair.hpp> + +#include "SalAquaPicker.hxx" + +#include <rtl/ustring.hxx> +#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 <config_features.h> + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ControlActions.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> + +#include "resourceprovider.hxx" + +#include <osl/file.hxx> +#include "NSString_OOoAdditions.hxx" +#include "NSURL_OOoAdditions.hxx" + +#include <iostream> + +#include "SalAquaFilePicker.hxx" + +#include <objc/objc-runtime.h> + +#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<OUString> 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<XFilePickerListener>& xListener ) +{ + SolarMutexGuard aGuard; + m_xListener = xListener; +} + +void SAL_CALL SalAquaFilePicker::removeFilePickerListener( const uno::Reference<XFilePickerListener>& ) +{ + 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<XFilePicker*>( static_cast<XFilePicker3*>( this ) )); + break; + } + + return retVal; +} + + +#pragma mark XFilePicker + +void SAL_CALL SalAquaFilePicker::setMultiSelectionMode( sal_Bool /* bMode */ ) +{ + SolarMutexGuard aGuard; + + if (m_nDialogType == NAVIGATIONSERVICES_OPEN) { + [static_cast<NSOpenPanel*>(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<OUString> 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<OUString> 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<NSOpenPanel*>(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<beans::StringPair>& 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<uno::Any>& aArguments ) +{ + SolarMutexGuard aGuard; + + // parameter checking + uno::Any aAny; + if( 0 == aArguments.getLength() ) + throw lang::IllegalArgumentException("no arguments", + static_cast<XFilePicker*>( static_cast<XFilePicker3*>(this) ), 1 ); + + aAny = aArguments[0]; + + if( ( aAny.getValueType() != ::cppu::UnoType<sal_Int16>::get() ) && + (aAny.getValueType() != ::cppu::UnoType<sal_Int8>::get() ) ) + throw lang::IllegalArgumentException("invalid argument type", + static_cast<XFilePicker*>( static_cast<XFilePicker3*>(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<XFilePicker*>( static_cast<XFilePicker3*>(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> 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<OUString> 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<css::uno::Any> 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 <cppuhelper/implbase.hxx> +#include <com/sun/star/util/XCancellable.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> + +#include "SalAquaPicker.hxx" + +#include <rtl/ustring.hxx> + + + + +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 <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include "SalAquaFolderPicker.hxx" + +#include <iostream> + +#include "resourceprovider.hxx" + +#include <osl/file.hxx> +#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<NSOpenPanel*>(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<OUString> 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<css::uno::Any> 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 <osl/mutex.hxx> + +#include <rtl/ustring.hxx> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include "ControlHelper.hxx" + +#include <premac.h> +#import <Cocoa/Cocoa.h> +#include <postmac.h> + +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 <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <cppuhelper/interfacecontainer.h> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include "SalAquaPicker.hxx" +#include <osl/file.hxx> +#include "NSString_OOoAdditions.hxx" + +#include "NSURL_OOoAdditions.hxx" + +#include "SalAquaFilePicker.hxx" + +#include <stdio.h> + +#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<NSOpenPanel*>(m_pDialog) setCanChooseDirectories:NO]; + [static_cast<NSOpenPanel*>(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<NSOpenPanel*>(m_pDialog) setCanChooseDirectories:YES]; + [static_cast<NSOpenPanel*>(m_pDialog) setCanChooseFiles:NO]; + break; + + default: + break; + } + + if (m_pDialog != nil) { + [static_cast<NSOpenPanel*>(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<NSOpenPanel*>(m_pDialog) runModal]; + break; + case NAVIGATIONSERVICES_SAVE: + [m_pDialog setDirectoryURL:startDirectory]; + [m_pDialog setNameFieldStringValue:[NSString stringWithOUString:static_cast<SalAquaFilePicker*>(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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.ui.dialogs.SalAquaFilePicker" + constructor="fpicker_SalAquaFilePicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.AquaFilePicker"/> + </implementation> + <implementation name="com.sun.star.ui.dialogs.SalAquaFolderPicker" + constructor="fpicker_SalAquaFolderPicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.AquaFolderPicker"/> + </implementation> +</component> 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 <sal/config.h> + +#include <memory> + +#include <sal/types.h> + +#include <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> + +#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 <sal/config.h> + +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <osl/mutex.hxx> +#include <fpicker/strings.hrc> +#include <vcl/svapp.hxx> +#include <unotools/resmgr.hxx> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> + +#include "NSString_OOoAdditions.hxx" +#include <fpicker/fpsofficeResMgr.hxx> +#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: */ diff --git a/fpicker/source/office/OfficeControlAccess.cxx b/fpicker/source/office/OfficeControlAccess.cxx new file mode 100644 index 000000000..3d06627da --- /dev/null +++ b/fpicker/source/office/OfficeControlAccess.cxx @@ -0,0 +1,770 @@ +/* -*- 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 <sal/macros.h> +#include "OfficeControlAccess.hxx" +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ControlActions.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> + +#include <algorithm> +#include <utility> + + +namespace svt +{ + + + // helper ------------------------------------------------------------- + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::ui::dialogs; + + using namespace ExtendedFilePickerElementIds; + using namespace CommonFilePickerElementIds; + using namespace InternalFilePickerElementIds; + + + namespace + { + + struct ControlDescription + { + const char* pControlName; + sal_Int16 nControlId; + PropFlags nPropertyFlags; + }; + + + typedef const ControlDescription* ControlDescIterator; + + + #define PROPERTY_FLAGS_COMMON ( PropFlags::Enabled | PropFlags::Visible | PropFlags::HelpUrl ) + #define PROPERTY_FLAGS_LISTBOX ( PropFlags::ListItems | PropFlags::SelectedItem | PropFlags::SelectedItemIndex ) + #define PROPERTY_FLAGS_CHECKBOX ( PropFlags::Checked | PropFlags::Text ) + + // Note: this array MUST be sorted by name! + const ControlDescription aDescriptions[] = { + { "AutoExtensionBox", CHECKBOX_AUTOEXTENSION, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "CancelButton", PUSHBUTTON_CANCEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "CurrentFolderText", FIXEDTEXT_CURRENTFOLDER, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "FileURLEdit", EDIT_FILEURL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "FileURLEditLabel", EDIT_FILEURL_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "FileView", CONTROL_FILEVIEW, PROPERTY_FLAGS_COMMON }, + { "FilterList", LISTBOX_FILTER, PROPERTY_FLAGS_COMMON }, + { "FilterListLabel", LISTBOX_FILTER_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "FilterOptionsBox", CHECKBOX_FILTEROPTIONS, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "GpgPassword", CHECKBOX_GPGENCRYPTION, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "HelpButton", PUSHBUTTON_HELP, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "ImageAnchorList", LISTBOX_IMAGE_ANCHOR, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_LISTBOX }, + { "ImageAnchorListLabel", LISTBOX_IMAGE_ANCHOR_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "ImageTemplateList", LISTBOX_IMAGE_TEMPLATE, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_LISTBOX }, + { "ImageTemplateListLabel", LISTBOX_IMAGE_TEMPLATE_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "LevelUpButton", TOOLBOXBUTTON_LEVEL_UP, PROPERTY_FLAGS_COMMON }, + { "LinkBox", CHECKBOX_LINK, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "NewFolderButton", TOOLBOXBUTTON_NEW_FOLDER, PROPERTY_FLAGS_COMMON }, + { "OkButton", PUSHBUTTON_OK , PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "PasswordBox", CHECKBOX_PASSWORD, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "PlayButton", PUSHBUTTON_PLAY, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "PreviewBox", CHECKBOX_PREVIEW, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "ReadOnlyBox", CHECKBOX_READONLY, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "SelectionBox", CHECKBOX_SELECTION, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_CHECKBOX }, + { "TemplateList", LISTBOX_TEMPLATE, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_LISTBOX }, + { "TemplateListLabel", LISTBOX_TEMPLATE_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text }, + { "VersionList", LISTBOX_VERSION, PROPERTY_FLAGS_COMMON | PROPERTY_FLAGS_LISTBOX }, + { "VersionListLabel", LISTBOX_VERSION_LABEL, PROPERTY_FLAGS_COMMON | PropFlags::Text } + }; + + const sal_Int32 s_nControlCount = SAL_N_ELEMENTS( aDescriptions ); + + ControlDescIterator s_pControls = aDescriptions; + ControlDescIterator s_pControlsEnd = aDescriptions + s_nControlCount; + + struct ControlDescriptionLookup + { + bool operator()( const ControlDescription& rDesc1, const ControlDescription& rDesc2 ) + { + return strcmp(rDesc1.pControlName, rDesc2.pControlName) < 0; + } + }; + + struct ControlProperty + { + const char* pPropertyName; + PropFlags nPropertyId; + }; + + typedef const ControlProperty* ControlPropertyIterator; + + const ControlProperty aProperties[] = { + { "Text", PropFlags::Text }, + { "Enabled", PropFlags::Enabled }, + { "Visible", PropFlags::Visible }, + { "HelpURL", PropFlags::HelpUrl }, + { "ListItems", PropFlags::ListItems }, + { "SelectedItem", PropFlags::SelectedItem }, + { "SelectedItemIndex", PropFlags::SelectedItemIndex }, + { "Checked", PropFlags::Checked } + }; + + const int s_nPropertyCount = SAL_N_ELEMENTS( aProperties ); + + ControlPropertyIterator s_pProperties = aProperties; + ControlPropertyIterator s_pPropertiesEnd = aProperties + s_nPropertyCount; + + + struct ControlPropertyLookup + { + OUString m_sLookup; + explicit ControlPropertyLookup(OUString aLookup) + : m_sLookup(std::move(aLookup)) + { + } + + bool operator()(const ControlProperty& rProp) + { + return m_sLookup.equalsAscii(rProp.pPropertyName); + } + }; + + + void lcl_throwIllegalArgumentException( ) + { + throw IllegalArgumentException(); + // TODO: error message in the exception + } + } + + OControlAccess::OControlAccess(IFilePickerController* pController, SvtFileView* pFileView) + : m_pFilePickerController(pController) + , m_pFileView(pFileView) + { + DBG_ASSERT( m_pFilePickerController, "OControlAccess::OControlAccess: invalid control locator!" ); + } + + bool OControlAccess::IsFileViewWidget(weld::Widget const * pControl) const + { + if (!pControl) + return false; + if (!m_pFileView) + return false; + return pControl == m_pFileView->identifier(); + } + + void OControlAccess::setHelpURL(weld::Widget* pControl, const OUString& sHelpURL) + { + OUString sHelpID( sHelpURL ); + INetURLObject aHID( sHelpURL ); + if (aHID.GetProtocol() == INetProtocol::Hid) + sHelpID = aHID.GetURLPath(); + + // URLs should always be UTF8 encoded and escaped + OString sID( OUStringToOString( sHelpID, RTL_TEXTENCODING_UTF8 ) ); + if (IsFileViewWidget(pControl)) + { + // the file view "overrides" the SetHelpId + m_pFileView->set_help_id(sID); + } + else + pControl->set_help_id(sID); + } + + OUString OControlAccess::getHelpURL(weld::Widget const * pControl) const + { + OString aHelpId = pControl->get_help_id(); + if (IsFileViewWidget(pControl)) + { + // the file view "overrides" the SetHelpId + aHelpId = m_pFileView->get_help_id(); + } + + OUString sHelpURL; + OUString aTmp( OStringToOUString( aHelpId, RTL_TEXTENCODING_UTF8 ) ); + INetURLObject aHID( aTmp ); + if ( aHID.GetProtocol() == INetProtocol::NotValid ) + sHelpURL = INET_HID_SCHEME; + sHelpURL += aTmp; + return sHelpURL; + } + + Any OControlAccess::getControlProperty( std::u16string_view rControlName, const OUString& rControlProperty ) + { + // look up the control + sal_Int16 nControlId = -1; + PropFlags nPropertyMask = PropFlags::NONE; + weld::Widget* pControl = implGetControl( rControlName, &nControlId, &nPropertyMask ); + // will throw an IllegalArgumentException if the name is not valid + + // look up the property + ControlPropertyIterator aPropDesc = ::std::find_if( s_pProperties, s_pPropertiesEnd, ControlPropertyLookup( rControlProperty ) ); + if ( aPropDesc == s_pPropertiesEnd ) + // it's a completely unknown property + lcl_throwIllegalArgumentException(); + + if ( !( nPropertyMask & aPropDesc->nPropertyId ) ) + // it's a property which is known, but not allowed for this control + lcl_throwIllegalArgumentException(); + + return implGetControlProperty( pControl, aPropDesc->nPropertyId ); + } + + weld::Widget* OControlAccess::implGetControl( std::u16string_view rControlName, sal_Int16* _pId, PropFlags* _pPropertyMask ) const + { + weld::Widget* pControl = nullptr; + ControlDescription tmpDesc; + OString aControlName = OUStringToOString( rControlName, RTL_TEXTENCODING_UTF8 ); + tmpDesc.pControlName = aControlName.getStr(); + + // translate the name into an id + auto aFoundRange = ::std::equal_range( s_pControls, s_pControlsEnd, tmpDesc, ControlDescriptionLookup() ); + if ( aFoundRange.first != aFoundRange.second ) + { + // get the VCL control determined by this id + pControl = m_pFilePickerController->getControl( aFoundRange.first->nControlId ); + } + + // if not found 'til here, the name is invalid, or we do not have the control in the current mode + if ( !pControl ) + lcl_throwIllegalArgumentException(); + + // out parameters and outta here + if ( _pId ) + *_pId = aFoundRange.first->nControlId; + if ( _pPropertyMask ) + *_pPropertyMask = aFoundRange.first->nPropertyFlags; + + return pControl; + } + + void OControlAccess::setControlProperty( std::u16string_view rControlName, const OUString& rControlProperty, const css::uno::Any& rValue ) + { + // look up the control + sal_Int16 nControlId = -1; + weld::Widget* pControl = implGetControl( rControlName, &nControlId ); + // will throw an IllegalArgumentException if the name is not valid + + // look up the property + ControlPropertyIterator aPropDesc = ::std::find_if( s_pProperties, s_pPropertiesEnd, ControlPropertyLookup( rControlProperty ) ); + if ( aPropDesc == s_pPropertiesEnd ) + lcl_throwIllegalArgumentException(); + + // set the property + implSetControlProperty( nControlId, pControl, aPropDesc->nPropertyId, rValue, false ); + } + + Sequence< OUString > OControlAccess::getSupportedControls( ) const + { + Sequence< OUString > aControls( s_nControlCount ); + OUString* pControls = aControls.getArray(); + + // collect the names of all _actually_existent_ controls + for ( ControlDescIterator aControl = s_pControls; aControl != s_pControlsEnd; ++aControl ) + { + if ( m_pFilePickerController->getControl( aControl->nControlId ) ) + *pControls++ = OUString::createFromAscii( aControl->pControlName ); + } + + aControls.realloc( pControls - aControls.getArray() ); + return aControls; + } + + Sequence< OUString > OControlAccess::getSupportedControlProperties( std::u16string_view rControlName ) + { + sal_Int16 nControlId = -1; + PropFlags nPropertyMask = PropFlags::NONE; + implGetControl( rControlName, &nControlId, &nPropertyMask ); + // will throw an IllegalArgumentException if the name is not valid + + // fill in the property names + Sequence< OUString > aProps( s_nPropertyCount ); + OUString* pProperty = aProps.getArray(); + + for ( ControlPropertyIterator aProp = s_pProperties; aProp != s_pPropertiesEnd; ++aProp ) + if ( nPropertyMask & aProp->nPropertyId ) + *pProperty++ = OUString::createFromAscii( aProp->pPropertyName ); + + aProps.realloc( pProperty - aProps.getArray() ); + return aProps; + } + + bool OControlAccess::isControlSupported( std::u16string_view rControlName ) + { + ControlDescription tmpDesc; + OString aControlName = OUStringToOString(rControlName, RTL_TEXTENCODING_UTF8); + tmpDesc.pControlName = aControlName.getStr(); + return ::std::binary_search( s_pControls, s_pControlsEnd, tmpDesc, ControlDescriptionLookup() ); + } + + bool OControlAccess::isControlPropertySupported( std::u16string_view rControlName, const OUString& rControlProperty ) + { + // look up the control + sal_Int16 nControlId = -1; + PropFlags nPropertyMask = PropFlags::NONE; + implGetControl( rControlName, &nControlId, &nPropertyMask ); + // will throw an IllegalArgumentException if the name is not valid + + // look up the property + ControlPropertyIterator aPropDesc = ::std::find_if( s_pProperties, s_pPropertiesEnd, ControlPropertyLookup( rControlProperty ) ); + if ( aPropDesc == s_pPropertiesEnd ) + // it's a property which is completely unknown + return false; + + return bool( aPropDesc->nPropertyId & nPropertyMask ); + } + + void OControlAccess::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const Any& rValue ) + { + weld::Widget* pControl = m_pFilePickerController->getControl( nControlId ); + DBG_ASSERT( pControl, "OControlAccess::SetValue: don't have this control in the current mode!" ); + if ( !pControl ) + return; + + PropFlags nPropertyId = PropFlags::Unknown; + if ( ControlActions::SET_HELP_URL == nControlAction ) + { + nPropertyId = PropFlags::HelpUrl; + } + else + { + switch ( nControlId ) + { + case CHECKBOX_AUTOEXTENSION: + case CHECKBOX_PASSWORD: + case CHECKBOX_FILTEROPTIONS: + case CHECKBOX_READONLY: + case CHECKBOX_LINK: + case CHECKBOX_PREVIEW: + case CHECKBOX_SELECTION: + nPropertyId = PropFlags::Checked; + break; + + case LISTBOX_FILTER: + SAL_WARN( "fpicker.office", "Use the XFilterManager to access the filter listbox" ); + break; + + case LISTBOX_VERSION: + case LISTBOX_TEMPLATE: + case LISTBOX_IMAGE_TEMPLATE: + case LISTBOX_IMAGE_ANCHOR: + if ( ControlActions::SET_SELECT_ITEM == nControlAction ) + { + nPropertyId = PropFlags::SelectedItemIndex; + } + else + { + weld::ComboBox* pComboBox = dynamic_cast<weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::SetValue: implGetControl returned nonsense!"); + implDoListboxAction(pComboBox, nControlAction, rValue); + } + break; + } + } + + if ( PropFlags::Unknown != nPropertyId ) + implSetControlProperty( nControlId, pControl, nPropertyId, rValue ); + } + + Any OControlAccess::getValue( sal_Int16 nControlId, sal_Int16 nControlAction ) const + { + Any aRet; + + weld::Widget* pControl = m_pFilePickerController->getControl( nControlId ); + DBG_ASSERT( pControl, "OControlAccess::GetValue: don't have this control in the current mode!" ); + if ( pControl ) + { + PropFlags nPropertyId = PropFlags::Unknown; + if ( ControlActions::SET_HELP_URL == nControlAction ) + { + nPropertyId = PropFlags::HelpUrl; + } + else + { + switch ( nControlId ) + { + case CHECKBOX_AUTOEXTENSION: + case CHECKBOX_PASSWORD: + case CHECKBOX_GPGENCRYPTION: + case CHECKBOX_FILTEROPTIONS: + case CHECKBOX_READONLY: + case CHECKBOX_LINK: + case CHECKBOX_PREVIEW: + case CHECKBOX_SELECTION: + nPropertyId = PropFlags::Checked; + break; + + case LISTBOX_FILTER: + if ( ControlActions::GET_SELECTED_ITEM == nControlAction ) + { + aRet <<= m_pFilePickerController->getCurFilter(); + } + else + { + SAL_WARN( "fpicker.office", "Use the XFilterManager to access the filter listbox" ); + } + break; + + case LISTBOX_VERSION: + case LISTBOX_TEMPLATE: + case LISTBOX_IMAGE_TEMPLATE: + case LISTBOX_IMAGE_ANCHOR: + switch ( nControlAction ) + { + case ControlActions::GET_SELECTED_ITEM: + nPropertyId = PropFlags::SelectedItem; + break; + case ControlActions::GET_SELECTED_ITEM_INDEX: + nPropertyId = PropFlags::SelectedItemIndex; + break; + case ControlActions::GET_ITEMS: + nPropertyId = PropFlags::ListItems; + break; + default: + SAL_WARN( "fpicker.office", "OControlAccess::GetValue: invalid control action for the listbox!" ); + break; + } + break; + } + } + + if ( PropFlags::Unknown != nPropertyId ) + aRet = implGetControlProperty( pControl, nPropertyId ); + } + + return aRet; + } + + void OControlAccess::setLabel( sal_Int16 nId, const OUString &rLabel ) + { + weld::Widget* pControl = m_pFilePickerController->getControl(nId, true); + if (weld::Label* pLabel = dynamic_cast<weld::Label*>(pControl)) + { + pLabel->set_label(rLabel); + return; + } + if (weld::Button* pButton = dynamic_cast<weld::Button*>(pControl)) + { + pButton->set_label(rLabel); + return; + } + assert(false && "OControlAccess::GetValue: don't have this control in the current mode!"); + } + + OUString OControlAccess::getLabel( sal_Int16 nId ) const + { + weld::Widget* pControl = m_pFilePickerController->getControl(nId, true); + if (weld::Label* pLabel = dynamic_cast<weld::Label*>(pControl)) + return pLabel->get_label(); + if (weld::Button* pButton = dynamic_cast<weld::Button*>(pControl)) + return pButton->get_label(); + assert(false && "OControlAccess::GetValue: don't have this control in the current mode!"); + return OUString(); + } + + void OControlAccess::enableControl(sal_Int16 nId, bool bEnable) + { + m_pFilePickerController->enableControl(nId, bEnable); + } + + void OControlAccess::implDoListboxAction(weld::ComboBox* pListbox, sal_Int16 nControlAction, const Any& rValue) + { + switch ( nControlAction ) + { + case ControlActions::ADD_ITEM: + { + OUString aEntry; + rValue >>= aEntry; + if ( !aEntry.isEmpty() ) + pListbox->append_text( aEntry ); + } + break; + + case ControlActions::ADD_ITEMS: + { + Sequence < OUString > aTemplateList; + rValue >>= aTemplateList; + + if ( aTemplateList.hasElements() ) + { + for ( const OUString& s : std::as_const(aTemplateList) ) + pListbox->append_text( s ); + } + } + break; + + case ControlActions::DELETE_ITEM: + { + sal_Int32 nPos = 0; + if ( rValue >>= nPos ) + pListbox->remove( nPos ); + } + break; + + case ControlActions::DELETE_ITEMS: + pListbox->clear(); + break; + + default: + SAL_WARN( "fpicker.office", "Wrong ControlAction for implDoListboxAction()" ); + } + } + + void OControlAccess::implSetControlProperty( sal_Int16 nControlId, weld::Widget* pControl, PropFlags _nProperty, const Any& rValue, bool _bIgnoreIllegalArgument ) + { + if ( !pControl ) + pControl = m_pFilePickerController->getControl( nControlId ); + DBG_ASSERT( pControl, "OControlAccess::implSetControlProperty: invalid argument, this will crash!" ); + if ( !pControl ) + return; + + DBG_ASSERT( pControl == m_pFilePickerController->getControl( nControlId ), + "OControlAccess::implSetControlProperty: inconsistent parameters!" ); + + switch ( _nProperty ) + { + case PropFlags::Text: + { + OUString sText; + if (rValue >>= sText) + { + weld::Label* pLabel = dynamic_cast<weld::Label*>(pControl); + assert(pLabel); + pLabel->set_label(sText); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::Enabled: + { + bool bEnabled = false; + if ( rValue >>= bEnabled ) + { + m_pFilePickerController->enableControl( nControlId, bEnabled ); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::Visible: + { + bool bVisible = false; + if ( rValue >>= bVisible ) + { + pControl->set_visible( bVisible ); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::HelpUrl: + { + OUString sHelpURL; + if ( rValue >>= sHelpURL ) + { + setHelpURL(pControl, sHelpURL); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::ListItems: + { + weld::ComboBox* pComboBox = dynamic_cast<weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implSetControlProperty: invalid control/property combination!"); + + Sequence< OUString > aItems; + if ( rValue >>= aItems ) + { + // remove all previous items + pComboBox->clear(); + + // add the new ones + for (auto const & item : std::as_const(aItems)) + { + pComboBox->append_text(item); + } + + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::SelectedItem: + { + weld::ComboBox* pComboBox = dynamic_cast<weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implSetControlProperty: invalid control/property combination!"); + + OUString sSelected; + if ( rValue >>= sSelected ) + { + pComboBox->set_active_text(sSelected); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::SelectedItemIndex: + { + weld::ComboBox* pComboBox = dynamic_cast<weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implSetControlProperty: invalid control/property combination!"); + + sal_Int32 nPos = 0; + if ( rValue >>= nPos ) + { + pComboBox->set_active(nPos); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + case PropFlags::Checked: + { + weld::Toggleable* pToggleButton = dynamic_cast<weld::Toggleable*>(pControl); + assert(pToggleButton && "OControlAccess::implSetControlProperty: invalid control/property combination!"); + + bool bChecked = false; + if ( rValue >>= bChecked ) + { + pToggleButton->set_active(bChecked); + } + else if ( !_bIgnoreIllegalArgument ) + { + lcl_throwIllegalArgumentException(); + } + } + break; + + default: + OSL_FAIL( "OControlAccess::implSetControlProperty: invalid property id!" ); + } + } + + Any OControlAccess::implGetControlProperty( weld::Widget const * pControl, PropFlags _nProperty ) const + { + assert(pControl && "OControlAccess::implGetControlProperty: invalid argument, this will crash!"); + + Any aReturn; + switch ( _nProperty ) + { + case PropFlags::Text: + { + const weld::Label* pLabel = dynamic_cast<const weld::Label*>(pControl); + assert(pLabel); + aReturn <<= pLabel->get_label(); + break; + } + case PropFlags::Enabled: + aReturn <<= pControl->get_sensitive(); + break; + + case PropFlags::Visible: + aReturn <<= pControl->get_visible(); + break; + + case PropFlags::HelpUrl: + aReturn <<= getHelpURL(pControl); + break; + + case PropFlags::ListItems: + { + const weld::ComboBox* pComboBox = dynamic_cast<const weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implGetControlProperty: invalid control/property combination!"); + + Sequence< OUString > aItems(pComboBox->get_count()); + OUString* pItems = aItems.getArray(); + for (sal_Int32 i = 0; i < pComboBox->get_count(); ++i) + *pItems++ = pComboBox->get_text(i); + + aReturn <<= aItems; + break; + } + + case PropFlags::SelectedItem: + { + const weld::ComboBox* pComboBox = dynamic_cast<const weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implGetControlProperty: invalid control/property combination!"); + + sal_Int32 nSelected = pComboBox->get_active(); + OUString sSelected; + if (nSelected != -1) + sSelected = pComboBox->get_active_text(); + aReturn <<= sSelected; + break; + } + + case PropFlags::SelectedItemIndex: + { + const weld::ComboBox* pComboBox = dynamic_cast<const weld::ComboBox*>(pControl); + assert(pComboBox && "OControlAccess::implGetControlProperty: invalid control/property combination!"); + + sal_Int32 nSelected = pComboBox->get_active(); + if (nSelected != -1) + aReturn <<= nSelected; + else + aReturn <<= sal_Int32(-1); + break; + } + + case PropFlags::Checked: + { + const weld::Toggleable* pToggleButton = dynamic_cast<const weld::Toggleable*>(pControl); + assert(pToggleButton && "OControlAccess::implGetControlProperty: invalid control/property combination!"); + + aReturn <<= pToggleButton->get_active(); + break; + } + + default: + OSL_FAIL( "OControlAccess::implGetControlProperty: invalid property id!" ); + } + return aReturn; + } + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/OfficeControlAccess.hxx b/fpicker/source/office/OfficeControlAccess.hxx new file mode 100644 index 000000000..859ff15f2 --- /dev/null +++ b/fpicker/source/office/OfficeControlAccess.hxx @@ -0,0 +1,134 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include "fileview.hxx" +#include "pickercallbacks.hxx" +#include <o3tl/typed_flags_set.hxx> + +enum class PropFlags { + Unknown = -1, // used as an error sentinel + NONE = 0x0000, + Text = 0x0001, + Enabled = 0x0002, + Visible = 0x0004, + HelpUrl = 0x0008, + ListItems = 0x0010, + SelectedItem = 0x0020, + SelectedItemIndex = 0x0040, + Checked = 0x0080, +}; +namespace o3tl { + template<> struct typed_flags<PropFlags> : is_typed_flags<PropFlags, 0x00ff> {}; +} + + +namespace svt +{ + + + namespace InternalFilePickerElementIds + { + const sal_Int16 PUSHBUTTON_HELP = sal_Int16(0x1000); + const sal_Int16 TOOLBOXBUTTON_LEVEL_UP = sal_Int16(0x1002); + const sal_Int16 TOOLBOXBUTTON_NEW_FOLDER = sal_Int16(0x1003); + const sal_Int16 FIXEDTEXT_CURRENTFOLDER = sal_Int16(0x1004); + } + + + /** implements the XControlAccess, XControlInformation and XFilePickerControlAccess for the file picker + */ + class OControlAccess + { + IFilePickerController* m_pFilePickerController; + SvtFileView* m_pFileView; + + public: + OControlAccess( IFilePickerController* pController, SvtFileView* pFileView ); + + // XControlAccess implementation + void setControlProperty( std::u16string_view rControlName, const OUString& rControlProperty, const css::uno::Any& rValue ); + css::uno::Any getControlProperty( std::u16string_view rControlName, const OUString& rControlProperty ); + + // XControlInformation implementation + css::uno::Sequence< OUString > getSupportedControls( ) const; + css::uno::Sequence< OUString > getSupportedControlProperties( std::u16string_view rControlName ); + static bool isControlSupported( std::u16string_view rControlName ); + bool isControlPropertySupported( std::u16string_view rControlName, const OUString& rControlProperty ); + + // XFilePickerControlAccess + void setValue( sal_Int16 nId, sal_Int16 nCtrlAction, const css::uno::Any& rValue ); + css::uno::Any getValue( sal_Int16 nId, sal_Int16 nCtrlAction ) const; + void setLabel( sal_Int16 nId, const OUString& rValue ); + OUString getLabel( sal_Int16 nId ) const; + void enableControl( sal_Int16 nId, bool bEnable ); + + void setHelpURL(weld::Widget* pControl, const OUString& rURL); + OUString getHelpURL(weld::Widget const* pControl) const; + + private: + /** implements the various methods for setting properties on controls + + @param nControlId + the id of the control + @param pControl + the affected control. Must be the same as referred by <arg>nControlId</arg>, or NULL. + @param nProperty + the property to set + See PropFlags::* + @param rValue + the value to set + @param bIgnoreIllegalArgument + if <FALSE/>, an exception will be thrown if the given value is of improper type + */ + void implSetControlProperty( + sal_Int16 nControlId, + weld::Widget* pControl, PropFlags nProperty, const css::uno::Any& rValue, + bool bIgnoreIllegalArgument = true ); + + weld::Widget* implGetControl( std::u16string_view rControlName, sal_Int16* pId, PropFlags* pPropertyMask = nullptr ) const; + + /** implements the various methods for retrieving properties from controls + + @param pControl + the affected control + @PRECOND not <NULL/> + @param nProperty + the property to retrieve + See PropFlags::* + @return + */ + css::uno::Any implGetControlProperty( weld::Widget const * pControl, PropFlags nProperty ) const; + + bool IsFileViewWidget(weld::Widget const * pControl) const; + + static void implDoListboxAction(weld::ComboBox* pListbox, sal_Int16 nCtrlAction, const css::uno::Any& rValue); + + }; + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/OfficeFilePicker.cxx b/fpicker/source/office/OfficeFilePicker.cxx new file mode 100644 index 000000000..7410e0c96 --- /dev/null +++ b/fpicker/source/office/OfficeFilePicker.cxx @@ -0,0 +1,1108 @@ +/* -*- 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 "OfficeControlAccess.hxx" +#include "OfficeFilePicker.hxx" +#include "iodlg.hxx" +#include "RemoteFilesDialog.hxx" + +#include <utility> +#include <vector> +#include <algorithm> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/ui/dialogs/FilePickerEvent.hpp> +#include <com/sun/star/ui/dialogs/FilePreviewImageFormats.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <unotools/pathoptions.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/make_shared.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::awt; +using namespace ::utl; + + +struct FilterEntry +{ +protected: + OUString m_sTitle; + OUString m_sFilter; + + UnoFilterList m_aSubFilters; + +public: + FilterEntry( OUString _aTitle, OUString _aFilter ) + :m_sTitle(std::move( _aTitle )) + ,m_sFilter(std::move( _aFilter )) + { + } + + FilterEntry( OUString _aTitle, const UnoFilterList& _rSubFilters ); + + const OUString& getTitle() const { return m_sTitle; } + const OUString& getFilter() const { return m_sFilter; } + + /// 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 + */ + void 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(); } +}; + + +FilterEntry::FilterEntry( OUString _aTitle, const UnoFilterList& _rSubFilters ) + :m_sTitle(std::move( _aTitle )) + ,m_aSubFilters( _rSubFilters ) +{ +} + + +bool FilterEntry::hasSubFilters( ) const +{ + return m_aSubFilters.hasElements(); +} + + +void FilterEntry::getSubFilters( UnoFilterList& _rSubFilterList ) +{ + _rSubFilterList = m_aSubFilters; +} + +struct ElementEntry_Impl +{ + sal_Int16 m_nElementID; + sal_Int16 m_nControlAction; + Any m_aValue; + OUString m_aLabel; + bool m_bEnabled : 1; + + bool m_bHasValue : 1; + bool m_bHasLabel : 1; + bool m_bHasEnabled : 1; + + explicit ElementEntry_Impl( sal_Int16 nId ); + + void setValue( const Any& rVal ) { m_aValue = rVal; m_bHasValue = true; } + void setAction( sal_Int16 nAction ) { m_nControlAction = nAction; } + void setLabel( const OUString& rVal ) { m_aLabel = rVal; m_bHasLabel = true; } + void setEnabled( bool bEnabled ) { m_bEnabled = bEnabled; m_bHasEnabled = true; } +}; + +ElementEntry_Impl::ElementEntry_Impl( sal_Int16 nId ) + : m_nElementID( nId ) + , m_nControlAction( 0 ) + , m_bEnabled( false ) + , m_bHasValue( false ) + , m_bHasLabel( false ) + , m_bHasEnabled( false ) +{} + + +void SvtFilePicker::prepareExecute() +{ + // set the default directory + // --**-- doesn't match the spec yet + if ( !m_aDisplayDirectory.isEmpty() || !m_aDefaultName.isEmpty() ) + { + bool isFileSet = false; + if ( !m_aDisplayDirectory.isEmpty() ) + { + + INetURLObject aPath; + INetURLObject givenPath( m_aDisplayDirectory ); + if (!givenPath.HasError()) + aPath = givenPath; + else + { + aPath = INetURLObject( SvtPathOptions().GetWorkPath() ); + } + if ( !m_aDefaultName.isEmpty() ) + { + aPath.insertName( m_aDefaultName ); + m_xDlg->SetHasFilename( true ); + } + m_xDlg->SetPath( aPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + isFileSet = true; + } + if ( !isFileSet && !m_aDefaultName.isEmpty() ) + { + m_xDlg->SetPath( m_aDefaultName ); + m_xDlg->SetHasFilename( true ); + } + } + else + { + // set the default standard dir + INetURLObject aStdDirObj( SvtPathOptions().GetWorkPath() ); + m_xDlg->SetPath( aStdDirObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + + // set the control values and set the control labels, too + if ( m_pElemList && !m_pElemList->empty() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + + for (auto const& elem : *m_pElemList) + { + if ( elem.m_bHasValue ) + aAccess.setValue( elem.m_nElementID, elem.m_nControlAction, elem.m_aValue ); + if ( elem.m_bHasLabel ) + aAccess.setLabel( elem.m_nElementID, elem.m_aLabel ); + if ( elem.m_bHasEnabled ) + aAccess.enableControl( elem.m_nElementID, elem.m_bEnabled ); + } + + } + + if ( m_pFilterList ) + { + for (auto & elem : *m_pFilterList) + { + if ( elem.hasSubFilters() ) + { // it's a filter group + UnoFilterList aSubFilters; + elem.getSubFilters( aSubFilters ); + + m_xDlg->AddFilterGroup( elem.getTitle(), aSubFilters ); + } + else + { + // it's a single filter + m_xDlg->AddFilter( elem.getTitle(), elem.getFilter() ); + } + } + } + + // set the default filter + if ( !m_aCurrentFilter.isEmpty() ) + m_xDlg->SetCurFilter( m_aCurrentFilter ); + +} + +void SvtFilePicker::DialogClosedHdl(sal_Int32 nResult) +{ + if ( m_xDlgClosedListener.is() ) + { + sal_Int16 nRet = static_cast< sal_Int16 >(nResult); + css::ui::dialogs::DialogClosedEvent aEvent( *this, nRet ); + m_xDlgClosedListener->dialogClosed( aEvent ); + m_xDlgClosedListener.clear(); + } +} + +// SvtFilePicker +PickerFlags SvtFilePicker::getPickerFlags() const +{ + // set the winbits for creating the filedialog + PickerFlags nBits = PickerFlags::NONE; + + // set the standard bits according to the service name + if ( m_nServiceType == TemplateDescription::FILEOPEN_SIMPLE ) + { + nBits = PickerFlags::Open; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_SIMPLE ) + { + nBits = PickerFlags::SaveAs; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_AUTOEXTENSION ) + { + nBits = PickerFlags::SaveAs | PickerFlags::AutoExtension; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD ) + { + nBits = PickerFlags::SaveAs | PickerFlags::Password | PickerFlags::AutoExtension; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS ) + { + nBits = PickerFlags::SaveAs | PickerFlags::Password | PickerFlags::AutoExtension | PickerFlags::FilterOptions; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_AUTOEXTENSION_TEMPLATE ) + { + nBits = PickerFlags::SaveAs | PickerFlags::AutoExtension | PickerFlags::Templates; + } + else if ( m_nServiceType == TemplateDescription::FILESAVE_AUTOEXTENSION_SELECTION ) + { + nBits = PickerFlags::SaveAs | PickerFlags::AutoExtension | PickerFlags::Selection; + } + + else if ( m_nServiceType == TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE ) + { + nBits = PickerFlags::Open | PickerFlags::InsertAsLink | PickerFlags::ShowPreview | PickerFlags::ImageTemplate; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_PLAY ) + { + nBits = PickerFlags::Open | PickerFlags::PlayButton; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_LINK_PLAY ) + { + nBits = PickerFlags::Open | PickerFlags::InsertAsLink | PickerFlags::PlayButton; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_READONLY_VERSION ) + { + nBits = PickerFlags::Open | PickerFlags::ReadOnly | PickerFlags::ShowVersions; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_LINK_PREVIEW ) + { + nBits = PickerFlags::Open | PickerFlags::InsertAsLink | PickerFlags::ShowPreview; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR ) + { + nBits = PickerFlags::Open | PickerFlags::InsertAsLink | PickerFlags::ShowPreview | PickerFlags::ImageAnchor; + } + else if ( m_nServiceType == TemplateDescription::FILEOPEN_PREVIEW ) + { + nBits = PickerFlags::Open | PickerFlags::ShowPreview; + } + if ( m_bMultiSelection && ( nBits & PickerFlags::Open ) ) + nBits |= PickerFlags::MultiSelection; + + return nBits; +} + + +void SvtFilePicker::notify( sal_Int16 _nEventId, sal_Int16 _nControlId ) +{ + if ( !m_xListener.is() ) + return; + + FilePickerEvent aEvent( *this, _nControlId ); + + switch ( _nEventId ) + { + case FILE_SELECTION_CHANGED: + m_xListener->fileSelectionChanged( aEvent ); + break; + case DIRECTORY_CHANGED: + m_xListener->directoryChanged( aEvent ); + break; + case CTRL_STATE_CHANGED: + m_xListener->controlStateChanged( aEvent ); + break; + case DIALOG_SIZE_CHANGED: + m_xListener->dialogSizeChanged(); + break; + default: + SAL_WARN( "fpicker.office", "SvtFilePicker::notify(): Unknown event id!" ); + break; + } +} + + +namespace { + + struct FilterTitleMatch + { + protected: + const OUString& rTitle; + + public: + explicit FilterTitleMatch( const OUString& _rTitle ) : rTitle( _rTitle ) { } + + + bool operator () ( const FilterEntry& _rEntry ) + { + bool bMatch; + if ( !_rEntry.hasSubFilters() ) + // a real filter + bMatch = ( _rEntry.getTitle() == 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 ) + { + return _rEntry.First == rTitle; + } + }; +} + + +bool SvtFilePicker::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 SvtFilePicker::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 SvtFilePicker::ensureFilterList( const OUString& _rInitialCurrentFilter ) +{ + if ( !m_pFilterList ) + { + m_pFilterList.reset( new FilterList ); + + // set the first filter to the current filter + if ( m_aCurrentFilter.isEmpty() ) + m_aCurrentFilter = _rInitialCurrentFilter; + } +} + + + +SvtFilePicker::SvtFilePicker() + :m_bMultiSelection ( false ) + ,m_nServiceType ( TemplateDescription::FILEOPEN_SIMPLE ) +{ +} + +SvtFilePicker::~SvtFilePicker() +{ +} + +sal_Int16 SvtFilePicker::implExecutePicker( ) +{ + m_xDlg->SetFileCallback( this ); + + prepareExecute(); + + m_xDlg->EnableAutocompletion(); + // now we are ready to execute the dialog + sal_Int16 nRet = m_xDlg->run(); + + // the execution of the dialog yields, so it is possible the at this point the window or the dialog is closed + if (m_xDlg) + m_xDlg->SetFileCallback( nullptr ); + + return nRet; +} + +std::shared_ptr<SvtFileDialog_Base> SvtFilePicker::implCreateDialog( weld::Window* pParent ) +{ + PickerFlags nBits = getPickerFlags(); + + auto dialog = o3tl::make_shared<SvtFileDialog>(pParent, nBits); + + // Set StandardDir if present + if ( !m_aStandardDir.isEmpty()) + { + OUString sStandardDir = m_aStandardDir; + dialog->SetStandardDir( sStandardDir ); + dialog->SetDenyList( m_aDenyList ); + } + + return dialog; +} + + +// disambiguate XInterface + +IMPLEMENT_FORWARD_XINTERFACE2( SvtFilePicker, OCommonPicker, SvtFilePicker_Base ) + + +// disambiguate XTypeProvider + +IMPLEMENT_FORWARD_XTYPEPROVIDER2( SvtFilePicker, OCommonPicker, SvtFilePicker_Base ) + +IMPLEMENT_FORWARD_XINTERFACE3( SvtRemoteFilePicker, SvtFilePicker, OCommonPicker, SvtFilePicker_Base ) + + +// disambiguate XTypeProvider + +css::uno::Sequence< css::uno::Type > SAL_CALL SvtRemoteFilePicker::getTypes( ) +{ + return ::comphelper::concatSequences( + SvtFilePicker::getTypes(), + OCommonPicker::getTypes(), + SvtFilePicker_Base::getTypes() + ); +} +IMPLEMENT_GET_IMPLEMENTATION_ID( SvtRemoteFilePicker ) + + +// XExecutableDialog functions + + +void SAL_CALL SvtFilePicker::setTitle( const OUString& _rTitle ) +{ + OCommonPicker::setTitle( _rTitle ); +} + +sal_Int16 SAL_CALL SvtFilePicker::execute( ) +{ + return OCommonPicker::execute(); +} + +// XAsynchronousExecutableDialog functions +void SAL_CALL SvtFilePicker::setDialogTitle( const OUString& _rTitle ) +{ + setTitle( _rTitle ); +} + +void SAL_CALL SvtFilePicker::startExecuteModal( const Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) +{ + m_xDlgClosedListener = xListener; + prepareDialog(); + prepareExecute(); + m_xDlg->EnableAutocompletion(); + if (!m_xDlg->PrepareExecute()) + return; + weld::DialogController::runAsync(m_xDlg, [this](sal_Int32 nResult){ + DialogClosedHdl(nResult); + }); +} + +// XFilePicker functions +void SAL_CALL SvtFilePicker::setMultiSelectionMode( sal_Bool bMode ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + m_bMultiSelection = bMode; +} + +void SAL_CALL SvtFilePicker::setDefaultName( const OUString& aName ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + m_aDefaultName = aName; +} + +void SAL_CALL SvtFilePicker::setDisplayDirectory( const OUString& aDirectory ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + m_aDisplayDirectory = aDirectory; +} + +OUString SAL_CALL SvtFilePicker::getDisplayDirectory() +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (m_xDlg) + { + OUString aPath = m_xDlg->GetPath(); + + if( m_aOldHideDirectory == aPath ) + return m_aOldDisplayDirectory; + m_aOldHideDirectory = aPath; + + if( !m_xDlg->ContentIsFolder( aPath ) ) + { + INetURLObject aFolder( aPath ); + aFolder.CutLastName(); + aPath = aFolder.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + m_aOldDisplayDirectory = aPath; + return aPath; + } + else + return m_aDisplayDirectory; +} + +Sequence< OUString > SAL_CALL SvtFilePicker::getSelectedFiles() +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (!m_xDlg) + { + Sequence< OUString > aEmpty; + return aEmpty; + } + + return comphelper::containerToSequence(m_xDlg->GetPathList()); +} + +Sequence< OUString > SAL_CALL SvtFilePicker::getFiles() +{ + Sequence< OUString > aFiles = getSelectedFiles(); + if (aFiles.getLength() > 1) + aFiles.realloc(1); + return aFiles; +} + + +// XFilePickerControlAccess functions + + +void SAL_CALL SvtFilePicker::setValue( sal_Int16 nElementID, + sal_Int16 nControlAction, + const Any& rValue ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (m_xDlg) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + aAccess.setValue( nElementID, nControlAction, rValue ); + } + else + { + if ( !m_pElemList ) + m_pElemList.reset( new ElementList ); + + bool bFound = false; + + for (auto & elem : *m_pElemList) + { + if ( ( elem.m_nElementID == nElementID ) && + ( !elem.m_bHasValue || ( elem.m_nControlAction == nControlAction ) ) ) + { + elem.setAction( nControlAction ); + elem.setValue( rValue ); + bFound = true; + } + } + + if ( !bFound ) + { + ElementEntry_Impl aNew( nElementID ); + aNew.setAction( nControlAction ); + aNew.setValue( rValue ); + m_pElemList->insert( m_pElemList->end(), aNew ); + } + } +} + + +Any SAL_CALL SvtFilePicker::getValue( sal_Int16 nElementID, sal_Int16 nControlAction ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + Any aAny; + + // execute() called? + if (m_xDlg) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + aAny = aAccess.getValue( nElementID, nControlAction ); + } + else if ( m_pElemList ) + { + for (auto const& elem : *m_pElemList) + { + if ( ( elem.m_nElementID == nElementID ) && + ( elem.m_bHasValue ) && + ( elem.m_nControlAction == nControlAction ) ) + { + aAny = elem.m_aValue; + break; + } + } + } + + return aAny; +} + + +void SAL_CALL SvtFilePicker::setLabel( sal_Int16 nLabelID, const OUString& rValue ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (m_xDlg) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + aAccess.setLabel( nLabelID, rValue ); + } + else + { + if ( !m_pElemList ) + m_pElemList.reset( new ElementList ); + + bool bFound = false; + + for (auto & elem : *m_pElemList) + { + if ( elem.m_nElementID == nLabelID ) + { + elem.setLabel( rValue ); + bFound = true; + } + } + + if ( !bFound ) + { + ElementEntry_Impl aNew( nLabelID ); + aNew.setLabel( rValue ); + m_pElemList->insert( m_pElemList->end(), aNew ); + } + } +} + + +OUString SAL_CALL SvtFilePicker::getLabel( sal_Int16 nLabelID ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + OUString aLabel; + + if (m_xDlg) + { + ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView()); + aLabel = aAccess.getLabel( nLabelID ); + } + else if ( m_pElemList ) + { + for (auto const& elem : *m_pElemList) + { + if ( elem.m_nElementID == nLabelID ) + { + if ( elem.m_bHasLabel ) + aLabel = elem.m_aLabel; + break; + } + } + } + + return aLabel; +} + + +void SAL_CALL SvtFilePicker::enableControl( sal_Int16 nElementID, sal_Bool bEnable ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (m_xDlg) + { + ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView()); + aAccess.enableControl( nElementID, bEnable ); + } + else + { + if ( !m_pElemList ) + m_pElemList.reset( new ElementList ); + + bool bFound = false; + + for (auto & elem : *m_pElemList) + { + if ( elem.m_nElementID == nElementID ) + { + elem.setEnabled( bEnable ); + bFound = true; + } + } + + if ( !bFound ) + { + ElementEntry_Impl aNew( nElementID ); + aNew.setEnabled( bEnable ); + m_pElemList->insert( m_pElemList->end(), aNew ); + } + } +} + + +// XFilePickerNotifier functions + + +void SAL_CALL SvtFilePicker::addFilePickerListener( const Reference< XFilePickerListener >& xListener ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + m_xListener = xListener; +} + + +void SAL_CALL SvtFilePicker::removeFilePickerListener( const Reference< XFilePickerListener >& ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + m_xListener.clear(); +} + +// XFilePreview functions +Sequence< sal_Int16 > SAL_CALL SvtFilePicker::getSupportedImageFormats() +{ + checkAlive(); + + return { FilePreviewImageFormats::BITMAP }; +} + +sal_Int32 SAL_CALL SvtFilePicker::getTargetColorDepth() +{ + return 0; +} + +sal_Int32 SAL_CALL SvtFilePicker::getAvailableWidth() +{ + checkAlive(); + + SolarMutexGuard aGuard; + sal_Int32 nWidth = 0; + + if (m_xDlg) + nWidth = m_xDlg->getAvailableWidth(); + + return nWidth; +} + +sal_Int32 SAL_CALL SvtFilePicker::getAvailableHeight() +{ + checkAlive(); + + SolarMutexGuard aGuard; + sal_Int32 nHeight = 0; + + if (m_xDlg) + nHeight = m_xDlg->getAvailableHeight(); + + return nHeight; +} + +void SAL_CALL SvtFilePicker::setImage(sal_Int16 /*aImageFormat*/, const Any& rImage) +{ + checkAlive(); + + SolarMutexGuard aGuard; + if (m_xDlg) + m_xDlg->setImage(rImage); +} + +sal_Bool SAL_CALL SvtFilePicker::setShowState( sal_Bool ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + bool bRet = false; + + if (m_xDlg) + { + // #97633 for the system filedialog it's + // useful to make the preview switchable + // because the preview occupies + // half of the size of the file listbox + // which is not the case here, + // so we (TRA/FS) decided not to make + // the preview window switchable because + // else we would have to change the layout + // of the file dialog dynamically + // support for set/getShowState is optionally + // see css::ui::dialogs::XFilePreview + + bRet = false; + } + + return bRet; +} + + +sal_Bool SAL_CALL SvtFilePicker::getShowState() +{ + checkAlive(); + + SolarMutexGuard aGuard; + bool bRet = false; + + if (m_xDlg) + bRet = m_xDlg->getShowState(); + + return bRet; +} + + +// XFilterGroupManager functions + + +void SAL_CALL SvtFilePicker::appendFilterGroup( const OUString& sGroupTitle, + const Sequence< StringPair >& aFilters ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + + // check the names + if ( FilterNameExists( aFilters ) ) + throw IllegalArgumentException( + "filter name exists", + static_cast< OWeakObject * >(this), 1); + + // ensure that we have a filter list + OUString sInitialCurrentFilter; + if ( aFilters.hasElements() ) + sInitialCurrentFilter = aFilters[0].First; + ensureFilterList( sInitialCurrentFilter ); + + // append the filter + m_pFilterList->insert( m_pFilterList->end(), FilterEntry( sGroupTitle, aFilters ) ); +} + + +// XFilterManager functions + + +void SAL_CALL SvtFilePicker::appendFilter( const OUString& aTitle, + const OUString& aFilter ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + // check the name + if ( FilterNameExists( aTitle ) ) + // TODO: a more precise exception message + throw IllegalArgumentException(); + + // ensure that we have a filter list + ensureFilterList( aTitle ); + + // append the filter + m_pFilterList->insert( m_pFilterList->end(), FilterEntry( aTitle, aFilter ) ); +} + + +void SAL_CALL SvtFilePicker::setCurrentFilter( const OUString& aTitle ) +{ + checkAlive(); + + SolarMutexGuard aGuard; + if ( ! FilterNameExists( aTitle ) ) + throw IllegalArgumentException(); + + m_aCurrentFilter = aTitle; + + if (m_xDlg) + m_xDlg->SetCurFilter( aTitle ); +} + + +OUString SAL_CALL SvtFilePicker::getCurrentFilter() +{ + checkAlive(); + + SolarMutexGuard aGuard; + OUString aFilter = m_xDlg ? m_xDlg->GetCurFilter() : + m_aCurrentFilter; + return aFilter; +} + + +// XInitialization functions + + +void SAL_CALL SvtFilePicker::initialize( const Sequence< Any >& _rArguments ) +{ + checkAlive(); + + Sequence< Any > aArguments( _rArguments.getLength()); + + m_nServiceType = TemplateDescription::FILEOPEN_SIMPLE; + + if ( _rArguments.hasElements() ) + { + // compatibility: one argument, type sal_Int16 , specifies the service type + int index = 0; + auto pArguments = aArguments.getArray(); + if (_rArguments[0] >>= m_nServiceType) + { + // skip the first entry if it was the ServiceType, because it's not needed in OCommonPicker::initialize and it's not a NamedValue + NamedValue emptyNamedValue; + pArguments[0] <<= emptyNamedValue; + index = 1; + + } + for ( int i = index; i < _rArguments.getLength(); i++) + { + NamedValue namedValue; + pArguments[i] = _rArguments[i]; + + if (aArguments[i] >>= namedValue ) + { + + if ( namedValue.Name == "StandardDir" ) + { + OUString sStandardDir; + + namedValue.Value >>= sStandardDir; + + // Set the directory for the "back to the default dir" button + if ( !sStandardDir.isEmpty() ) + { + m_aStandardDir = sStandardDir; + } + } + else if ( namedValue.Name == "DenyList" ) + { + namedValue.Value >>= m_aDenyList; + } + } + } + } + + // let the base class analyze the sequence (will call into implHandleInitializationArgument) + OCommonPicker::initialize( aArguments ); +} + + +bool SvtFilePicker::implHandleInitializationArgument( const OUString& _rName, const Any& _rValue ) +{ + if ( _rName == "TemplateDescription" ) + { + m_nServiceType = TemplateDescription::FILEOPEN_SIMPLE; + OSL_VERIFY( _rValue >>= m_nServiceType ); + return true; + } + if ( _rName == "StandardDir" ) + { + OSL_VERIFY( _rValue >>= m_aStandardDir ); + return true; + } + + if ( _rName == "DenyList" ) + { + OSL_VERIFY( _rValue >>= m_aDenyList ); + return true; + } + + + return OCommonPicker::implHandleInitializationArgument( _rName, _rValue ); +} + + +// XServiceInfo + + +/* XServiceInfo */ +OUString SAL_CALL SvtFilePicker::getImplementationName() +{ + return "com.sun.star.svtools.OfficeFilePicker"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SvtFilePicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SvtFilePicker::getSupportedServiceNames() +{ + return { "com.sun.star.ui.dialogs.OfficeFilePicker" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_SvtFilePicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SvtFilePicker()); +} + + +// SvtRemoteFilePicker + +SvtRemoteFilePicker::SvtRemoteFilePicker() +{ +} + +std::shared_ptr<SvtFileDialog_Base> SvtRemoteFilePicker::implCreateDialog(weld::Window* pParent) +{ + PickerFlags nBits = getPickerFlags(); + + auto dialog = std::make_shared<RemoteFilesDialog>(pParent, nBits); + + // Set StandardDir if present + if ( !m_aStandardDir.isEmpty()) + { + OUString sStandardDir = m_aStandardDir; + dialog->SetStandardDir( sStandardDir ); + dialog->SetDenyList( m_aDenyList ); + } + + return dialog; +} + +// XServiceInfo + + +/* XServiceInfo */ +OUString SAL_CALL SvtRemoteFilePicker::getImplementationName() +{ + return "com.sun.star.svtools.RemoteFilePicker"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SvtRemoteFilePicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SvtRemoteFilePicker::getSupportedServiceNames() +{ + return { "com.sun.star.ui.dialogs.RemoteFilePicker" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_SvtRemoteFilePicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SvtRemoteFilePicker()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/OfficeFilePicker.hxx b/fpicker/source/office/OfficeFilePicker.hxx new file mode 100644 index 000000000..b75a3f636 --- /dev/null +++ b/fpicker/source/office/OfficeFilePicker.hxx @@ -0,0 +1,239 @@ +/* -*- 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 <cppuhelper/implbase5.hxx> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/ui/dialogs/XFilePreview.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerListener.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XEventListener.hpp> + + +#include "commonpicker.hxx" +#include "pickercallbacks.hxx" + +#include <vector> + +class Dialog; +struct FilterEntry; +struct ElementEntry_Impl; +enum class PickerFlags; + +typedef ::std::vector< FilterEntry > FilterList; // can be maintained more effectively +typedef ::std::vector< ElementEntry_Impl > ElementList; + +typedef css::beans::StringPair UnoFilterEntry; +typedef css::uno::Sequence< UnoFilterEntry > UnoFilterList; // can be transported more effectively + +// class SvtFilePicker --------------------------------------------------- + +typedef ::cppu::ImplHelper5 < css::ui::dialogs::XFilePicker3 + , css::ui::dialogs::XFilePickerControlAccess + , css::ui::dialogs::XFilePreview + , css::lang::XServiceInfo + , css::ui::dialogs::XAsynchronousExecutableDialog + > SvtFilePicker_Base; + +class SvtFilePicker :public SvtFilePicker_Base + ,public ::svt::OCommonPicker + ,public ::svt::IFilePickerListener +{ +protected: + std::unique_ptr<FilterList> + m_pFilterList; + std::unique_ptr<ElementList> + m_pElemList; + + bool m_bMultiSelection; + sal_Int16 m_nServiceType; + OUString m_aDefaultName; + OUString m_aCurrentFilter; + + OUString m_aOldDisplayDirectory; + OUString m_aOldHideDirectory; + + OUString m_aStandardDir; + css::uno::Sequence< OUString > + m_aDenyList; + + css::uno::Reference< css::ui::dialogs::XFilePickerListener > + m_xListener; + css::uno::Reference< css::ui::dialogs::XDialogClosedListener > + m_xDlgClosedListener; + +public: + SvtFilePicker(); + virtual ~SvtFilePicker() override; + + + // disambiguate XInterface + + DECLARE_XINTERFACE( ) + + + // disambiguate XTypeProvider + + DECLARE_XTYPEPROVIDER( ) + + + // XExecutableDialog functions + + virtual void SAL_CALL setTitle( const OUString& _rTitle ) override; + virtual sal_Int16 SAL_CALL execute( ) override; + + + // XAsynchronousExecutableDialog functions + + virtual void SAL_CALL setDialogTitle( const OUString& _rTitle ) override; + virtual void SAL_CALL startExecuteModal( const css::uno::Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) 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; + + + // XFilePickerControlAccess functions + + + virtual void SAL_CALL setValue( sal_Int16 ElementID, sal_Int16 ControlAction, const css::uno::Any& value ) override; + virtual css::uno::Any SAL_CALL getValue( sal_Int16 ElementID, sal_Int16 ControlAction ) override; + virtual void SAL_CALL setLabel( sal_Int16 ElementID, const OUString& aValue ) override; + virtual OUString SAL_CALL getLabel( sal_Int16 ElementID ) override; + virtual void SAL_CALL enableControl( sal_Int16 ElementID, sal_Bool bEnable ) override; + + + // XFilePickerNotifier functions + + + 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; + + + // XFilePreview functions + + + virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats() override; + virtual sal_Int32 SAL_CALL getTargetColorDepth() override; + virtual sal_Int32 SAL_CALL getAvailableWidth() override; + virtual sal_Int32 SAL_CALL getAvailableHeight() override; + virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const css::uno::Any& aImage ) override; + virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState ) override; + virtual sal_Bool SAL_CALL getShowState() 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; + + + // these methods are here because they're ambiguous + + virtual void SAL_CALL cancel() override + { ::svt::OCommonPicker::cancel(); } + virtual void SAL_CALL dispose() override + { ::svt::OCommonPicker::dispose(); } + virtual void SAL_CALL addEventListener(const css::uno::Reference<css::lang::XEventListener>& l) override + { ::svt::OCommonPicker::addEventListener(l); } + virtual void SAL_CALL removeEventListener(const css::uno::Reference<css::lang::XEventListener>& l) override + { ::svt::OCommonPicker::removeEventListener(l); } + + + // XInitialization functions + + + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + + // XServiceInfo functions + + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + +protected: + + // OCommonPicker overridables + + virtual std::shared_ptr<SvtFileDialog_Base> implCreateDialog( weld::Window* pParent ) override; + virtual sal_Int16 implExecutePicker( ) override; + virtual bool implHandleInitializationArgument( + const OUString& _rName, + const css::uno::Any& _rValue + ) override; + +protected: + PickerFlags getPickerFlags() const; + virtual void notify( sal_Int16 _nEventId, sal_Int16 _nControlId ) override; + + bool FilterNameExists( const OUString& rTitle ); + bool FilterNameExists( const UnoFilterList& _rGroupedFilters ); + + void ensureFilterList( const OUString& _rInitialCurrentFilter ); + + void prepareExecute( ); + + void DialogClosedHdl(sal_Int32 nResult); +}; + +// SvtRemoteFilePicker + +class SvtRemoteFilePicker : public SvtFilePicker +{ +public: + SvtRemoteFilePicker(); + + virtual std::shared_ptr<SvtFileDialog_Base> implCreateDialog( weld::Window* pParent ) override; + + // disambiguate XInterface + + DECLARE_XINTERFACE( ) + + // disambiguate XTypeProvider + + DECLARE_XTYPEPROVIDER( ) + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/OfficeFolderPicker.cxx b/fpicker/source/office/OfficeFolderPicker.cxx new file mode 100644 index 000000000..c941d6cb6 --- /dev/null +++ b/fpicker/source/office/OfficeFolderPicker.cxx @@ -0,0 +1,175 @@ +/* -*- 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 "OfficeFolderPicker.hxx" + +#include "iodlg.hxx" + +#include <vector> +#include <tools/urlobj.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/make_shared.hxx> +#include <unotools/pathoptions.hxx> + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +SvtFolderPicker::SvtFolderPicker() +{ +} + +SvtFolderPicker::~SvtFolderPicker() +{ +} + +void SAL_CALL SvtFolderPicker::setTitle( const OUString& _rTitle ) +{ + OCommonPicker::setTitle( _rTitle ); +} + +sal_Int16 SAL_CALL SvtFolderPicker::execute( ) +{ + return OCommonPicker::execute(); +} + +void SAL_CALL SvtFolderPicker::setDialogTitle( const OUString& _rTitle) +{ + setTitle( _rTitle ); +} + +void SAL_CALL SvtFolderPicker::startExecuteModal( const Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) +{ + m_xListener = xListener; + prepareDialog(); + prepareExecute(); + + m_xDlg->EnableAutocompletion(); + if (!m_xDlg->PrepareExecute()) + return; + weld::DialogController::runAsync(m_xDlg, [this](sal_Int32 nResult){ + DialogClosedHdl(nResult); + }); +} + +std::shared_ptr<SvtFileDialog_Base> SvtFolderPicker::implCreateDialog( weld::Window* pParent ) +{ + return o3tl::make_shared<SvtFileDialog>(pParent, PickerFlags::PathDialog); +} + +sal_Int16 SvtFolderPicker::implExecutePicker( ) +{ + prepareExecute(); + + // now we are ready to execute the dialog + m_xDlg->EnableAutocompletion( false ); + return m_xDlg->run(); +} + +void SvtFolderPicker::prepareExecute() +{ + // set the default directory + if ( !m_aDisplayDirectory.isEmpty() ) + m_xDlg->SetPath( m_aDisplayDirectory ); + else + { + // set the default standard dir + INetURLObject aStdDirObj( SvtPathOptions().GetWorkPath() ); + m_xDlg->SetPath( aStdDirObj.GetMainURL( INetURLObject::DecodeMechanism::NONE) ); + } +} + +void SvtFolderPicker::DialogClosedHdl(sal_Int32 nResult) +{ + if ( m_xListener.is() ) + { + sal_Int16 nRet = static_cast<sal_Int16>(nResult); + css::ui::dialogs::DialogClosedEvent aEvent( *this, nRet ); + m_xListener->dialogClosed( aEvent ); + m_xListener.clear(); + } +} + +void SAL_CALL SvtFolderPicker::setDisplayDirectory( const OUString& aDirectory ) +{ + m_aDisplayDirectory = aDirectory; +} + +OUString SAL_CALL SvtFolderPicker::getDisplayDirectory() +{ + if (!m_xDlg) + return m_aDisplayDirectory; + + std::vector<OUString> aPathList(m_xDlg->GetPathList()); + + if(!aPathList.empty()) + return aPathList[0]; + + return OUString(); +} + +OUString SAL_CALL SvtFolderPicker::getDirectory() +{ + if (!m_xDlg) + return m_aDisplayDirectory; + + std::vector<OUString> aPathList(m_xDlg->GetPathList()); + + if(!aPathList.empty()) + return aPathList[0]; + + return OUString(); +} + +void SAL_CALL SvtFolderPicker::setDescription( const OUString& ) +{ +} + +void SvtFolderPicker::cancel() +{ + OCommonPicker::cancel(); +} + +/* XServiceInfo */ +OUString SAL_CALL SvtFolderPicker::getImplementationName() +{ + return "com.sun.star.svtools.OfficeFolderPicker"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SvtFolderPicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SvtFolderPicker::getSupportedServiceNames() +{ + return { "com.sun.star.ui.dialogs.OfficeFolderPicker" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_SvtFolderPicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SvtFolderPicker()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/OfficeFolderPicker.hxx b/fpicker/source/office/OfficeFolderPicker.hxx new file mode 100644 index 000000000..7b98fb5bf --- /dev/null +++ b/fpicker/source/office/OfficeFolderPicker.hxx @@ -0,0 +1,89 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include "commonpicker.hxx" + +class Dialog; + +typedef + cppu::ImplInheritanceHelper< + svt::OCommonPicker, css::ui::dialogs::XFolderPicker2, + css::ui::dialogs::XAsynchronousExecutableDialog, + css::lang::XServiceInfo > + SvtFolderPicker_Base; + +class SvtFolderPicker: public SvtFolderPicker_Base +{ +private: + css::uno::Reference< css::ui::dialogs::XDialogClosedListener > + m_xListener; + + void prepareExecute(); + void DialogClosedHdl(sal_Int32 nResult); + +public: + SvtFolderPicker(); + virtual ~SvtFolderPicker() override; + + + // XFolderPicker2 functions + + virtual void SAL_CALL setDisplayDirectory( const OUString& aDirectory ) override; + virtual OUString SAL_CALL getDisplayDirectory() override; + virtual OUString SAL_CALL getDirectory() override; + virtual void SAL_CALL setDescription( const OUString& aDescription ) override; + + virtual void SAL_CALL cancel() override; + + + // XExecutableDialog functions + + virtual void SAL_CALL setTitle( const OUString& _rTitle ) override; + virtual sal_Int16 SAL_CALL execute( ) override; + + + // XAsynchronousExecutableDialog functions + + virtual void SAL_CALL setDialogTitle( const OUString& _rTitle ) override; + virtual void SAL_CALL startExecuteModal( const css::uno::Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) override; + + + // XServiceInfo functions + + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + +protected: + + // OCommonPicker overridables + + virtual std::shared_ptr<SvtFileDialog_Base> implCreateDialog( weld::Window* pParent ) override; + virtual sal_Int16 implExecutePicker( ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/PlacesListBox.cxx b/fpicker/source/office/PlacesListBox.cxx new file mode 100644 index 000000000..86bd50517 --- /dev/null +++ b/fpicker/source/office/PlacesListBox.cxx @@ -0,0 +1,158 @@ +/* -*- 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/. + */ + +#include "PlacesListBox.hxx" +#include <svtools/PlaceEditDialog.hxx> + +#include <bitmaps.hlst> + +PlacesListBox::PlacesListBox(std::unique_ptr<weld::TreeView> xControl, + std::unique_ptr<weld::Button> xAdd, + std::unique_ptr<weld::Button> xDel, + SvtFileDialog* pFileDlg) + : mpDlg(pFileDlg) + , mxImpl(std::move(xControl)) + , mxAddBtn(std::move(xAdd)) + , mxDelBtn(std::move(xDel)) + , mnNbEditables(0) + , mbUpdated( false ) +{ + Size aSize(mxImpl->get_approximate_digit_width() * 18, + mxImpl->get_height_rows(9)); + mxImpl->set_size_request(aSize.Width(), aSize.Height()); + + mxImpl->connect_changed( LINK( this, PlacesListBox, Selection ) ); + mxImpl->connect_row_activated( LINK( this, PlacesListBox, DoubleClick ) ) ; + mxImpl->connect_query_tooltip(LINK(this, PlacesListBox, QueryTooltipHdl)); +} + +PlacesListBox::~PlacesListBox( ) +{ +} + +void PlacesListBox::AppendPlace( const PlacePtr& pPlace ) +{ + maPlaces.push_back( pPlace ); + mxImpl->append_text(pPlace->GetName()); + mxImpl->set_image(maPlaces.size() - 1, getEntryIcon(pPlace)); + + if(pPlace->IsEditable()) { + ++mnNbEditables; + mbUpdated = true; + } +} + +bool PlacesListBox::IsUpdated() { + if(mbUpdated) { + mbUpdated = false; + return true; + } + return false; +} + +void PlacesListBox::RemovePlace( sal_uInt16 nPos ) +{ + if ( nPos < maPlaces.size() ) + { + if(maPlaces[nPos]->IsEditable()) { + --mnNbEditables; + mbUpdated = true; + } + maPlaces.erase( maPlaces.begin() + nPos ); + mxImpl->remove(nPos); + } +} + +void PlacesListBox::RemoveSelectedPlace() { + RemovePlace(mxImpl->get_cursor_index()); +} + +void PlacesListBox::SetAddHdl( const Link<weld::Button&,void>& rHdl ) +{ + mxAddBtn->connect_clicked( rHdl ); +} + +void PlacesListBox::SetDelHdl( const Link<weld::Button&,void>& rHdl ) +{ + mxDelBtn->connect_clicked( rHdl ); +} + +void PlacesListBox::SetDelEnabled( bool enabled ) +{ + mxDelBtn->set_sensitive( enabled ); +} + +OUString PlacesListBox::getEntryIcon( const PlacePtr& pPlace ) +{ + OUString theImage = BMP_FILEDLG_PLACE_LOCAL; + if ( !pPlace->IsLocal( ) ) + theImage = BMP_FILEDLG_PLACE_REMOTE; + return theImage; +} + +IMPL_LINK_NOARG( PlacesListBox, Selection, weld::TreeView&, void ) +{ + sal_uInt32 nSelected = mxImpl->get_cursor_index(); + PlacePtr pPlace = maPlaces[nSelected]; + + if (pPlace->IsEditable()) + mpDlg->RemovablePlaceSelected(); + else + mpDlg->RemovablePlaceSelected(false); + + updateView(); +} + +IMPL_LINK_NOARG( PlacesListBox, DoubleClick, weld::TreeView&, bool ) +{ + sal_uInt16 nSelected = mxImpl->get_cursor_index(); + PlacePtr pPlace = maPlaces[nSelected]; + if ( !pPlace->IsEditable() || pPlace->IsLocal( ) ) + return true; + PlaceEditDialog aDlg(mpDlg->getDialog(), pPlace); + short aRetCode = aDlg.run(); + switch (aRetCode) + { + case RET_OK : + { + pPlace->SetName ( aDlg.GetServerName() ); + pPlace->SetUrl( aDlg.GetServerUrl() ); + mbUpdated = true; + break; + } + case RET_NO : + { + RemovePlace(nSelected); + break; + } + default: + break; + } + return true; +} + +IMPL_LINK(PlacesListBox, QueryTooltipHdl, const weld::TreeIter&, rIter, OUString) +{ + const OUString sText = mxImpl->get_text(rIter); + for (const auto& pPlace : maPlaces) + { + if (pPlace->GetName() == sText) + return pPlace->GetUrlObject().GetMainURL(INetURLObject::DecodeMechanism::Unambiguous); + } + return OUString(); +} + +void PlacesListBox::updateView( ) +{ + sal_uInt32 nSelected = mxImpl->get_cursor_index(); + PlacePtr pPlace = maPlaces[nSelected]; + mpDlg->OpenURL_Impl( pPlace->GetUrl( ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/PlacesListBox.hxx b/fpicker/source/office/PlacesListBox.hxx new file mode 100644 index 000000000..ed3a0798e --- /dev/null +++ b/fpicker/source/office/PlacesListBox.hxx @@ -0,0 +1,66 @@ +/* -*- 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/. + */ +#pragma once + +#include "iodlg.hxx" + +#include <svtools/place.hxx> +#include <vcl/weld.hxx> + +#include <memory> +#include <vector> + +typedef std::shared_ptr<Place> PlacePtr; + +class PlacesListBox; + +/** ListBox to handle Places. + */ +class PlacesListBox +{ +private: + std::vector<PlacePtr> maPlaces; + SvtFileDialog* mpDlg; + std::unique_ptr<weld::TreeView> mxImpl; + std::unique_ptr<weld::Button> mxAddBtn; + std::unique_ptr<weld::Button> mxDelBtn; + sal_Int32 mnNbEditables; + bool mbUpdated; + +public: + PlacesListBox(std::unique_ptr<weld::TreeView> xTreeView, + std::unique_ptr<weld::Button> xAddBtn, + std::unique_ptr<weld::Button> xDelBtn, + SvtFileDialog* pFileDlg); + ~PlacesListBox(); + + void AppendPlace( const PlacePtr& pPlace ); + void RemovePlace( sal_uInt16 nPos ); + void RemoveSelectedPlace(); + sal_Int32 GetNbEditablePlaces() const { return mnNbEditables;} + bool IsUpdated(); + const std::vector<PlacePtr>& GetPlaces() const { return maPlaces;} + + void SetAddHdl( const Link<weld::Button&,void>& rHdl ); + void SetDelHdl( const Link<weld::Button&,void>& rHdl ); + void SetDelEnabled( bool enabled ); + void updateView( ); + + void set_help_id(const OString& rHelpId) { mxImpl->set_help_id(rHelpId); } + +private: + + static OUString getEntryIcon(const PlacePtr& pPlace); + + DECL_LINK( Selection, weld::TreeView&, void ); + DECL_LINK( DoubleClick, weld::TreeView&, bool ); + DECL_LINK(QueryTooltipHdl, const weld::TreeIter&, OUString); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/QueryFolderName.hxx b/fpicker/source/office/QueryFolderName.hxx new file mode 100644 index 000000000..81bc4ad10 --- /dev/null +++ b/fpicker/source/office/QueryFolderName.hxx @@ -0,0 +1,39 @@ +/* -*- 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 <vcl/weld.hxx> + +class QueryFolderNameDialog : public weld::GenericDialogController +{ +private: + std::unique_ptr<weld::Entry> m_xNameEdit; + std::unique_ptr<weld::Button> m_xOKBtn; + + DECL_LINK(OKHdl, weld::Button&, void); + DECL_LINK(NameHdl, weld::Entry&, void); + +public: + QueryFolderNameDialog(weld::Window* _pParent, const OUString& rTitle, + const OUString& rDefaultText); + virtual ~QueryFolderNameDialog() override; + OUString GetName() const { return m_xNameEdit->get_text(); } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/RemoteFilesDialog.cxx b/fpicker/source/office/RemoteFilesDialog.cxx new file mode 100644 index 000000000..4536690f9 --- /dev/null +++ b/fpicker/source/office/RemoteFilesDialog.cxx @@ -0,0 +1,1201 @@ +/* -*- 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/. + */ + +#include <config_oauth2.h> + +#include "fpsmartcontent.hxx" +#include "QueryFolderName.hxx" +#include "RemoteFilesDialog.hxx" +#include <fpicker/fpsofficeResMgr.hxx> +#include <fpicker/strings.hrc> +#include <strings.hrc> +#include <comphelper/docpasswordrequest.hxx> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/PasswordContainer.hpp> +#include <svtools/PlaceEditDialog.hxx> +#include <tools/debug.hxx> +#include <unotools/ucbhelper.hxx> +#include <vcl/errinf.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace ::svt; + +RemoteFilesDialog::RemoteFilesDialog( weld::Window* pParent, PickerFlags nBits ) + : SvtFileDialog_Base( pParent, "fps/ui/remotefilesdialog.ui", "RemoteFilesDialog" ) + , m_xContext( comphelper::getProcessComponentContext() ) + , m_xMasterPasswd( PasswordContainer::create( m_xContext ) ) + , m_bIsInExecute( false ) + , m_xOk_btn(m_xBuilder->weld_button("ok")) + , m_xCancel_btn(m_xBuilder->weld_button("cancel")) + , m_xManageServices(m_xBuilder->weld_menu_button("add_service_btn")) + , m_xServices_lb(m_xBuilder->weld_combo_box("services_lb")) + , m_xPathContainer(m_xBuilder->weld_container("breadcrumb_container")) + , m_xNewFolder(m_xBuilder->weld_button("new_folder")) + , m_xListView_btn(m_xBuilder->weld_toggle_button("list_view")) + , m_xIconView_btn(m_xBuilder->weld_toggle_button("icon_view")) + , m_xFilter_lb(m_xBuilder->weld_combo_box("filter_lb")) + , m_xName_ed(new AutocompleteEdit(m_xBuilder->weld_entry("filename"))) +{ + m_eMode = ( nBits & PickerFlags::SaveAs ) ? REMOTEDLG_MODE_SAVE : REMOTEDLG_MODE_OPEN; + m_eType = ( nBits & PickerFlags::PathDialog ) ? REMOTEDLG_TYPE_PATHDLG : REMOTEDLG_TYPE_FILEDLG; + bool bMultiselection = bool( nBits & PickerFlags::MultiSelection ); + m_bIsUpdated = false; + m_bIsConnected = false; + m_bServiceChanged = false; + m_nCurrentFilter = -1; + + m_xName_ed->show(); + + // limit width due to super wide strings that may end up here + m_xFilter_lb->set_size_request(m_xFilter_lb->get_approximate_digit_width() * 60, -1); + + m_xFilter_lb->set_sensitive(false); + m_xName_ed->set_sensitive(false); + m_xNewFolder->set_sensitive(false); + + if( m_eMode == REMOTEDLG_MODE_OPEN ) + { + m_xNewFolder->hide(); + } + else + { + m_xOk_btn->set_label(FpsResId(STR_EXPLORERFILE_BUTTONSAVE)); + m_xNewFolder->connect_clicked( LINK( this, RemoteFilesDialog, NewFolderHdl ) ); + } + + m_xListView_btn->set_active(true); + m_xIconView_btn->connect_clicked( LINK( this, RemoteFilesDialog, IconViewHdl ) ); + m_xListView_btn->connect_clicked( LINK( this, RemoteFilesDialog, ListViewHdl ) ); + + m_xOk_btn->set_sensitive(false); + + m_xOk_btn->connect_clicked( LINK( this, RemoteFilesDialog, OkHdl ) ); + m_xCancel_btn->connect_clicked( LINK( this, RemoteFilesDialog, CancelHdl ) ); + + m_sRootLabel = FpsResId( STR_SVT_ROOTLABEL ); + m_xPath.reset(new Breadcrumb(m_xPathContainer.get())); + m_xPath->connect_clicked( LINK( this, RemoteFilesDialog, SelectBreadcrumbHdl ) ); + m_xPath->SetMode( SvtBreadcrumbMode::ALL_VISITED ); + + m_xContainer = m_xBuilder->weld_container("container"); + m_xContainer->set_size_request(m_xContainer->get_approximate_digit_width() * 82, -1); + + m_xFileView.reset(new SvtFileView(m_xDialog.get(), + m_xBuilder->weld_tree_view("fileview"), + m_xBuilder->weld_icon_view("iconview"), + REMOTEDLG_TYPE_PATHDLG == m_eType, + bMultiselection, false)); + + m_xFileView->SetDoubleClickHdl( LINK( this, RemoteFilesDialog, DoubleClickHdl ) ); + m_xFileView->SetSelectHdl( LINK( this, RemoteFilesDialog, SelectHdl ) ); + m_xFileView->EnableDelete( true ); + + m_xTreeView.reset(new FolderTree(m_xBuilder->weld_tree_view("foldertree"), m_xDialog.get())); + m_xTreeView->connect_changed(LINK(this, RemoteFilesDialog, TreeSelectHdl)); + + m_xContainer->set_sensitive(false); + + m_sIniKey = "RemoteFilesDialog"; + InitSize(); + + m_xName_ed->connect_focus_in(LINK(this, RemoteFilesDialog, FileNameGetFocusHdl)); + m_xName_ed->connect_changed(LINK(this, RemoteFilesDialog, FileNameModifyHdl)); + + m_xManageServices->connect_selected(LINK(this, RemoteFilesDialog, EditServiceMenuHdl)); + + FillServicesListbox(); + + m_xServices_lb->connect_changed( LINK( this, RemoteFilesDialog, SelectServiceHdl ) ); + + m_xFilter_lb->connect_changed( LINK( this, RemoteFilesDialog, SelectFilterHdl ) ); +} + +RemoteFilesDialog::~RemoteFilesDialog() +{ + m_xFileView->SetSelectHdl(Link<SvtFileView*,void>()); + + // save window state + if( !m_sIniKey.isEmpty() ) + { + SvtViewOptions aDlgOpt( EViewType::Dialog, m_sIniKey ); + aDlgOpt.SetWindowState(OStringToOUString(m_xDialog->get_window_state(WindowStateMask::All), RTL_TEXTENCODING_UTF8)); + + Size aSize(m_xDialog->get_size()); + + OUString sSize = OUString::number( aSize.Width() ) + "|"; + sSize = sSize + OUString::number( aSize.Height() ) + "|"; + + OUString sUserData = m_xFileView->GetConfigString(); + aDlgOpt.SetUserItem( "UserData", + Any( sSize + sUserData ) ); + } + + // save services + std::shared_ptr< comphelper::ConfigurationChanges > batch( comphelper::ConfigurationChanges::create() ); + + officecfg::Office::Common::Misc::FilePickerLastService::set( m_sLastServiceUrl, batch ); + + if( m_bIsUpdated ) + { + Sequence< OUString > placesUrlsList( m_aServices.size() ); + auto placesUrlsListRange = asNonConstRange(placesUrlsList); + Sequence< OUString > placesNamesList( m_aServices.size() ); + auto placesNamesListRange = asNonConstRange(placesNamesList); + + int i = 0; + for (auto const& service : m_aServices) + { + placesUrlsListRange[i] = service->GetUrl(); + placesNamesListRange[i] = service->GetName(); + ++i; + } + + officecfg::Office::Common::Misc::FilePickerPlacesUrls::set( placesUrlsList, batch ); + officecfg::Office::Common::Misc::FilePickerPlacesNames::set( placesNamesList, batch ); + } + + batch->commit(); +} + +void RemoteFilesDialog::EnableExtraMenuItems(bool bEnable) +{ + m_xManageServices->set_item_visible("change_password", bEnable); + m_xManageServices->set_item_visible("edit_service", bEnable); + m_xManageServices->set_item_visible("delete_service", bEnable); + m_xManageServices->set_item_visible("change_password", bEnable); +} + +short RemoteFilesDialog::run() +{ + if (m_xServices_lb->get_count() > 0) + { + m_xDialog->show(); + SelectServiceHdl(*m_xServices_lb); + } + if (!m_bIsConnected) + { + m_xServices_lb->set_active(-1); + EnableExtraMenuItems(false); + } + + m_bIsInExecute = true; + short nRet = SvtFileDialog_Base::run(); + m_bIsInExecute = false; + return nRet; +} + +static OUString lcl_GetServiceType( const ServicePtr& pService ) +{ + INetProtocol aProtocol = pService->GetUrlObject().GetProtocol(); + switch( aProtocol ) + { + case INetProtocol::Ftp: + return "FTP"; + case INetProtocol::Cmis: + { + OUString sHost = pService->GetUrlObject().GetHost( INetURLObject::DecodeMechanism::WithCharset ); + + if( sHost.startsWith( GDRIVE_BASE_URL ) ) + return "Google Drive"; + else if( sHost.startsWith( ALFRESCO_CLOUD_BASE_URL ) ) + return "Alfresco Cloud"; + else if( sHost.startsWith( ONEDRIVE_BASE_URL ) ) + return "OneDrive"; + + return "CMIS"; + } + case INetProtocol::Smb: + return "Windows Share"; + case INetProtocol::File: + return "SSH"; + case INetProtocol::Http: + return "WebDAV"; + case INetProtocol::Https: + return "WebDAV"; + case INetProtocol::Generic: + return "SSH"; + default: + return OUString(); + } +} + +void RemoteFilesDialog::InitSize() +{ + if( m_sIniKey.isEmpty() ) + return; + + // initialize from config + SvtViewOptions aDlgOpt( EViewType::Dialog, m_sIniKey ); + + if( !aDlgOpt.Exists() ) + return; + + m_xDialog->set_window_state(OUStringToOString(aDlgOpt.GetWindowState(), RTL_TEXTENCODING_UTF8)); + + Any aUserData = aDlgOpt.GetUserItem( "UserData" ); + OUString sCfgStr; + if( aUserData >>= sCfgStr ) + { + sal_Int32 nPos1{ sCfgStr.indexOf('|') }; + if (nPos1<0) + return; + sal_Int32 nPos2{ sCfgStr.indexOf('|', nPos1+1 ) }; + if (nPos2<0) + return; + m_xFileView->SetConfigString( sCfgStr.subView(nPos2+1) ); + } +} + +void RemoteFilesDialog::FillServicesListbox() +{ + m_xServices_lb->clear(); + m_aServices.clear(); + + // Load from user settings + Sequence< OUString > placesUrlsList( officecfg::Office::Common::Misc::FilePickerPlacesUrls::get() ); + Sequence< OUString > placesNamesList( officecfg::Office::Common::Misc::FilePickerPlacesNames::get() ); + + unsigned int nPos = 0; + unsigned int i = 0; + + m_sLastServiceUrl = officecfg::Office::Common::Misc::FilePickerLastService::get(); + + for( sal_Int32 nPlace = 0; nPlace < placesUrlsList.getLength() && nPlace < placesNamesList.getLength(); ++nPlace ) + { + ServicePtr pService = std::make_shared<Place>( placesNamesList[nPlace], placesUrlsList[nPlace], true ); + m_aServices.push_back( pService ); + + // Add to the listbox only remote services, not local bookmarks + if( !pService->IsLocal() ) + { + OUString sPrefix = lcl_GetServiceType( pService ); + + if( !sPrefix.isEmpty() ) + sPrefix += ": "; + + if( placesUrlsList[nPlace] == m_sLastServiceUrl ) + nPos = i; + + m_xServices_lb->append_text(sPrefix + placesNamesList[nPlace]); + + i++; + } + } + + if (m_xServices_lb->get_count() > 0) + { + m_xServices_lb->set_active(nPos); + EnableExtraMenuItems(true); + } + else + EnableExtraMenuItems(false); + + EnableControls(); +} + +int RemoteFilesDialog::GetSelectedServicePos() +{ + if( m_aServices.empty() ) + return -1; + + int nPos = 0; + int i = -1; + + int nSelected = m_xServices_lb->get_active(); + + int nServices = static_cast<int>(m_aServices.size()); + while( nPos < nServices ) + { + while( (nPos < nServices) && m_aServices[nPos]->IsLocal() ) + nPos++; + i++; + if( i == nSelected ) + break; + nPos++; + } + + return nPos; +} + +void RemoteFilesDialog::AddFilter( const OUString& rFilter, const OUString& rType ) +{ + OUString sName = rFilter; + + m_aFilters.emplace_back( rFilter, rType ); + if (rType.isEmpty()) + m_xFilter_lb->append_separator(""); + else + m_xFilter_lb->append_text(sName); + + if (m_xFilter_lb->get_active() == -1) + m_xFilter_lb->set_active(0); +} + +void RemoteFilesDialog::OpenURL( OUString const & sURL ) +{ + if( !m_xFileView ) + return; + + DisableControls(); + + auto xWait = std::make_unique<weld::WaitObject>(m_xDialog.get()); + + if( !sURL.isEmpty() ) + { + OUString sFilter = FILEDIALOG_FILTER_ALL; + + if( m_nCurrentFilter != -1) + { + sFilter = m_aFilters[m_nCurrentFilter].second; + } + + m_xFileView->EndInplaceEditing(); + + DBG_ASSERT( !m_pCurrentAsyncAction.is(), "SvtFileDialog::executeAsync: previous async action not yet finished!" ); + + m_pCurrentAsyncAction = new AsyncPickerAction( this, m_xFileView.get(), AsyncPickerAction::Action::eOpenURL ); + + // -1 timeout - sync + m_pCurrentAsyncAction->execute( sURL, sFilter, -1, -1, GetDenyList() ); + + if( m_eMode != REMOTEDLG_MODE_SAVE ) + m_xName_ed->set_text( "" ); + + m_xFileView->grab_focus(); + } + else + { + xWait.reset(); + + // content doesn't exist + ErrorHandler::HandleError( ERRCODE_IO_NOTEXISTS ); + + EnableControls(); + } +} + +OUString RemoteFilesDialog::AddFileExtension(const OUString& rFileName) +{ + if (m_nCurrentFilter == -1) + return rFileName; + + OUString sExt = m_aFilters[m_nCurrentFilter].second; + sal_Int32 nDotPos = rFileName.lastIndexOf( '.' ); + + if (nDotPos == -1) + return rFileName + sExt.subView( 1 ); // without '*' + + return rFileName; +} + +void RemoteFilesDialog::EnableControls() +{ + if (m_xServices_lb->get_count() > 0) + { + m_xServices_lb->set_sensitive(true); + + if (m_xServices_lb->get_active() != -1) + { + m_xManageServices->set_item_sensitive("change_password", false); + + try + { + if( m_xMasterPasswd->isPersistentStoringAllowed() ) + { + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 ) + { + OUString sUrl( m_aServices[nPos]->GetUrl() ); + + UrlRecord aURLEntries = m_xMasterPasswd->find( sUrl, Reference< XInteractionHandler>() ); + + if( aURLEntries.UserList.hasElements() ) + { + m_xManageServices->set_item_sensitive("change_password", true); + } + } + } + } + catch( const Exception& ) + {} + } + } + else + m_xServices_lb->set_sensitive(false); + + if( m_bIsConnected ) + { + m_xFilter_lb->set_sensitive(true); + m_xName_ed->set_sensitive(true); + m_xContainer->set_sensitive(true); + m_xNewFolder->set_sensitive(true); + + if (!m_xName_ed->get_text().isEmpty()) + m_xOk_btn->set_sensitive(true); + else + m_xOk_btn->set_sensitive(false); + } + else + { + m_xFilter_lb->set_sensitive(false); + m_xName_ed->set_sensitive(false); + m_xContainer->set_sensitive(false); + m_xNewFolder->set_sensitive(false); + m_xOk_btn->set_sensitive(false); + } + + m_xPath->EnableFields( true ); + m_xManageServices->set_sensitive(true); +} + +void RemoteFilesDialog::DisableControls() +{ + m_xServices_lb->set_sensitive(false); + m_xFilter_lb->set_sensitive(false); + m_xManageServices->set_sensitive(false); + m_xName_ed->set_sensitive(false); + m_xContainer->set_sensitive(false); + m_xOk_btn->set_sensitive(false); + m_xPath->EnableFields( false ); + + m_xCancel_btn->set_sensitive(true); +} + +void RemoteFilesDialog::SavePassword(const OUString& rURL, const OUString& rUser, + const OUString& rPassword, bool bPersistent) +{ + if( rURL.isEmpty() || rUser.isEmpty() || rPassword.isEmpty() ) + return; + + try + { + if( !bPersistent || + ( m_xMasterPasswd->isPersistentStoringAllowed() + && m_xMasterPasswd->authorizateWithMasterPassword( Reference< XInteractionHandler>() ) ) + ) + { + Reference< XInteractionHandler > xInteractionHandler = + InteractionHandler::createWithParent( m_xContext, nullptr ); + + Sequence<OUString> aPasswd { rPassword }; + + if( bPersistent ) + m_xMasterPasswd->addPersistent( + rURL, rUser, aPasswd, xInteractionHandler ); + else + m_xMasterPasswd->add( rURL, rUser, aPasswd, xInteractionHandler ); + } + } + catch( const Exception& ) + {} +} + +IMPL_LINK_NOARG ( RemoteFilesDialog, IconViewHdl, weld::Button&, void ) +{ + m_xListView_btn->set_active(false); + m_xFileView->SetViewMode( eIcon ); +} + +IMPL_LINK_NOARG ( RemoteFilesDialog, ListViewHdl, weld::Button&, void ) +{ + m_xIconView_btn->set_active(false); + m_xFileView->SetViewMode( eDetailedList ); +} + +void RemoteFilesDialog::AddService() +{ + PlaceEditDialog aDlg(m_xDialog.get()); + aDlg.ShowPasswordControl(); + short aRetCode = aDlg.run(); + + switch( aRetCode ) + { + case RET_OK : + { + ServicePtr newService = aDlg.GetPlace(); + m_aServices.push_back( newService ); + + OUString sPassword = aDlg.GetPassword(); + OUString sUser = aDlg.GetUser(); + if( !sUser.isEmpty() && !sPassword.isEmpty() ) + { + bool bPersistent = aDlg.IsRememberChecked(); + SavePassword( newService->GetUrl(), sUser, sPassword, bPersistent ); + } + + OUString sPrefix = lcl_GetServiceType( newService ); + + if(!sPrefix.isEmpty()) + sPrefix += ": "; + + m_xServices_lb->append_text( sPrefix + newService->GetName() ); + m_xServices_lb->set_active( m_xServices_lb->get_count() - 1 ); + EnableExtraMenuItems(true); + SelectServiceHdl( *m_xServices_lb ); + + m_bIsUpdated = true; + + EnableControls(); + break; + } + case RET_CANCEL : + default : + // Do Nothing + break; + } +} + +IMPL_LINK_NOARG( RemoteFilesDialog, SelectServiceHdl, weld::ComboBox&, void ) +{ + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 ) + { + OUString sURL = m_aServices[nPos]->GetUrl(); + EnableExtraMenuItems(true); + + m_bServiceChanged = true; + OpenURL( sURL ); + } +} + +IMPL_LINK ( RemoteFilesDialog, EditServiceMenuHdl, const OString&, rIdent, void ) +{ + OString sIdent(rIdent); + if( sIdent == "edit_service" && m_xServices_lb->get_count() > 0 ) + { + int nSelected = m_xServices_lb->get_active(); + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 ) + { + PlaceEditDialog aDlg(m_xDialog.get(), m_aServices[nPos]); + short aRetCode = aDlg.run(); + + switch( aRetCode ) + { + case RET_OK : + { + ServicePtr pEditedService = aDlg.GetPlace(); + + m_aServices[nPos] = pEditedService; + m_xServices_lb->remove( nSelected ); + + OUString sPrefix = lcl_GetServiceType( pEditedService ); + + if(!sPrefix.isEmpty()) + sPrefix += ": "; + + m_xServices_lb->insert_text(nSelected, sPrefix + pEditedService->GetName()); + m_xServices_lb->set_active( nSelected ); + + m_bIsUpdated = true; + break; + } + case RET_NO: + sIdent = "delete_service"; + break; + case RET_CANCEL : + default : + // Do Nothing + break; + } + } + } + if( sIdent == "delete_service" && m_xServices_lb->get_count() > 0 ) + { + int nSelected = m_xServices_lb->get_active(); + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 ) + { + OUString sMsg = FpsResId( STR_SVT_DELETESERVICE ); + sMsg = sMsg.replaceFirst( "$servicename$", m_xServices_lb->get_active_text() ); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, sMsg)); + if (xBox->run() == RET_YES) + { + // remove password + try + { + if( m_xMasterPasswd->isPersistentStoringAllowed() ) + { + OUString sUrl( m_aServices[nPos]->GetUrl() ); + + Reference< XInteractionHandler > xInteractionHandler = + InteractionHandler::createWithParent( m_xContext, nullptr ); + + UrlRecord aURLEntries = m_xMasterPasswd->find( sUrl, xInteractionHandler ); + + if( aURLEntries.Url == sUrl && aURLEntries.UserList.hasElements() ) + { + OUString sUserName = aURLEntries.UserList[0].UserName; + + m_xMasterPasswd->removePersistent( sUrl, sUserName ); + } + } + } + catch( const Exception& ) + {} + + m_aServices.erase( m_aServices.begin() + nPos ); + m_xServices_lb->remove( nSelected ); + + m_xServices_lb->set_active(-1); + EnableExtraMenuItems(false); + + m_bIsUpdated = true; + + m_bIsConnected = false; + EnableControls(); + } + } + } + else if( sIdent == "change_password" ) + { + try + { + if( m_xMasterPasswd->isPersistentStoringAllowed() && m_xMasterPasswd->authorizateWithMasterPassword( Reference< XInteractionHandler>() ) ) + { + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 ) + { + OUString sUrl( m_aServices[nPos]->GetUrl() ); + + Reference< XInteractionHandler > xInteractionHandler = + InteractionHandler::createWithParent( m_xContext, nullptr ); + + UrlRecord aURLEntries = m_xMasterPasswd->find( sUrl, xInteractionHandler ); + + if( aURLEntries.Url == sUrl && aURLEntries.UserList.hasElements() ) + { + OUString sUserName = aURLEntries.UserList[0].UserName; + + rtl::Reference<::comphelper::SimplePasswordRequest> pPasswordRequest + = new ::comphelper::SimplePasswordRequest; + + xInteractionHandler->handle( pPasswordRequest ); + + if ( pPasswordRequest->isPassword() ) + { + OUString aNewPass = pPasswordRequest->getPassword(); + Sequence<OUString> aPasswd { aNewPass }; + + m_xMasterPasswd->addPersistent( + sUrl, sUserName, aPasswd, xInteractionHandler ); + } + } + } + } + } + catch( const Exception& ) + {} + } + else if( sIdent == "add_service" ) + AddService(); + + EnableControls(); +} + +IMPL_LINK_NOARG( RemoteFilesDialog, DoubleClickHdl, SvtFileView*, bool ) +{ + SvtContentEntry* pData = m_xFileView->FirstSelected(); + if (pData) + { + if (!pData->mbIsFolder) + OkHdl(*m_xOk_btn); + else + OpenURL(pData->maURL); + } + return true; +} + +IMPL_LINK_NOARG( RemoteFilesDialog, SelectHdl, SvtFileView*, void ) +{ + SvtContentEntry* pData = m_xFileView->FirstSelected(); + if (!pData) + return; + + if( ( pData->mbIsFolder && ( m_eType == REMOTEDLG_TYPE_PATHDLG ) ) + || ( !pData->mbIsFolder && ( m_eType == REMOTEDLG_TYPE_FILEDLG ) ) ) + { + // url must contain user info, because we need this info in recent files entry + // (to fill user field in login box by default) + INetURLObject aURL( pData->maURL ); + INetURLObject aCurrentURL( m_sLastServiceUrl ); + aURL.SetUser( aCurrentURL.GetUser() ); + + m_sPath = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + m_xName_ed->set_text( aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset) ); + } + else + { + if( m_eMode == REMOTEDLG_MODE_OPEN ) + { + m_sPath.clear(); + m_xName_ed->set_text( "" ); + } + } + + EnableControls(); +} + +IMPL_LINK_NOARG(RemoteFilesDialog, FileNameGetFocusHdl, weld::Widget&, void) +{ + m_xFileView->SetNoSelection(); +} + +IMPL_LINK_NOARG(RemoteFilesDialog, FileNameModifyHdl, weld::Entry&, void) +{ + m_xFileView->SetNoSelection(); + if (!m_xOk_btn->get_sensitive()) + EnableControls(); +} + +IMPL_LINK_NOARG( RemoteFilesDialog, SelectFilterHdl, weld::ComboBox&, void ) +{ + int nPos = m_xFilter_lb->get_active(); + + if( nPos != -1 && !m_aFilters[nPos].second.isEmpty() ) + { + m_nCurrentFilter = nPos; + + OUString sCurrentURL = m_xFileView->GetViewURL(); + + if( !sCurrentURL.isEmpty() && m_bIsConnected ) + OpenURL( sCurrentURL ); + } +} + +IMPL_LINK(RemoteFilesDialog, TreeSelectHdl, weld::TreeView&, rBox, void) +{ + OpenURL(rBox.get_selected_id()); + m_xFileView->grab_focus(); +} + +IMPL_LINK(RemoteFilesDialog, SelectBreadcrumbHdl, Breadcrumb*, pPtr, bool) +{ + OpenURL( pPtr->GetHdlURL() ); + return true; +} + +IMPL_LINK_NOARG ( RemoteFilesDialog, NewFolderHdl, weld::Button&, void ) +{ + m_xFileView->EndInplaceEditing(); + + // will be bound after InteractionHandler is enabled + SmartContent aContent; + aContent.enableDefaultInteractionHandler(); + // now it can be bound + aContent.bindTo( m_xFileView->GetViewURL() ); + if( !aContent.canCreateFolder() ) + return; + + OUString aTitle; + aContent.getTitle( aTitle ); + QueryFolderNameDialog aDlg(m_xDialog.get(), aTitle, FpsResId(STR_SVT_NEW_FOLDER)); + bool bHandled = false; + + while( !bHandled ) + { + if (aDlg.run() == RET_OK) + { + OUString aUrl = aContent.createFolder(aDlg.GetName()); + if( !aUrl.isEmpty() ) + { + m_xFileView->CreatedFolder(aUrl, aDlg.GetName()); + bHandled = true; + } + } + else + bHandled = true; + } +} + +IMPL_LINK_NOARG ( RemoteFilesDialog, OkHdl, weld::Button&, void ) +{ + OUString sUserSelectedPath; + + // check if file/path exists + OUString sCurrentPath = m_xFileView->GetViewURL(); + OUString sSelectedItem = m_xFileView->GetCurrentURL(); + OUString sUserTypedName = m_xName_ed->get_text(); + OUString sFileName; + // auto extension + if( m_eMode == REMOTEDLG_MODE_SAVE ) + sFileName = AddFileExtension(sUserTypedName); + else + sFileName = sUserTypedName; + + bool bFileDlg = ( m_eType == REMOTEDLG_TYPE_FILEDLG ); + bool bSelected = ( m_xFileView->GetSelectionCount() > 0 ); + + if( !sCurrentPath.endsWith("/") ) + sCurrentPath += "/"; + + if( !bSelected ) + { + m_sPath = sCurrentPath + INetURLObject::encode(sFileName, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All); + sUserSelectedPath = sCurrentPath + INetURLObject::encode(sUserTypedName, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All); + } + else + { + if( m_eType == REMOTEDLG_TYPE_PATHDLG ) + m_sPath = sCurrentPath; + else + m_sPath = sSelectedItem; + + // url must contain user info, because we need this info in recent files entry + // (to fill user field in login box by default) + INetURLObject aURL( m_sPath ); + INetURLObject aCurrentURL( m_sLastServiceUrl ); + aURL.SetUser( aCurrentURL.GetUser() ); + + m_sPath = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + sUserSelectedPath = m_sPath; + } + + bool bExists = false; + + if( bFileDlg ) + bExists = ContentIsDocument( m_sPath ); + else + bExists = ContentIsFolder( m_sPath ); + + if( bExists ) + { + if( m_eMode == REMOTEDLG_MODE_SAVE ) + { + OUString sMsg = FpsResId( STR_SVT_ALREADYEXISTOVERWRITE ); + sMsg = sMsg.replaceFirst("$filename$", sFileName); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, sMsg)); + if (xBox->run() != RET_YES) + return; + } + } + else + { + if (ContentIsFolder(sUserSelectedPath)) + { + OpenURL(sUserSelectedPath); + + if (!bSelected) + m_xName_ed->grab_focus(); + + return; + } + + if( m_eMode == REMOTEDLG_MODE_OPEN ) + return; + } + + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG ( RemoteFilesDialog, CancelHdl, weld::Button&, void ) +{ + if( m_pCurrentAsyncAction.is() ) + { + m_pCurrentAsyncAction->cancel(); + onAsyncOperationFinished(); + } + else + { + m_xDialog->response(RET_CANCEL); + } +} + +// SvtFileDialog_Base +SvtFileView* RemoteFilesDialog::GetView() +{ + return m_xFileView.get(); +} + +void RemoteFilesDialog::SetHasFilename( bool ) +{ +} + +void RemoteFilesDialog::SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) +{ + m_aDenyList = rDenyList; + m_xTreeView->SetDenyList( rDenyList ); +} + +const css::uno::Sequence< OUString >& RemoteFilesDialog::GetDenyList() const +{ + return m_aDenyList; +} + +void RemoteFilesDialog::SetStandardDir( const OUString& rStdDir ) +{ + m_sStdDir = rStdDir; +} + +const OUString& RemoteFilesDialog::GetStandardDir() const +{ + return m_sStdDir; +} + +void RemoteFilesDialog::SetPath( const OUString& rNewURL ) +{ + m_sPath = rNewURL; + + if( m_eMode == REMOTEDLG_MODE_SAVE ) + { + INetURLObject aUrl( m_sPath ); + OUString sFileName = aUrl.GetLastName( INetURLObject::DecodeMechanism::WithCharset ); + + m_xName_ed->set_text( sFileName ); + } +} + +OUString RemoteFilesDialog::getCurrentFileText() const +{ + OUString sReturn; + if( m_xName_ed ) + sReturn = m_xName_ed->get_text(); + return sReturn; +} + +void RemoteFilesDialog::setCurrentFileText( const OUString& rText, bool bSelectAll ) +{ + if (m_xName_ed) + { + m_xName_ed->set_text(rText); + if( bSelectAll ) + m_xName_ed->select_region(0, -1); + } +} + +void RemoteFilesDialog::AddFilterGroup( + const OUString& rFilter, + const css::uno::Sequence< css::beans::StringPair >& rFilters ) +{ + AddFilter( rFilter, OUString() ); + const StringPair* pSubFilters = rFilters.getConstArray(); + const StringPair* pSubFiltersEnd = pSubFilters + rFilters.getLength(); + for ( ; pSubFilters != pSubFiltersEnd; ++pSubFilters ) + AddFilter( pSubFilters->First, pSubFilters->Second ); +} + +OUString RemoteFilesDialog::GetCurFilter() const +{ + OUString sFilter; + + if (m_nCurrentFilter != -1) + { + sFilter = m_aFilters[m_nCurrentFilter].first; + } + + return sFilter; +} + +OUString RemoteFilesDialog::getCurFilter( ) const +{ + return GetCurFilter(); +} + +void RemoteFilesDialog::SetCurFilter( const OUString& rFilter ) +{ + DBG_ASSERT( !m_bIsInExecute, "SvtFileDialog::SetCurFilter: currently executing!" ); + + // look for corresponding filter + sal_uInt16 nPos = m_aFilters.size(); + + while ( nPos-- ) + { + if ( m_aFilters[nPos].first == rFilter ) + { + m_nCurrentFilter = nPos; + m_xFilter_lb->set_active( m_nCurrentFilter ); + break; + } + } +} + +void RemoteFilesDialog::FilterSelect() +{ +} + +void RemoteFilesDialog::SetFileCallback( ::svt::IFilePickerListener * ) +{ +} + +void RemoteFilesDialog::onAsyncOperationStarted() +{ + DisableControls(); +} + +void RemoteFilesDialog::onAsyncOperationFinished() +{ + m_pCurrentAsyncAction = nullptr; + EnableControls(); +} + +void RemoteFilesDialog::UpdateControls( const OUString& rURL ) +{ + int nPos = GetSelectedServicePos(); + + if( nPos >= 0 && m_bServiceChanged && rURL == m_aServices[nPos]->GetUrl() ) + { + OUString sURL = m_aServices[nPos]->GetUrl(); + + m_xPath->SetRootName( m_sRootLabel ); + m_xTreeView->clear(); + + m_xTreeView->InsertRootEntry(rURL, m_sRootLabel); + + m_xName_ed->grab_focus(); + + m_sLastServiceUrl = sURL; + + m_bServiceChanged = false; + } + + m_xPath->SetURL( rURL ); + + m_xTreeView->connect_changed(Link<weld::TreeView&,void>()); + + // read cached data for this url and fill the tree + const ::std::vector< SvtContentEntry >& rFolders = m_xFileView->GetContent(); + ::std::vector< std::pair< OUString, OUString > > aFolders; + + m_xName_ed->ClearEntries(); + + for(const auto & rFolder : rFolders) + { + //WebDAV folders path ends in '/', so strip it + OUString aFolderName = rFolder.maURL; + if( rFolder.mbIsFolder && ( ( aFolderName.lastIndexOf( '/' ) + 1 ) == aFolderName.getLength() ) ) + aFolderName = aFolderName.copy( 0, aFolderName.getLength() - 1 ); + + int nTitleStart = aFolderName.lastIndexOf( '/' ); + if( nTitleStart != -1 ) + { + OUString sTitle( INetURLObject::decode( + aFolderName.subView( nTitleStart + 1 ), + INetURLObject::DecodeMechanism::WithCharset ) ); + + if( rFolder.mbIsFolder ) + { + aFolders.emplace_back( sTitle, aFolderName ); + } + + // add entries to the autocompletion mechanism + m_xName_ed->AddEntry( sTitle ); + } + } + + m_xTreeView->FillTreeEntry( rURL, aFolders ); + + m_xTreeView->connect_changed( LINK( this, RemoteFilesDialog, TreeSelectHdl ) ); + + m_bIsConnected = true; + EnableControls(); +} + +void RemoteFilesDialog::EnableAutocompletion( bool ) +{ + // This dialog contains Breadcrumb, not Edit +} + +const OUString& RemoteFilesDialog::GetPath() +{ + return m_sPath; +} + +std::vector<OUString> RemoteFilesDialog::GetPathList() const +{ + std::vector<OUString> aList; + + m_xFileView->selected_foreach([this, &aList](weld::TreeIter& rCurEntry){ + // url must contain user info, because we need this info in recent files entry + // (to fill user field in login box by default) + INetURLObject aURL(m_xFileView->GetURL(rCurEntry)); + INetURLObject aCurrentURL( m_sLastServiceUrl ); + aURL.SetUser( aCurrentURL.GetUser() ); + + aList.push_back( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + return false; + }); + + if( aList.empty() && !m_sPath.isEmpty() ) + aList.push_back( m_sPath ); + + return aList; +} + +bool RemoteFilesDialog::ContentIsFolder( const OUString& rURL ) +{ + try + { + ::ucbhelper::Content content(rURL, + ::utl::UCBContentHelper::getDefaultCommandEnvironment(), + m_xContext); + return content.isFolder(); + } + catch (css::uno::Exception const&) + { + return false; + } +} + +bool RemoteFilesDialog::ContentIsDocument( const OUString& rURL ) +{ + try + { + ::ucbhelper::Content content(rURL, + ::utl::UCBContentHelper::getDefaultCommandEnvironment(), + m_xContext); + return content.isDocument(); + } + catch (css::uno::Exception const&) + { + return false; + } +} + +sal_Int32 RemoteFilesDialog::getAvailableWidth() +{ + // This dialog doesn't contain preview + return 0; +} + +sal_Int32 RemoteFilesDialog::getAvailableHeight() +{ + // This dialog doesn't contain preview + return 0; +} + +void RemoteFilesDialog::setImage( const css::uno::Any& ) +{ + // This dialog doesn't contain preview +} + +bool RemoteFilesDialog::getShowState() +{ + // This dialog doesn't contain preview + return false; +} + +weld::Widget* RemoteFilesDialog::getControl( sal_Int16, bool) const +{ + return nullptr; +} + +void RemoteFilesDialog::enableControl( sal_Int16, bool ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/RemoteFilesDialog.hxx b/fpicker/source/office/RemoteFilesDialog.hxx new file mode 100644 index 000000000..3dfb4e1dc --- /dev/null +++ b/fpicker/source/office/RemoteFilesDialog.hxx @@ -0,0 +1,186 @@ +/* -*- 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/. + */ + +#pragma once + +#include "autocmpledit.hxx" +#include <svtools/place.hxx> + +#include <unotools/viewoptions.hxx> + +#include <vcl/svapp.hxx> + +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/task/XPasswordContainer2.hpp> + +#include <vector> + +#include "asyncfilepicker.hxx" +#include "fpdialogbase.hxx" +#include "breadcrumb.hxx" +#include "fileview.hxx" +#include "foldertree.hxx" + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ui::dialogs; + +enum SvtRemoteDlgMode +{ + REMOTEDLG_MODE_OPEN = 0, + REMOTEDLG_MODE_SAVE = 1 +}; + +enum SvtRemoteDlgType +{ + REMOTEDLG_TYPE_FILEDLG = 0, + REMOTEDLG_TYPE_PATHDLG = 1 +}; + +typedef std::shared_ptr< Place > ServicePtr; + +class RemoteFilesDialog : public SvtFileDialog_Base +{ +public: + RemoteFilesDialog( weld::Window* pParent, PickerFlags nBits ); + virtual ~RemoteFilesDialog() override; + + virtual short run() override; + + // SvtFileDialog_Base + + virtual SvtFileView* GetView() override; + + virtual void SetHasFilename( bool ) override; + virtual void SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) override; + virtual const css::uno::Sequence< OUString >& GetDenyList() const override; + virtual void SetStandardDir( const OUString& rStdDir ) override; + virtual const OUString& GetStandardDir() const override; + virtual void SetPath( const OUString& rNewURL ) override; + virtual const OUString& GetPath() override; + virtual std::vector<OUString> GetPathList() const override; + virtual bool ContentIsFolder( const OUString& rURL ) override; + bool ContentIsDocument(const OUString& rURL); + + virtual OUString getCurrentFileText() const override; + virtual void setCurrentFileText( const OUString& rText, bool bSelectAll = false ) override; + + virtual void AddFilter( const OUString& rFilter, const OUString& rType ) override; + virtual void AddFilterGroup( const OUString& _rFilter, + const css::uno::Sequence< css::beans::StringPair >& rFilters ) override; + virtual OUString GetCurFilter() const override; + virtual void SetCurFilter( const OUString& rFilter ) override; + virtual void FilterSelect() override; + + virtual void SetFileCallback( ::svt::IFilePickerListener *pNotifier ) override; + virtual void onAsyncOperationStarted() override; + virtual void onAsyncOperationFinished() override; + virtual void UpdateControls( const OUString& rURL ) override; + + virtual void EnableAutocompletion( bool = true) override; + + virtual sal_Int32 getAvailableWidth() override; + virtual sal_Int32 getAvailableHeight() override; + + virtual void setImage( const css::uno::Any& rImage ) override; + + virtual bool getShowState() override; + + virtual weld::Widget* getControl( sal_Int16 nControlId, bool bLabelControl = false ) const override; + virtual void enableControl( sal_Int16 nControlId, bool bEnable ) override; + virtual OUString getCurFilter( ) const override; + +private: + Reference< XComponentContext > m_xContext; + Reference< XPasswordContainer2 > m_xMasterPasswd; + + SvtRemoteDlgMode m_eMode; + SvtRemoteDlgType m_eType; + bool m_bIsUpdated; + bool m_bIsConnected; + bool m_bServiceChanged; + + OUString m_sIniKey; + + bool m_bIsInExecute; + + OUString m_sPath; + OUString m_sStdDir; + OUString m_sRootLabel; + OUString m_sLastServiceUrl; + int m_nCurrentFilter; + + ::rtl::Reference< ::svt::AsyncPickerAction > m_pCurrentAsyncAction; + + css::uno::Sequence< OUString > m_aDenyList; + + std::unique_ptr<weld::Button> m_xOk_btn; + std::unique_ptr<weld::Button> m_xCancel_btn; + std::unique_ptr<weld::MenuButton> m_xManageServices; + std::unique_ptr<weld::ComboBox> m_xServices_lb; + std::unique_ptr<weld::Container> m_xPathContainer; + std::unique_ptr<Breadcrumb> m_xPath; + std::unique_ptr<weld::Button> m_xNewFolder; + std::unique_ptr<weld::ToggleButton> m_xListView_btn; + std::unique_ptr<weld::ToggleButton> m_xIconView_btn; + std::unique_ptr<FolderTree> m_xTreeView; + std::unique_ptr<SvtFileView> m_xFileView; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::ComboBox> m_xFilter_lb; + std::unique_ptr<AutocompleteEdit> m_xName_ed; + + std::vector< ServicePtr > m_aServices; + std::vector< std::pair< OUString, OUString > > m_aFilters; + + void InitSize(); + + void FillServicesListbox(); + + /* If failure returns < 0 */ + int GetSelectedServicePos(); + + void OpenURL( OUString const & sURL ); + + OUString AddFileExtension(const OUString& rFileName); + + void EnableExtraMenuItems(bool bEnable); + void EnableControls(); + void DisableControls(); + + void SavePassword(const OUString& rURL, const OUString& rUser, + const OUString& rPassword, bool bPersistent); + + void AddService(); + + DECL_LINK ( SelectServiceHdl, weld::ComboBox&, void ); + DECL_LINK ( EditServiceMenuHdl, const OString&, void ); + + DECL_LINK( DoubleClickHdl, SvtFileView*, bool ); + DECL_LINK( SelectHdl, SvtFileView*, void ); + + DECL_LINK( FileNameGetFocusHdl, weld::Widget&, void ); + DECL_LINK( FileNameModifyHdl, weld::Entry&, void ); + + DECL_LINK( SelectFilterHdl, weld::ComboBox&, void ); + + DECL_LINK( TreeSelectHdl, weld::TreeView&, void ); + + DECL_LINK( SelectBreadcrumbHdl, Breadcrumb*, bool ); + + DECL_LINK( NewFolderHdl, weld::Button&, void ); + DECL_LINK( IconViewHdl, weld::Button&, void ); + DECL_LINK( ListViewHdl, weld::Button&, void ); + + DECL_LINK( OkHdl, weld::Button&, void ); + DECL_LINK( CancelHdl, weld::Button&, void ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/asyncfilepicker.cxx b/fpicker/source/office/asyncfilepicker.cxx new file mode 100644 index 000000000..349d586b8 --- /dev/null +++ b/fpicker/source/office/asyncfilepicker.cxx @@ -0,0 +1,183 @@ +/* -*- 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 "asyncfilepicker.hxx" +#include "fileview.hxx" +#include "iodlg.hxx" +#include <tools/debug.hxx> +#include <osl/diagnose.h> + +#include <memory> + + +namespace svt +{ + AsyncPickerAction::AsyncPickerAction( SvtFileDialog_Base* _pDialog, SvtFileView* _pView, const Action _eAction ) + :m_eAction ( _eAction ) + ,m_pView ( _pView ) + ,m_pDialog ( _pDialog ) + ,m_bRunning ( false ) + { + assert( m_pDialog && "AsyncPickerAction::AsyncPickerAction: invalid dialog!" ); + assert( m_pView && "AsyncPickerAction::AsyncPickerAction: invalid view!" ); + } + + + AsyncPickerAction::~AsyncPickerAction() + { + } + + + void AsyncPickerAction::cancel() + { + DBG_TESTSOLARMUTEX(); + // if this asserts, we'd need to have an own mutex per instance + + OSL_ENSURE( m_bRunning, "AsyncPickerAction::cancel: not running" ); + if ( m_pView ) + m_pView->CancelRunningAsyncAction(); + } + + + void AsyncPickerAction::execute( + const OUString& _rURL, + const OUString& _rFilter, + sal_Int32 _nMinTimeout, + sal_Int32 _nMaxTimeout, + const css::uno::Sequence< OUString >& rDenyList ) + { + DBG_TESTSOLARMUTEX(); + // if this asserts, we'd need to have an own mutex per instance + + sal_Int32 nMinTimeout = _nMinTimeout; + sal_Int32 nMaxTimeout = _nMaxTimeout; + // normalizations + if ( nMinTimeout < 0 ) + // if negative, this is considered as "do it synchronously" + nMinTimeout = 0; + else if ( nMinTimeout < 1000 ) + nMinTimeout = 1000; + if ( nMaxTimeout <= nMinTimeout ) + nMaxTimeout = nMinTimeout + 30000; + + std::unique_ptr< FileViewAsyncAction > pActionDescriptor; + if ( nMinTimeout ) + { + pActionDescriptor.reset( new FileViewAsyncAction ); + pActionDescriptor->nMinTimeout = nMinTimeout; + pActionDescriptor->nMaxTimeout = nMaxTimeout; + pActionDescriptor->aFinishHandler = LINK( this, AsyncPickerAction, OnActionDone ); + } + + FileViewResult eResult = eFailure; + m_sURL = _rURL; + switch ( m_eAction ) + { + case ePrevLevel: + eResult = m_pView->PreviousLevel( pActionDescriptor.get() ); + break; + + case eOpenURL: + eResult = m_pView->Initialize( _rURL, _rFilter, pActionDescriptor.get(), rDenyList ); + break; + + case eExecuteFilter: + // preserve the filename (FS: why?) + m_sFileName = m_pDialog->getCurrentFileText(); + // execute the new filter + eResult = m_pView->ExecuteFilter( _rFilter, pActionDescriptor.get() ); + break; + + default: + OSL_FAIL( "AsyncPickerAction::execute: unknown action!" ); + break; + } + + acquire(); + if ( ( eResult == eSuccess ) || ( eResult == eFailure ) ) + { + // the handler is only called if the action could not be finished within + // the given minimum time period. In case of success, we need to call it + // explicitly + OnActionDone( reinterpret_cast< void* >( eResult ) ); + } + else if ( eResult == eStillRunning ) + { + m_bRunning = true; + m_pDialog->onAsyncOperationStarted(); + } + } + + + IMPL_LINK( AsyncPickerAction, OnActionDone, void*, pEmptyArg, void ) + { + DBG_TESTSOLARMUTEX(); + // if this asserts, we'd need to have an own mutex per instance + + FileViewResult eResult = static_cast< FileViewResult >( reinterpret_cast< sal_IntPtr >( pEmptyArg ) ); + OSL_ENSURE( eStillRunning != eResult, "AsyncPickerAction::OnActionDone: invalid result!" ); + + // release once (since we acquired in |execute|), but keep alive until the + // end of the method + ::rtl::Reference< AsyncPickerAction > xKeepAlive( this ); + release(); + + m_pDialog->onAsyncOperationFinished(); + m_bRunning = true; + + if ( eFailure == eResult ) + // TODO: do we need some kind of cleanup here? + return; + + if ( eTimeout == eResult ) + { + SvtFileDialog::displayIOException( m_sURL, css::ucb::IOErrorCode_CANT_READ ); + return; + } + + OSL_ENSURE( eSuccess == eResult, "AsyncPickerAction::OnActionDone: what else valid results are there?" ); + + switch ( m_eAction ) + { + case ePrevLevel: + case eOpenURL: + m_pDialog->UpdateControls( m_pView->GetViewURL() ); + break; + + case eExecuteFilter: + // restore the filename + m_pView->SetNoSelection(); + m_pDialog->setCurrentFileText( m_sFileName, true ); + + // notify listeners + m_pDialog->FilterSelect(); + break; + + default: + OSL_FAIL( "AsyncPickerAction::OnActionDone: unknown action!" ); + break; + } + } + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/asyncfilepicker.hxx b/fpicker/source/office/asyncfilepicker.hxx new file mode 100644 index 000000000..e2bac1208 --- /dev/null +++ b/fpicker/source/office/asyncfilepicker.hxx @@ -0,0 +1,93 @@ +/* -*- 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 <tools/link.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <salhelper/simplereferenceobject.hxx> + +class SvtFileView; +class SvtFileDialog_Base; + +namespace svt +{ + + + //= AsyncPickerAction + + class AsyncPickerAction : public salhelper::SimpleReferenceObject + { + public: + enum Action + { + ePrevLevel, + eOpenURL, + eExecuteFilter + }; + + private: + Action m_eAction; + SvtFileView* m_pView; + SvtFileDialog_Base* m_pDialog; + OUString m_sURL; + OUString m_sFileName; + bool m_bRunning; + + public: + AsyncPickerAction( SvtFileDialog_Base* _pDialog, SvtFileView* _pView, const Action _eAction ); + + /** executes the action + + @param _nMinTimeout + the minimum timeout to wait, in milliseconds. If negative, the action will we done + synchronously. If between 0 and 999, it will be corrected to 1000, means the + smallest valid value is 1000 (which equals one second). + @param _nMaxTimeout + The maximum time to wait for a result, in milliseconds. If there's no result of + the action within the given time frame, the action will be cancelled. + If smaller than or equal to <arg>_nMinTimeout</arg>, it will be corrected to + <arg>_nMinTimeout</arg> + 30000. + */ + void execute( + const OUString& _rURL, + const OUString& _rFilter, + sal_Int32 _nMinTimeout, + sal_Int32 _nMaxTimeout, + const css::uno::Sequence< OUString >& rDenyList ); + + /// cancels the running action + void cancel(); + + protected: + virtual ~AsyncPickerAction() override; + + private: + DECL_LINK( OnActionDone, void*, void ); + + AsyncPickerAction( const AsyncPickerAction& ) = delete; + AsyncPickerAction& operator=( const AsyncPickerAction& ) = delete; + }; + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/autocmpledit.cxx b/fpicker/source/office/autocmpledit.cxx new file mode 100644 index 000000000..89a2d0b0c --- /dev/null +++ b/fpicker/source/office/autocmpledit.cxx @@ -0,0 +1,104 @@ +/* -*- 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/. + */ + +#include <vcl/event.hxx> +#include "autocmpledit.hxx" + +AutocompleteEdit::AutocompleteEdit(std::unique_ptr<weld::Entry> xEntry) + : m_xEntry(std::move(xEntry)) + , m_aChangedIdle("fpicker::AutocompleteEdit m_aChangedIdle") + , m_nLastCharCode(0) +{ + m_xEntry->connect_changed(LINK(this, AutocompleteEdit, ChangedHdl)); + m_xEntry->connect_key_press(LINK(this, AutocompleteEdit, KeyInputHdl)); + + m_aChangedIdle.SetInvokeHandler(LINK(this, AutocompleteEdit, TryAutoComplete)); +} + +IMPL_LINK(AutocompleteEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + m_nLastCharCode = rKEvt.GetKeyCode().GetCode(); + return false; +} + +IMPL_LINK_NOARG(AutocompleteEdit, ChangedHdl, weld::Entry&, void) +{ + m_aChangeHdl.Call(*m_xEntry); + + switch (m_nLastCharCode) + { + case css::awt::Key::DELETE_WORD_BACKWARD: + case css::awt::Key::DELETE_WORD_FORWARD: + case css::awt::Key::DELETE_TO_BEGIN_OF_LINE: + case css::awt::Key::DELETE_TO_END_OF_LINE: + case KEY_BACKSPACE: + case KEY_DELETE: + m_aChangedIdle.Stop(); + break; + default: + m_aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set + break; + } +} + +void AutocompleteEdit::AddEntry( const OUString& rEntry ) +{ + m_aEntries.push_back( rEntry ); +} + +void AutocompleteEdit::ClearEntries() +{ + m_aEntries.clear(); + m_aMatching.clear(); +} + +IMPL_LINK_NOARG(AutocompleteEdit, TryAutoComplete, Timer *, void) +{ + OUString aCurText = m_xEntry->get_text(); + + int nStartPos, nEndPos; + m_xEntry->get_selection_bounds(nStartPos, nEndPos); + if (std::max(nStartPos, nEndPos) != aCurText.getLength()) + return; + + auto nLen = std::min(nStartPos, nEndPos); + aCurText = aCurText.copy( 0, nLen ); + if( aCurText.isEmpty() ) + return; + + if( !m_aEntries.empty() ) + { + if( Match( aCurText ) ) + { + m_xEntry->set_text(m_aMatching[0]); + auto nNewLen = m_aMatching[0].getLength(); + m_xEntry->select_region(nLen, nNewLen); + } + } +} + +bool AutocompleteEdit::Match( std::u16string_view rText ) +{ + bool bRet = false; + + m_aMatching.clear(); + + for(const OUString & rEntry : m_aEntries) + { + if( rEntry.startsWithIgnoreAsciiCase( rText ) ) + { + m_aMatching.push_back( rEntry ); + bRet = true; + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/autocmpledit.hxx b/fpicker/source/office/autocmpledit.hxx new file mode 100644 index 000000000..3eb79eb14 --- /dev/null +++ b/fpicker/source/office/autocmpledit.hxx @@ -0,0 +1,53 @@ +/* -*- 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/. + */ + +#pragma once + +#include <vcl/idle.hxx> +#include <vcl/weld.hxx> +#include <vector> + +class AutocompleteEdit +{ +private: + std::unique_ptr<weld::Entry> m_xEntry; + + std::vector<OUString> m_aEntries; + std::vector<OUString> m_aMatching; + Idle m_aChangedIdle; + Link<weld::Entry&, void> m_aChangeHdl; + sal_uInt16 m_nLastCharCode; + + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + DECL_LINK(ChangedHdl, weld::Entry&, void); + DECL_LINK(TryAutoComplete, Timer*, void); + + bool Match(std::u16string_view rText); + +public: + AutocompleteEdit(std::unique_ptr<weld::Entry> xEntry); + + void show() { m_xEntry->show(); } + void set_sensitive(bool bSensitive) { m_xEntry->set_sensitive(bSensitive); } + OUString get_text() const { return m_xEntry->get_text(); } + void set_text(const OUString& rText) { m_xEntry->set_text(rText); } + void grab_focus() { m_xEntry->grab_focus(); } + void select_region(int nStartPos, int nEndPos) { m_xEntry->select_region(nStartPos, nEndPos); } + + void connect_changed(const Link<weld::Entry&, void>& rLink) { m_aChangeHdl = rLink; } + void connect_focus_in(const Link<weld::Widget&, void>& rLink) + { + m_xEntry->connect_focus_in(rLink); + } + + void AddEntry(const OUString& rEntry); + void ClearEntries(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/breadcrumb.cxx b/fpicker/source/office/breadcrumb.cxx new file mode 100644 index 000000000..0b27367d8 --- /dev/null +++ b/fpicker/source/office/breadcrumb.cxx @@ -0,0 +1,246 @@ +/* -*- 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/. + */ + +#include <tools/urlobj.hxx> +#include <vcl/svapp.hxx> +#include "breadcrumb.hxx" + +Breadcrumb::Breadcrumb(weld::Container* pParent) + : m_pParent(pParent) + , m_nMaxWidth(m_pParent->get_preferred_size().Width()) +{ + m_pParent->connect_size_allocate(LINK(this, Breadcrumb, SizeAllocHdl)); + m_eMode = SvtBreadcrumbMode::ONLY_CURRENT_PATH; + appendField(); // root +} + +IMPL_LINK(Breadcrumb, SizeAllocHdl, const Size&, rSize, void) +{ + m_nMaxWidth = rSize.Width(); +} + +Breadcrumb::~Breadcrumb() +{ + m_pParent->connect_size_allocate(Link<const Size&, void>()); +} + +void Breadcrumb::EnableFields( bool bEnable ) +{ + if( bEnable ) + { + INetURLObject aURL( m_aCurrentURL ); + int nSegments = aURL.getSegmentCount(); + m_aSegments[nSegments]->m_xLink->set_sensitive(false); + } +} + +void Breadcrumb::connect_clicked( const Link<Breadcrumb*,bool>& rLink ) +{ + m_aClickHdl = rLink; +} + +const OUString& Breadcrumb::GetHdlURL() const +{ + return m_sClickedURL; +} + +void Breadcrumb::SetRootName( const OUString& rURL ) +{ + m_sRootName = rURL; + + // we changed root - clear all fields + for (size_t i = 1; i < m_aSegments.size(); ++i) + { + m_aSegments[i]->m_xLink->set_label(""); + + m_aSegments[i]->m_xLink->hide(); + m_aSegments[i]->m_xSeparator->hide(); + m_aSegments[i]->m_xLink->set_sensitive(true); + } +} + +void Breadcrumb::SetURL( const OUString& rURL ) +{ + m_aCurrentURL = rURL; + INetURLObject aURL(rURL); + aURL.setFinalSlash(); + + bool bClear = m_eMode == SvtBreadcrumbMode::ONLY_CURRENT_PATH; + + int nSegments = aURL.getSegmentCount(); + + size_t nVecSizeRequired = nSegments + 1; + + while (m_aSegments.size() < nVecSizeRequired) + appendField(); + + // fill the fields under root + for (int i = nSegments; i; --i) + { + OUString sLabel = aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); + OUString sLink = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + + if (m_eMode == SvtBreadcrumbMode::ALL_VISITED) + { + if( m_aSegments[i]->m_xLink->get_label() != sLabel ) + bClear = true; + } + + m_aSegments[i]->m_xLink->hide(); + m_aSegments[i]->m_xLink->set_label(sLabel); + m_aSegments[i]->m_xLink->set_sensitive(true); + m_aSegments[i]->m_xLink->set_uri(sLink); + m_aUris[m_aSegments[i]->m_xLink.get()] = sLink; + + m_aSegments[i]->m_xSeparator->hide(); + + aURL.removeSegment(); + } + + OUString sRootPath = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset); + + // root field + m_aSegments[0]->m_xLink->set_label( m_sRootName ); + m_aSegments[0]->m_xLink->set_sensitive(true); + m_aSegments[0]->m_xLink->set_uri(sRootPath); + m_aUris[m_aSegments[0]->m_xLink.get()] = sRootPath; + + // clear unused fields + for (size_t i = nSegments + 1; i < m_aSegments.size(); i++ ) + { + if( bClear ) + m_aSegments[i]->m_xLink->set_label( "" ); + + m_aSegments[i]->m_xLink->hide(); + m_aSegments[i]->m_xSeparator->hide(); + m_aSegments[i]->m_xLink->set_sensitive(true); + } + + // show fields + unsigned int nSeparatorWidth = m_aSegments[0]->m_xSeparator->get_preferred_size().Width(); + unsigned int nCurrentWidth = 0; + unsigned int nLastVisible = nSegments; + + bool bRight = ( m_eMode == SvtBreadcrumbMode::ALL_VISITED ); + bool bLeft = true; + + int i = 0; + + while( bLeft || bRight ) + { + if( nSegments - i == -1 ) + bLeft = false; + + if( bLeft ) + { + unsigned int nIndex = nSegments - i; + + if( showField( nIndex, m_nMaxWidth - nCurrentWidth ) ) + { + nCurrentWidth += m_aSegments[nIndex]->m_xLink->get_preferred_size().Width() + + nSeparatorWidth + 2*SPACING; + } + else + { + // label is too long + if( nSegments != 0 ) + { + m_aSegments[0]->m_xLink->set_label("..."); + m_aSegments[0]->m_xLink->set_sensitive(false); + } + bLeft = false; + } + } + + if( nSegments + i == static_cast<int>(m_aSegments.size()) ) + bRight = false; + + if( i != 0 && bRight ) + { + unsigned int nIndex = nSegments + i; + + if( m_aSegments[nIndex]->m_xLink->get_label().isEmpty() ) + { + bRight = false; + } + else if( showField( nIndex, m_nMaxWidth - nCurrentWidth ) ) + { + nCurrentWidth += m_aSegments[nIndex]->m_xLink->get_preferred_size().Width() + + nSeparatorWidth + 3*SPACING; + nLastVisible = nIndex; + } + else + { + bRight = false; + } + } + + i++; + } + + // current dir should be inactive + m_aSegments[nSegments]->m_xLink->set_sensitive(false); + + // hide last separator + m_aSegments[nLastVisible]->m_xSeparator->hide(); +} + +void Breadcrumb::SetMode( SvtBreadcrumbMode eMode ) +{ + m_eMode = eMode; +} + +void Breadcrumb::appendField() +{ + m_aSegments.emplace_back(std::make_unique<BreadcrumbPath>(m_pParent)); + size_t nIndex = m_aSegments.size() - 1; + m_aSegments[nIndex]->m_xLink->hide(); + m_aSegments[nIndex]->m_xLink->connect_activate_link(LINK(this, Breadcrumb, ClickLinkHdl)); + m_aSegments[nIndex]->m_xSeparator->set_label( ">" ); + m_aSegments[nIndex]->m_xSeparator->hide(); +} + +bool Breadcrumb::showField( unsigned int nIndex, unsigned int nWidthMax ) +{ + m_aSegments[nIndex]->m_xLink->show(); + m_aSegments[nIndex]->m_xSeparator->show(); + + unsigned int nSeparatorWidth = m_aSegments[0]->m_xSeparator->get_preferred_size().Width(); + unsigned int nWidth = m_aSegments[nIndex]->m_xLink->get_preferred_size().Width() + + nSeparatorWidth + 3*SPACING; + + if( nWidth > nWidthMax ) + { + if( nIndex != 0 ) + { + m_aSegments[nIndex]->m_xLink->hide(); + m_aSegments[nIndex]->m_xSeparator->hide(); + } + + return false; + } + + return true; +} + +IMPL_LINK(Breadcrumb, ClickLinkHdl, weld::LinkButton&, rLink, bool) +{ + m_sClickedURL = m_aUris[&rLink]; + return m_aClickHdl.Call(this); +} + +BreadcrumbPath::BreadcrumbPath(weld::Container* pContainer) + : m_xBuilder(Application::CreateBuilder(pContainer, "fps/ui/breadcrumb.ui")) + , m_xContainer(m_xBuilder->weld_container("container")) + , m_xLink(m_xBuilder->weld_link_button("link")) + , m_xSeparator(m_xBuilder->weld_label("label")) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/breadcrumb.hxx b/fpicker/source/office/breadcrumb.hxx new file mode 100644 index 000000000..5f476010c --- /dev/null +++ b/fpicker/source/office/breadcrumb.hxx @@ -0,0 +1,70 @@ +/* -*- 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/. + */ + +#pragma once + +#include <vcl/weld.hxx> +#include <map> +#include <vector> + +#define SPACING 6 + +enum SvtBreadcrumbMode +{ + ONLY_CURRENT_PATH = 0, + ALL_VISITED = 1 +}; + +struct BreadcrumbPath +{ + BreadcrumbPath(weld::Container* pParent); + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::LinkButton> m_xLink; + std::unique_ptr<weld::Label> m_xSeparator; +}; + +class Breadcrumb +{ +private: + weld::Container* m_pParent; + int m_nMaxWidth; + + std::vector<std::unique_ptr<BreadcrumbPath>> m_aSegments; + std::map<weld::LinkButton*, OUString> m_aUris; + + OUString m_sRootName; + OUString m_sClickedURL; + OUString m_aCurrentURL; + + SvtBreadcrumbMode m_eMode; + + Link<Breadcrumb*, bool> m_aClickHdl; + + void appendField(); + bool showField(unsigned int nIndex, unsigned int nWidthMax); + + DECL_LINK(SizeAllocHdl, const Size&, void); + DECL_LINK(ClickLinkHdl, weld::LinkButton&, bool); + +public: + Breadcrumb(weld::Container* pParent); + ~Breadcrumb(); + + void EnableFields(bool bEnable); + + void connect_clicked(const Link<Breadcrumb*, bool>& rLink); + const OUString& GetHdlURL() const; + + void SetRootName(const OUString& rURL); + void SetURL(const OUString& rURL); + void SetMode(SvtBreadcrumbMode eMode); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/commonpicker.cxx b/fpicker/source/office/commonpicker.cxx new file mode 100644 index 000000000..29ae5d08b --- /dev/null +++ b/fpicker/source/office/commonpicker.cxx @@ -0,0 +1,476 @@ +/* -*- 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 "commonpicker.hxx" +#include "fpdialogbase.hxx" +#include "OfficeControlAccess.hxx" +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/weakeventlistener.hxx> +#include <comphelper/types.hxx> + + +namespace svt +{ + + +#define PROPERTY_ID_HELPURL 1 +#define PROPERTY_ID_WINDOW 2 + + // using -------------------------------------------------------------- + + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::ui::dialogs; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::comphelper; + + + OCommonPicker::OCommonPicker() + :OCommonPicker_Base( m_aMutex ) + ,OPropertyContainer( GetBroadcastHelper() ) + ,m_nCancelEvent( nullptr ) + ,m_bExecuting( false ) + { + // the two properties we have + registerProperty( + "HelpURL", PROPERTY_ID_HELPURL, + PropertyAttribute::TRANSIENT, + &m_sHelpURL, cppu::UnoType<decltype(m_sHelpURL)>::get() + ); + + registerProperty( + "Window", PROPERTY_ID_WINDOW, + PropertyAttribute::TRANSIENT | PropertyAttribute::READONLY, + &m_xWindow, cppu::UnoType<decltype(m_xWindow)>::get() + ); + } + + + OCommonPicker::~OCommonPicker() + { + if ( !GetBroadcastHelper().bDisposed ) + { + acquire(); + dispose(); + } + } + + + // disambiguate XInterface + + IMPLEMENT_FORWARD_XINTERFACE2( OCommonPicker, OCommonPicker_Base, OPropertyContainer ) + + + // disambiguate XTypeProvider + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCommonPicker, OCommonPicker_Base, OPropertyContainer ) + + + // XComponent related methods + + void OCommonPicker::checkAlive() const + { + if ( GetBroadcastHelper().bInDispose || GetBroadcastHelper().bDisposed ) + throw DisposedException(); + } + + void OCommonPicker::prepareDialog() + { + if(createPicker()) + { + // set the title + if ( !m_aTitle.isEmpty() ) + m_xDlg->set_title(m_aTitle); + } + } + + + void SAL_CALL OCommonPicker::disposing() + { + SolarMutexGuard aGuard; + + stopWindowListening(); + + if ( m_nCancelEvent ) + Application::RemoveUserEvent( m_nCancelEvent ); + + { + ::osl::MutexGuard aOwnGuard( m_aMutex ); + if ( m_bExecuting && m_xDlg ) + m_xDlg->response(RET_CANCEL); + } + + m_xDlg.reset(); + m_xWindow = nullptr; + m_xDialogParent = nullptr; + } + + + void OCommonPicker::stopWindowListening() + { + disposeComponent( m_xWindowListenerAdapter ); + disposeComponent( m_xParentListenerAdapter ); + } + + // XEventListener + void SAL_CALL OCommonPicker::disposing( const EventObject& _rSource ) + { + SolarMutexGuard aGuard; + bool bDialogDying = _rSource.Source == m_xWindow; + bool bParentDying = _rSource.Source == m_xDialogParent; + + if ( bDialogDying || bParentDying ) + { + stopWindowListening(); + + SAL_WARN_IF(bDialogDying && m_bExecuting, "fpicker.office", "unexpected disposing before response" ); + + // it's the parent which is dying -> delete the dialog + { + ::osl::MutexGuard aOwnGuard(m_aMutex); + if (m_bExecuting && m_xDlg) + m_xDlg->response(RET_CANCEL); + } + + m_xDlg.reset(); + m_xWindow = nullptr; + m_xDialogParent = nullptr; + } + else + { + OSL_FAIL( "OCommonPicker::disposing: where did this come from?" ); + } + } + + // property set related methods + ::cppu::IPropertyArrayHelper* OCommonPicker::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new cppu::OPropertyArrayHelper( aProps ); + } + + ::cppu::IPropertyArrayHelper& SAL_CALL OCommonPicker::getInfoHelper() + { + return *getArrayHelper(); + } + + Reference< XPropertySetInfo > SAL_CALL OCommonPicker::getPropertySetInfo( ) + { + return ::cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ); + } + + void SAL_CALL OCommonPicker::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle, const Any& rValue) + { + OPropertyContainer::setFastPropertyValue_NoBroadcast(nHandle, rValue); + + // if the HelpURL changed, forward this to the dialog + if (PROPERTY_ID_HELPURL == nHandle && m_xDlg) + { + ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView()); + aAccess.setHelpURL(m_xDlg->getDialog(), m_sHelpURL); + } + } + + bool OCommonPicker::createPicker() + { + if ( !m_xDlg ) + { + m_xDlg = implCreateDialog(Application::GetFrameWeld(m_xDialogParent)); + SAL_WARN_IF( !m_xDlg, "fpicker.office", "OCommonPicker::createPicker: invalid dialog returned!" ); + + if ( m_xDlg ) + { + weld::Dialog* pDlg = m_xDlg->getDialog(); + + ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView()); + // synchronize the help id of the dialog without help URL property + if ( !m_sHelpURL.isEmpty() ) + { // somebody already set the help URL while we had no dialog yet + aAccess.setHelpURL(pDlg, m_sHelpURL); + } + else + { + m_sHelpURL = aAccess.getHelpURL(pDlg); + } + + m_xWindow = pDlg->GetXWindow(); + + // add as event listener to the window + OSL_ENSURE( m_xWindow.is(), "OCommonPicker::createFileDialog: invalid window component!" ); + if ( m_xWindow.is() ) + { + m_xWindowListenerAdapter = new OWeakEventListenerAdapter( this, m_xWindow ); + // the adapter will add itself as listener, and forward notifications + } + + VclPtr<vcl::Window> xVclDialog(VCLUnoHelper::GetWindow(m_xWindow)); + if (xVclDialog) // this block is quite possibly unnecessary by now + { + // _and_ add as event listener to the parent - in case the parent is destroyed + // before we are disposed, our disposal would access dead VCL windows then... + m_xDialogParent = VCLUnoHelper::GetInterface(xVclDialog->GetParent()); + OSL_ENSURE(m_xDialogParent.is() || !xVclDialog->GetParent(), "OCommonPicker::createFileDialog: invalid window component (the parent this time)!"); + } + if ( m_xDialogParent.is() ) + { + m_xParentListenerAdapter = new OWeakEventListenerAdapter( this, m_xDialogParent ); + // the adapter will add itself as listener, and forward notifications + } + } + } + + return nullptr != m_xDlg; + } + + // XControlAccess functions + void SAL_CALL OCommonPicker::setControlProperty( const OUString& aControlName, const OUString& aControlProperty, const Any& aValue ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + aAccess.setControlProperty( aControlName, aControlProperty, aValue ); + } + } + + Any SAL_CALL OCommonPicker::getControlProperty( const OUString& aControlName, const OUString& aControlProperty ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + return aAccess.getControlProperty( aControlName, aControlProperty ); + } + + return Any(); + } + + // XControlInformation functions + Sequence< OUString > SAL_CALL OCommonPicker::getSupportedControls( ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + return aAccess.getSupportedControls( ); + } + + return Sequence< OUString >(); + } + + sal_Bool SAL_CALL OCommonPicker::isControlSupported( const OUString& aControlName ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + return svt::OControlAccess::isControlSupported( aControlName ); + } + + return false; + } + + Sequence< OUString > SAL_CALL OCommonPicker::getSupportedControlProperties( const OUString& aControlName ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + return aAccess.getSupportedControlProperties( aControlName ); + } + + return Sequence< OUString >(); + } + + sal_Bool SAL_CALL OCommonPicker::isControlPropertySupported( const OUString& aControlName, const OUString& aControlProperty ) + { + checkAlive(); + + SolarMutexGuard aGuard; + if ( createPicker() ) + { + ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() ); + return aAccess.isControlPropertySupported( aControlName, aControlProperty ); + } + + return false; + } + + + // XExecutableDialog functions + + void OCommonPicker::setTitle( const OUString& _rTitle ) + { + SolarMutexGuard aGuard; + m_aTitle = _rTitle; + } + + + sal_Int16 OCommonPicker::execute() + { + SolarMutexGuard aGuard; + + prepareDialog(); + + { + ::osl::MutexGuard aOwnGuard( m_aMutex ); + m_bExecuting = true; + } + sal_Int16 nResult = implExecutePicker(); + { + ::osl::MutexGuard aOwnGuard( m_aMutex ); + m_bExecuting = false; + } + + return nResult; + } + + + // XCancellable functions + + void SAL_CALL OCommonPicker::cancel( ) + { + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_nCancelEvent ) + // nothing to do - the event for cancelling the dialog is already on the way + return; + } + + // The thread which executes our dialog has locked the solar mutex for + // sure. Cancelling the dialog should be done with a locked solar mutex, too. + // Thus we post ourself a message for cancelling the dialog. This way, the message + // is either handled in the thread which opened the dialog (which may even be + // this thread here), or, if no dialog is open, in the thread doing scheduling + // currently. Both is okay for us... + + // Note that we could do check if we are really executing the dialog currently. + // but the information would be potentially obsolete at the moment our event + // arrives, so we need to check it there, anyway... + m_nCancelEvent = Application::PostUserEvent( LINK( this, OCommonPicker, OnCancelPicker ) ); + } + + IMPL_LINK_NOARG(OCommonPicker, OnCancelPicker, void*, void) + { + // By definition, the solar mutex is locked when we arrive here. Note that this + // is important, as for instance the consistency of m_xDlg depends on this mutex. + ::osl::MutexGuard aGuard( m_aMutex ); + m_nCancelEvent = nullptr; + + if ( !m_bExecuting ) + // nothing to do. This may be because the dialog was canceled after our cancel method + // posted this async event, or because somebody called cancel without the dialog + // being executed at this time. + return; + + OSL_ENSURE( m_xDlg, "OCommonPicker::OnCancelPicker: executing, but no dialog!" ); + if (m_xDlg) + m_xDlg->response(RET_CANCEL); + } + + // XInitialization functions + void SAL_CALL OCommonPicker::initialize( const Sequence< Any >& _rArguments ) + { + checkAlive(); + + OUString sSettingName; + Any aSettingValue; + + PropertyValue aPropArg; + NamedValue aPairArg; + + + const Any* pArguments = _rArguments.getConstArray(); + const Any* pArgumentsEnd = _rArguments.getConstArray() + _rArguments.getLength(); + for ( const Any* pArgument = pArguments; + pArgument != pArgumentsEnd; + ++pArgument + ) + { + if ( *pArgument >>= aPropArg ) + { + if ( aPropArg.Name.isEmpty()) + continue; + + sSettingName = aPropArg.Name; + aSettingValue = aPropArg.Value; + } + else if ( *pArgument >>= aPairArg ) + { + if ( aPairArg.Name.isEmpty()) + continue; + + sSettingName = aPairArg.Name; + aSettingValue = aPairArg.Value; + + + } + else + { + SAL_WARN( "fpicker", "OCommonPicker::initialize: unknown argument type at position " + << (pArguments - _rArguments.getConstArray())); + continue; + } + + bool bKnownSetting = + implHandleInitializationArgument( sSettingName, aSettingValue ); + DBG_ASSERT( bKnownSetting, + OString( + "OCommonPicker::initialize: unknown argument \"" + + OString(sSettingName.getStr(), sSettingName.getLength(), osl_getThreadTextEncoding()) + + "\"!").getStr() ); + } + } + + bool OCommonPicker::implHandleInitializationArgument( const OUString& _rName, const Any& _rValue ) + { + bool bKnown = true; + if ( _rName == "ParentWindow" ) + { + m_xDialogParent.clear(); + OSL_VERIFY( _rValue >>= m_xDialogParent ); + OSL_ENSURE( m_xDialogParent.is(), "OCommonPicker::implHandleInitializationArgument: invalid parent window given!" ); + } + else + bKnown = false; + return bKnown; + } + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/commonpicker.hxx b/fpicker/source/office/commonpicker.hxx new file mode 100644 index 000000000..1c24d8eb9 --- /dev/null +++ b/fpicker/source/office/commonpicker.hxx @@ -0,0 +1,190 @@ +/* -*- 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 <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/ui/dialogs/XControlInformation.hpp> +#include <com/sun/star/ui/dialogs/XControlAccess.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/util/XCancellable.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <comphelper/propertycontainer.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/uno3.hxx> +#include <tools/link.hxx> + +class SvtFileDialog_Base; +namespace weld { class Window; } +struct ImplSVEvent; + +namespace svt +{ + + + typedef ::cppu::WeakComponentImplHelper < css::ui::dialogs::XControlAccess + , css::ui::dialogs::XControlInformation + , css::lang::XEventListener + , css::util::XCancellable + , css::lang::XInitialization + > OCommonPicker_Base; + /** implements common functionality for the 2 UNO picker components + */ + class OCommonPicker + :public ::cppu::BaseMutex + ,public OCommonPicker_Base + ,public ::comphelper::OPropertyContainer + ,public ::comphelper::OPropertyArrayUsageHelper< OCommonPicker > + { + private: + // <properties> + OUString m_sHelpURL; + css::uno::Reference< css::awt::XWindow > m_xWindow; + // </properties> + + ImplSVEvent * m_nCancelEvent; + bool m_bExecuting; + + css::uno::Reference< css::awt::XWindow > m_xDialogParent; + + css::uno::Reference< css::lang::XComponent > m_xWindowListenerAdapter; + css::uno::Reference< css::lang::XComponent > m_xParentListenerAdapter; + + protected: + OUString m_aTitle; + OUString m_aDisplayDirectory; + + protected: + std::shared_ptr<SvtFileDialog_Base> m_xDlg; + + const ::cppu::OBroadcastHelper& GetBroadcastHelper() const { return OCommonPicker_Base::rBHelper; } + ::cppu::OBroadcastHelper& GetBroadcastHelper() { return OCommonPicker_Base::rBHelper; } + + public: + OCommonPicker(); + + protected: + virtual ~OCommonPicker() override; + + // overridables + + // will be called with locked SolarMutex + virtual std::shared_ptr<SvtFileDialog_Base> implCreateDialog( weld::Window* pParent ) = 0; + virtual sal_Int16 implExecutePicker( ) = 0; + // do NOT override XExecutableDialog::execute! We need to do some stuff there ourself ... + + protected: + + // disambiguate XInterface + + DECLARE_XINTERFACE( ) + + + // disambiguate XTypeProvider + + DECLARE_XTYPEPROVIDER( ) + + + // ComponentHelper/XComponent + + virtual void SAL_CALL disposing() override; + + + // XEventListener + + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + + // property set related methods + + + // XPropertySet pure methods + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + // OPropertySetHelper pure methods + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + // OPropertyArrayUsageHelper pure methods + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + // OPropertySetHelper overridden methods + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( + sal_Int32 _nHandle, const css::uno::Any& _rValue ) override; + + + // XExecutableDialog functions + + /// @throws css::uno::RuntimeException + virtual void SAL_CALL setTitle( const OUString& _rTitle ); + /// @throws css::uno::RuntimeException + virtual sal_Int16 SAL_CALL execute(); + + + // XControlAccess functions + + virtual void SAL_CALL setControlProperty( const OUString& aControlName, const OUString& aControlProperty, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getControlProperty( const OUString& aControlName, const OUString& aControlProperty ) override; + + + // XControlInformation functions + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedControls( ) override; + virtual sal_Bool SAL_CALL isControlSupported( const OUString& aControlName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedControlProperties( const OUString& aControlName ) override; + virtual sal_Bool SAL_CALL isControlPropertySupported( const OUString& aControlName, const OUString& aControlProperty ) override; + + + // XCancellable functions + + virtual void SAL_CALL cancel( ) override; + + + // XInitialization functions + + + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + + // misc + + void checkAlive() const; + + void prepareDialog(); + + protected: + bool createPicker(); + + /** handle a single argument from the XInitialization::initialize method + + @return <TRUE/> if the argument could be handled + */ + virtual bool implHandleInitializationArgument( + const OUString& _rName, + const css::uno::Any& _rValue + ); + + private: + void stopWindowListening(); + + DECL_LINK( OnCancelPicker, void*, void ); + }; + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/contentenumeration.cxx b/fpicker/source/office/contentenumeration.cxx new file mode 100644 index 000000000..5b74719a3 --- /dev/null +++ b/fpicker/source/office/contentenumeration.cxx @@ -0,0 +1,326 @@ +/* -*- 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 "contentenumeration.hxx" +#include <svtools/imagemgr.hxx> + +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/XDynamicResultSet.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/document/DocumentProperties.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> + +namespace svt +{ + + +#define ROW_TITLE 1 +#define ROW_SIZE 2 +#define ROW_DATE_MOD 3 +#define ROW_DATE_CREATE 4 +#define ROW_IS_FOLDER 5 +#define ROW_TARGET_URL 6 +#define ROW_IS_HIDDEN 7 +#define ROW_IS_VOLUME 8 +#define ROW_IS_REMOTE 9 +#define ROW_IS_REMOVABLE 10 +#define ROW_IS_FLOPPY 11 +#define ROW_IS_COMPACTDISC 12 + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::util::DateTime; + using ::com::sun::star::sdbc::XResultSet; + using ::com::sun::star::sdbc::XRow; + using ::com::sun::star::ucb::XDynamicResultSet; + using ::com::sun::star::ucb::CommandAbortedException; + using ::com::sun::star::ucb::XContentAccess; + using ::com::sun::star::ucb::XCommandEnvironment; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::document::DocumentProperties; + using ::ucbhelper::ResultSetInclude; + using ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS; + + + //= FileViewContentEnumerator + + + FileViewContentEnumerator::FileViewContentEnumerator( + const Reference< XCommandEnvironment >& _rxCommandEnv, + ContentData& _rContentToFill, ::osl::Mutex& _rContentMutex ) + :Thread ( "FileViewContentEnumerator" ) + ,m_rContent ( _rContentToFill ) + ,m_rContentMutex ( _rContentMutex ) + ,m_xCommandEnv ( _rxCommandEnv ) + ,m_pResultHandler ( nullptr ) + ,m_bCancelled ( false ) + { + } + + + FileViewContentEnumerator::~FileViewContentEnumerator() + { + } + + + void FileViewContentEnumerator::cancel() + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_bCancelled = true; + m_pResultHandler = nullptr; + m_aFolder.aContent = ::ucbhelper::Content(); + m_aFolder.sURL.clear(); + } + + + EnumerationResult FileViewContentEnumerator::enumerateFolderContentSync( + const FolderDescriptor& _rFolder, + const css::uno::Sequence< OUString >& rDenyList ) + { + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_aFolder = _rFolder; + m_pResultHandler = nullptr; + m_rDenyList = rDenyList; + } + return enumerateFolderContent(); + } + + + void FileViewContentEnumerator::enumerateFolderContent( + const FolderDescriptor& _rFolder, IEnumerationResultHandler* _pResultHandler ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_aFolder = _rFolder; + m_pResultHandler = _pResultHandler; + + OSL_ENSURE( m_aFolder.aContent.get().is() || !m_aFolder.sURL.isEmpty(), + "FileViewContentEnumerator::enumerateFolderContent: invalid folder descriptor!" ); + + launch(); + //TODO: a protocol is missing how to join with the launched thread + // before exit(3), to ensure the thread is no longer relying on any + // infrastructure while that infrastructure is being shut down in + // atexit handlers + } + + + EnumerationResult FileViewContentEnumerator::enumerateFolderContent() + { + EnumerationResult eResult = EnumerationResult::ERROR; + try + { + + Reference< XResultSet > xResultSet; + Sequence< OUString > aProps{ "Title", + "Size", + "DateModified", + "DateCreated", + "IsFolder", + "TargetURL", + "IsHidden", + "IsVolume", + "IsRemote", + "IsRemoveable", + "IsFloppy", + "IsCompactDisc" }; + + Reference< XCommandEnvironment > xEnvironment; + try + { + FolderDescriptor aFolder; + { + ::osl::MutexGuard aGuard( m_aMutex ); + aFolder = m_aFolder; + xEnvironment = m_xCommandEnv; + } + if ( !aFolder.aContent.get().is() ) + { + aFolder.aContent = ::ucbhelper::Content( aFolder.sURL, xEnvironment, comphelper::getProcessComponentContext() ); + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_aFolder.aContent = aFolder.aContent; + } + } + + Reference< XDynamicResultSet > xDynResultSet = aFolder.aContent.createDynamicCursor( aProps, INCLUDE_FOLDERS_AND_DOCUMENTS ); + + if ( xDynResultSet.is() ) + xResultSet = xDynResultSet->getStaticResultSet(); + } + catch( CommandAbortedException& ) + { + TOOLS_WARN_EXCEPTION( "svtools.contnr", ""); + } + catch( Exception& ) + { + } + + if ( xResultSet.is() ) + { + Reference< XRow > xRow( xResultSet, UNO_QUERY ); + Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + + try + { + DateTime aDT; + + bool bCancelled = false; + while ( !bCancelled && xResultSet->next() ) + { + bool bIsHidden = xRow->getBoolean( ROW_IS_HIDDEN ); + // don't show hidden files + if ( !bIsHidden || xRow->wasNull() ) + { + aDT = xRow->getTimestamp( ROW_DATE_MOD ); + bool bContainsDate = !xRow->wasNull(); + if ( !bContainsDate ) + { + aDT = xRow->getTimestamp( ROW_DATE_CREATE ); + bContainsDate = !xRow->wasNull(); + } + + OUString aContentURL = xContentAccess->queryContentIdentifierString(); + OUString aTargetURL = xRow->getString( ROW_TARGET_URL ); + bool bHasTargetURL = !xRow->wasNull() && !aTargetURL.isEmpty(); + + OUString sRealURL = bHasTargetURL ? aTargetURL : aContentURL; + + // check for restrictions + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( /* m_rDenyList.hasElements() && */ URLOnDenyList ( sRealURL ) ) + continue; + } + + std::unique_ptr<SortingData_Impl> pData(new SortingData_Impl); + pData->maTargetURL = sRealURL; + + pData->mbIsFolder = xRow->getBoolean( ROW_IS_FOLDER ) && !xRow->wasNull(); + pData->mbIsVolume = xRow->getBoolean( ROW_IS_VOLUME ) && !xRow->wasNull(); + pData->mbIsRemote = xRow->getBoolean( ROW_IS_REMOTE ) && !xRow->wasNull(); + pData->mbIsRemoveable = xRow->getBoolean( ROW_IS_REMOVABLE ) && !xRow->wasNull(); + pData->mbIsFloppy = xRow->getBoolean( ROW_IS_FLOPPY ) && !xRow->wasNull(); + pData->mbIsCompactDisc = xRow->getBoolean( ROW_IS_COMPACTDISC ) && !xRow->wasNull(); + pData->SetNewTitle( xRow->getString( ROW_TITLE ) ); + pData->maSize = xRow->getLong( ROW_SIZE ); + + if ( bHasTargetURL && + INetURLObject( aContentURL ).GetProtocol() == INetProtocol::VndSunStarHier ) + { + ::ucbhelper::Content aCnt( aTargetURL, xEnvironment, comphelper::getProcessComponentContext() ); + try + { + aCnt.getPropertyValue("Size") >>= pData->maSize; + aCnt.getPropertyValue("DateModified") >>= aDT; + } + catch (...) {} + } + + if ( bContainsDate ) + { + pData->maModDate = ::DateTime( aDT ); + } + + if ( pData->mbIsFolder ) + { + SolarMutexGuard aGuard; + ::svtools::VolumeInfo aVolInfo( pData->mbIsVolume, pData->mbIsRemote, + pData->mbIsRemoveable, pData->mbIsFloppy, + pData->mbIsCompactDisc ); + pData->maType = SvFileInformationManager::GetFolderDescription( aVolInfo ); + } + else + pData->maType = SvFileInformationManager::GetFileDescription( + INetURLObject( pData->maTargetURL ) ); + + { + ::osl::MutexGuard aGuard( m_rContentMutex ); + m_rContent.push_back( std::move(pData) ); + } + } + + { + ::osl::MutexGuard aGuard( m_aMutex ); + bCancelled = m_bCancelled; + } + } + eResult = EnumerationResult::SUCCESS; + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svtools.contnr", "FileViewContentEnumerator::enumerateFolderContent: caught an exception while enumerating"); + } + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svtools.contnr", "FileViewContentEnumerator::enumerateFolderContent" ); + } + + IEnumerationResultHandler* pHandler = nullptr; + { + ::osl::MutexGuard aGuard( m_aMutex ); + pHandler = m_pResultHandler; + if ( m_bCancelled ) + return EnumerationResult::ERROR; + } + + { + ::osl::MutexGuard aGuard( m_rContentMutex ); + if ( eResult != EnumerationResult::SUCCESS ) + // clear any "intermediate" and unfinished result + m_rContent.clear(); + } + + if ( pHandler ) + pHandler->enumerationDone( eResult ); + return eResult; + } + + + bool FileViewContentEnumerator::URLOnDenyList ( std::u16string_view sRealURL ) + { + std::u16string_view entryName = sRealURL.substr( sRealURL.rfind( '/' ) + 1 ); + + return comphelper::findValue(m_rDenyList, entryName) != -1; + } + + + void FileViewContentEnumerator::execute() + { + enumerateFolderContent(); + } + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/contentenumeration.hxx b/fpicker/source/office/contentenumeration.hxx new file mode 100644 index 000000000..b1c07afcf --- /dev/null +++ b/fpicker/source/office/contentenumeration.hxx @@ -0,0 +1,237 @@ +/* -*- 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 <sal/config.h> + +#include <memory> + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <salhelper/thread.hxx> +#include <ucbhelper/content.hxx> +#include <rtl/ustring.hxx> +#include <tools/datetime.hxx> +#include <utility> + +namespace svt +{ + + + //= SortingData_Impl + + struct SortingData_Impl + { + private: + OUString maFilename; // only filename in upper case - for compare purposes + OUString maTitle; // -> be careful when changing maTitle to update maFilename only when new + OUString maLowerTitle; + + + public: + OUString maType; + OUString maTargetURL; + OUString maDisplayName; + OUString maDisplaySize; + OUString maDisplayDate; + DateTime maModDate; + OUString maImage; + sal_Int64 maSize; + bool mbIsFolder; + bool mbIsVolume; + bool mbIsRemote; + bool mbIsRemoveable; + bool mbIsFloppy; + bool mbIsCompactDisc; + + inline SortingData_Impl(); + inline const OUString& GetTitle() const; + inline const OUString& GetLowerTitle() const; + inline const OUString& GetFileName() const; + inline void SetNewTitle( const OUString& rNewTitle ); // new maTitle is set -> maFilename is set to same! + + private: + inline void SetTitles( const OUString& rNewTitle ); + }; + + inline SortingData_Impl::SortingData_Impl() : + maModDate ( DateTime::EMPTY ), + maSize ( 0 ), + mbIsFolder ( false ), + mbIsVolume ( false ), + mbIsRemote ( false ), + mbIsRemoveable ( false ), + mbIsFloppy ( false ), + mbIsCompactDisc ( false ) + { + } + + inline const OUString& SortingData_Impl::GetTitle() const + { + return maTitle; + } + + inline const OUString& SortingData_Impl::GetLowerTitle() const + { + return maLowerTitle; + } + + inline const OUString& SortingData_Impl::GetFileName() const + { + return maFilename; + } + + inline void SortingData_Impl::SetNewTitle( const OUString& rNewTitle ) + { + SetTitles( rNewTitle ); + maFilename = rNewTitle.toAsciiUpperCase(); + } + + inline void SortingData_Impl::SetTitles( const OUString& rNewTitle ) + { + maTitle = rNewTitle; + maLowerTitle = rNewTitle.toAsciiLowerCase(); + } + + + //= EnumerationResult + + enum class EnumerationResult + { + SUCCESS, /// the enumeration was successful + ERROR, /// the enumeration was unsuccessful + }; + + + //= FolderDescriptor + + struct FolderDescriptor + { + /** a content object describing the folder. Can be <NULL/>, in this case <member>sURL</member> + is relevant. + */ + ::ucbhelper::Content aContent; + /** the URL of a folder. Will be ignored if <member>aContent</member> is not <NULL/>. + */ + OUString sURL; + + FolderDescriptor() { } + + explicit FolderDescriptor( OUString _aURL ) + :sURL(std::move( _aURL )) + { + } + }; + + + //= IEnumerationResultHandler + + class IEnumerationResultHandler + { + public: + virtual void enumerationDone( EnumerationResult _eResult ) = 0; + + protected: + ~IEnumerationResultHandler() {} + }; + + + //= FileViewContentEnumerator + + class FileViewContentEnumerator: public salhelper::Thread + { + public: + typedef ::std::vector< std::unique_ptr<SortingData_Impl> > ContentData; + + private: + ContentData& m_rContent; + ::osl::Mutex& m_rContentMutex; + + mutable ::osl::Mutex m_aMutex; + + FolderDescriptor m_aFolder; + css::uno::Reference< css::ucb::XCommandEnvironment > + m_xCommandEnv; + IEnumerationResultHandler* m_pResultHandler; + bool m_bCancelled; + + css::uno::Sequence< OUString > m_rDenyList; + + bool URLOnDenyList ( std::u16string_view sRealURL ); + + public: + /** constructs an enumerator instance + + @param _rContentToFill + the structure which is to be filled with the found content + @param _rContentMutex + the mutex which protects the access to <arg>_rContentToFill</arg> + @param _pTranslator + an instance which should be used to translate content titles. May be <NULL/> + */ + FileViewContentEnumerator( + const css::uno::Reference< css::ucb::XCommandEnvironment >& _rxCommandEnv, + ContentData& _rContentToFill, + ::osl::Mutex& _rContentMutex + ); + + /** enumerates the content of a given folder + + @param _rFolder + the folder whose content is to be enumerated + @param _pFilter + a filter to apply to the found contents + @param _pResultHandler + an instance which should handle the results of the enumeration + */ + void enumerateFolderContent( + const FolderDescriptor& _rFolder, + IEnumerationResultHandler* _pResultHandler + ); + + /** enumerates the content of a given folder synchronously + */ + EnumerationResult enumerateFolderContentSync( + const FolderDescriptor& _rFolder, + const css::uno::Sequence< OUString >& rDenyList + ); + + /** cancels the running operation. + + Note that "cancel" may mean that the operation is running, but its result + is simply disregarded later on. + */ + void cancel(); + + protected: + virtual ~FileViewContentEnumerator() override; + + private: + EnumerationResult enumerateFolderContent(); + + // Thread overridables + virtual void execute() override; + + }; + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fileview.cxx b/fpicker/source/office/fileview.cxx new file mode 100644 index 000000000..7e78f773e --- /dev/null +++ b/fpicker/source/office/fileview.cxx @@ -0,0 +1,1816 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svtools/svtresid.hxx> +#include <svtools/imagemgr.hxx> +#include <svtools/querydelete.hxx> +#include <svtools/strings.hrc> +#include <bitmaps.hlst> +#include "contentenumeration.hxx" +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <algorithm> +#include <string_view> +#include <vector> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <rtl/math.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/string_view.hxx> +#include <osl/mutex.hxx> +#include <osl/conditn.hxx> +#include <salhelper/timer.hxx> +#include <svtools/urlfilter.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/timer.hxx> +#include <memory> +#include "fileview.hxx" + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::beans; +using namespace ::comphelper; +using ::svt::SortingData_Impl; +using ::svt::FolderDescriptor; + +constexpr OUStringLiteral ALL_FILES_FILTER = u"*.*"; + +#define COLUMN_TITLE 1 +#define COLUMN_TYPE 2 +#define COLUMN_SIZE 3 +#define COLUMN_DATE 4 + +#define QUICK_SEARCH_TIMEOUT 1500 // time in mSec before the quicksearch string will be reset + +namespace { + +enum class FileViewFlags +{ + NONE = 0x00, + MULTISELECTION = 0x02, + SHOW_TYPE = 0x04, + SHOW_NONE = 0x20, +}; + +} + +namespace o3tl +{ + template<> struct typed_flags<FileViewFlags> : is_typed_flags<FileViewFlags, 0x26> {}; +} + +namespace +{ + + //= CallbackTimer + + class CallbackTimer : public ::salhelper::Timer + { + protected: + SvtFileView_Impl* const m_pTimeoutHandler; + + public: + explicit CallbackTimer( SvtFileView_Impl* _pHandler ) : m_pTimeoutHandler( _pHandler ) { } + + protected: + virtual void SAL_CALL onShot() override; + }; + +class ViewTabListBox_Impl +{ +private: + Reference< XCommandEnvironment > mxCmdEnv; + std::unique_ptr<weld::TreeView> mxTreeView; + std::unique_ptr<weld::TreeIter> mxScratchIter; + + ::osl::Mutex maMutex; + SvtFileView_Impl* mpParent; + Timer maResetQuickSearch { "fpicker SvtFileView_Impl maResetQuickSearch" }; + OUString maQuickSearchText; + sal_uInt32 mnSearchIndex; + bool mbEnableDelete; + bool mbEditing; + bool const mbShowType; + + void DeleteEntries(); + void DoQuickSearch( sal_Unicode rChar ); + bool Kill( const OUString& rURL ); + +public: + ViewTabListBox_Impl(std::unique_ptr<weld::TreeView> xTreeView, weld::Window* pTopLevel, SvtFileView_Impl* pParent, FileViewFlags nFlags); + + std::unique_ptr<weld::TreeIter> make_iterator() const { return mxTreeView->make_iterator(); } + void insert(const OUString &rEntry, const OUString& rId, const OUString& rImage, weld::TreeIter& rIter) + { + mxTreeView->insert(nullptr, -1, &rEntry, &rId, nullptr, nullptr, false, &rIter); + mxTreeView->set_image(rIter, rImage); + } + void append(const OUString& rId, const OUString& rStr, const OUString& rType, const OUString& rSize, const OUString& rDate, const OUString& rImage) + { + mxTreeView->insert(nullptr, -1, &rStr, &rId, nullptr, nullptr, false, mxScratchIter.get()); + mxTreeView->set_image(*mxScratchIter, rImage); + int nCol = 1; + if (mbShowType) + mxTreeView->set_text(*mxScratchIter, rType, nCol++); + mxTreeView->set_text(*mxScratchIter, rSize, nCol++); + mxTreeView->set_text(*mxScratchIter, rDate, nCol++); + } + + void scroll_to_row(const weld::TreeIter& rIter) { mxTreeView->scroll_to_row(rIter); } + void set_cursor(int nPos) { mxTreeView->set_cursor(nPos); } + void set_cursor(const weld::TreeIter& rIter) { mxTreeView->set_cursor(rIter); } + bool get_cursor(weld::TreeIter* pIter) const { return mxTreeView->get_cursor(pIter); } + bool get_iter_first(weld::TreeIter& rIter) const { return mxTreeView->get_iter_first(rIter); } + bool get_selected(weld::TreeIter* pIter) const { return mxTreeView->get_selected(pIter); } + + OUString get_selected_text() const + { + // tdf#131898 only care about column 0 + int nIndex = mxTreeView->get_selected_index(); + return nIndex != -1 ? mxTreeView->get_text(nIndex, 0) : OUString(); + } + + void unselect_all() { mxTreeView->unselect_all(); } + + OUString get_id(const weld::TreeIter& rIter) { return mxTreeView->get_id(rIter); } + + void connect_row_activated(const Link<weld::TreeView&, bool>& rLink) { mxTreeView->connect_row_activated(rLink); } + void connect_changed(const Link<weld::TreeView&, void>& rLink) { mxTreeView->connect_changed(rLink); } + + int n_children() const { return mxTreeView->n_children(); } + + void freeze() { mxTreeView->freeze(); } + void thaw() { mxTreeView->thaw(); } + + void show() { mxTreeView->show(); } + void hide() { mxTreeView->hide(); } + bool get_visible() const { return mxTreeView->get_visible(); } + + int count_selected_rows() const { return mxTreeView->count_selected_rows(); } + + void grab_focus() { mxTreeView->grab_focus(); } + bool has_focus() const { return mxTreeView->has_focus(); } + + void set_help_id(const OString& rHelpId) { mxTreeView->set_help_id(rHelpId); } + OString get_help_id() const { return mxTreeView->get_help_id(); } + + bool IsEditingActive() const { return mbEditing; } + + void end_editing() + { + mxTreeView->end_editing(); + mxTreeView->connect_editing(Link<const weld::TreeIter&, bool>(), Link<const IterString&, bool>()); + mbEditing = false; + } + + void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) + { + mxTreeView->selected_foreach(func); + } + + weld::TreeView* getWidget() const + { + return mxTreeView.get(); + } + + void clear() { mxTreeView->clear(); } + + void EnableDelete( bool bEnable ) { mbEnableDelete = bEnable; } + bool TypeColumnVisible() const { return mbShowType; } + + const Reference< XCommandEnvironment >& GetCommandEnvironment() const { return mxCmdEnv; } + + DECL_LINK(ResetQuickSearch_Impl, Timer *, void); + DECL_LINK(CommandHdl, const CommandEvent&, bool); + DECL_LINK(EditingEntryHdl, const weld::TreeIter&, bool); + typedef std::pair<const weld::TreeIter&, OUString> IterString; + DECL_LINK(EditedEntryHdl, const IterString&, bool); + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + + void ExecuteContextMenuAction(std::string_view rSelectedPopentry); +}; + +} + +//= SvtFileView_Impl +class SvtFileView_Impl :public ::svt::IEnumerationResultHandler +{ +protected: + SvtFileView* m_pAntiImpl; + Link<SvtFileView*,void> m_aSelectHandler; + + ::rtl::Reference< ::svt::FileViewContentEnumerator > + m_xContentEnumerator; + Link<void*,void> m_aCurrentAsyncActionHandler; + ::osl::Condition m_aAsyncActionFinished; + ::rtl::Reference< ::salhelper::Timer > m_xCancelAsyncTimer; + ::svt::EnumerationResult m_eAsyncActionResult; + bool m_bRunningAsyncAction; + bool m_bAsyncActionCancelled; + +public: + + ::std::vector<std::unique_ptr<SortingData_Impl>> maContent; + ::std::vector<std::unique_ptr<SvtContentEntry>> maEntries; + ::osl::Mutex maMutex; + + weld::Window* m_pTopLevel; + std::unique_ptr<ViewTabListBox_Impl> mxView; + std::unique_ptr<weld::IconView> mxIconView; + sal_uInt16 mnSortColumn; + bool mbAscending : 1; + bool const mbOnlyFolder : 1; + sal_Int16 mnSuspendSelectCallback : 1; + bool mbIsFirstResort : 1; + + IntlWrapper const aIntlWrapper; + + OUString maViewURL; + OUString maCurrentFilter; + OUString maFolderImage; + Link<SvtFileView*,void> maOpenDoneLink; + Link<SvtFileView*,bool> maDoubleClickHandler; + + Reference< XCommandEnvironment > mxCmdEnv; + + SvtFileView_Impl(SvtFileView* pAntiImpl, weld::Window* pTopLevel, + std::unique_ptr<weld::TreeView> xTreeView, + std::unique_ptr<weld::IconView> xIconView, + Reference < XCommandEnvironment > const & xEnv, + FileViewFlags nFlags, + bool bOnlyFolder); + + virtual ~SvtFileView_Impl(); + + void Clear(); + + FileViewResult GetFolderContent_Impl( + std::u16string_view rFolder, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList ); + + FileViewResult GetFolderContent_Impl( + const FolderDescriptor& _rFolder, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList ); + void FilterFolderContent_Impl( const OUString &rFilter ); + void CancelRunningAsyncAction(); + + void OpenFolder_Impl(); + static OUString ReplaceTabWithString(const OUString& rValue); + void CreateDisplayText_Impl(); + void SortFolderContent_Impl(); + + void EntryRemoved( std::u16string_view rURL ); + void EntryRenamed( OUString& rURL, + const OUString& rName ); + const SortingData_Impl& FolderInserted( const OUString& rURL, + const OUString& rTitle ); + + int GetEntryPos( std::u16string_view rURL ); + + void SetViewMode( FileViewMode eMode ); + + inline void EnableDelete( bool bEnable ); + + void Resort_Impl( sal_Int16 nColumn, bool bAscending ); + bool SearchNextEntry( sal_uInt32 &nIndex, + std::u16string_view rTitle, + bool bWrapAround ); + + void SetSelectHandler( const Link<SvtFileView*,void>& rHdl ); + void SetDoubleClickHandler(const Link<SvtFileView*,bool>& rHdl); + + void ResetCursor(); + + void EndEditing() + { + if (mxView->IsEditingActive()) + mxView->end_editing(); + } + + void onTimeout(); + + void grab_focus() + { + if (mxView->get_visible()) + mxView->grab_focus(); + else + mxIconView->grab_focus(); + } + + bool has_focus() const + { + return mxView->has_focus() || mxIconView->has_focus(); + } + + int GetSortColumn() const + { + sal_uInt16 nOldSortID = mnSortColumn; + // skip "TYPE" + if (!mxView->TypeColumnVisible() && nOldSortID != COLUMN_TITLE) + --nOldSortID; + return nOldSortID - 1; + } + +protected: + DECL_LINK(ChangedHdl, weld::TreeView&, void); + DECL_LINK(SelectionChangedHdl, weld::IconView&, void); + DECL_LINK(RowActivatedHdl, weld::TreeView&, bool); + DECL_LINK(ItemActivatedHdl, weld::IconView&, bool); + + // IEnumerationResultHandler overridables + virtual void enumerationDone( ::svt::EnumerationResult eResult ) override; + void implEnumerationSuccess(); +}; + +inline void SvtFileView_Impl::EnableDelete( bool bEnable ) +{ + mxView->EnableDelete( bEnable ); +} + +namespace +{ + // functions ------------------------------------------------------------- + + OUString CreateExactSizeText( sal_Int64 nSize ) + { + double fSize( static_cast<double>(nSize) ); + int nDec; + + tools::Long nMega = 1024 * 1024; + tools::Long nGiga = nMega * 1024; + + OUString aUnitStr(' '); + + if ( nSize < 10000 ) + { + aUnitStr += SvtResId(STR_SVT_BYTES ); + nDec = 0; + } + else if ( nSize < nMega ) + { + fSize /= 1024; + aUnitStr += SvtResId(STR_SVT_KB); + nDec = 1; + } + else if ( nSize < nGiga ) + { + fSize /= nMega; + aUnitStr += SvtResId(STR_SVT_MB); + nDec = 2; + } + else + { + fSize /= nGiga; + aUnitStr += SvtResId(STR_SVT_GB); + nDec = 3; + } + + OUString aSizeStr( ::rtl::math::doubleToUString( fSize, + rtl_math_StringFormat_F, nDec, + SvtSysLocale().GetLocaleData().getNumDecimalSep()[0]) ); + aSizeStr += aUnitStr; + + return aSizeStr; + } +} + +ViewTabListBox_Impl::ViewTabListBox_Impl(std::unique_ptr<weld::TreeView> xTreeView, + weld::Window* pTopLevel, + SvtFileView_Impl* pParent, + FileViewFlags nFlags) + : mxTreeView(std::move(xTreeView)) + , mxScratchIter(mxTreeView->make_iterator()) + , mpParent( pParent ) + , mnSearchIndex( 0 ) + , mbEnableDelete( false ) + , mbEditing( false ) + , mbShowType(nFlags & FileViewFlags::SHOW_TYPE) +{ + std::vector<int> aWidths { 180 }; + if (nFlags & FileViewFlags::SHOW_TYPE) + aWidths.push_back(140); + aWidths.push_back(80); + mxTreeView->set_column_fixed_widths(aWidths); + + if (nFlags & FileViewFlags::MULTISELECTION) + mxTreeView->set_selection_mode(SelectionMode::Multiple); + + maResetQuickSearch.SetTimeout( QUICK_SEARCH_TIMEOUT ); + maResetQuickSearch.SetInvokeHandler( LINK( this, ViewTabListBox_Impl, ResetQuickSearch_Impl ) ); + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xInteractionHandler( + InteractionHandler::createWithParent(xContext, pTopLevel->GetXWindow()), UNO_QUERY_THROW); + + mxCmdEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, Reference< XProgressHandler >() ); + + mxTreeView->connect_popup_menu(LINK(this, ViewTabListBox_Impl, CommandHdl)); + mxTreeView->connect_key_press(LINK(this, ViewTabListBox_Impl, KeyInputHdl)); +} + +IMPL_LINK_NOARG(ViewTabListBox_Impl, EditingEntryHdl, const weld::TreeIter&, bool) +{ + return mbEditing; +} + +IMPL_LINK_NOARG(ViewTabListBox_Impl, ResetQuickSearch_Impl, Timer *, void) +{ + ::osl::MutexGuard aGuard( maMutex ); + + maQuickSearchText.clear(); + mnSearchIndex = 0; +} + +IMPL_LINK(ViewTabListBox_Impl, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + if (mbEditing) + return false; + + bool bHandled = false; + + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + if ( 0 == rKeyCode.GetModifier() ) + { + if ( ( rKeyCode.GetCode() == KEY_DELETE ) && + mbEnableDelete ) + { + ResetQuickSearch_Impl( nullptr ); + DeleteEntries(); + bHandled = true; + } + else if ( ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_NUM ) || + ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_ALPHA ) ) + { + DoQuickSearch( rKEvt.GetCharCode() ); + bHandled = true; + } + } + + if (!bHandled) + ResetQuickSearch_Impl( nullptr ); + return bHandled; +} + +IMPL_LINK(ViewTabListBox_Impl, CommandHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + bool bEnableDelete = mbEnableDelete; + bool bEnableRename = true; + + int nCount = 0; + mxTreeView->selected_foreach([this, &nCount, &bEnableDelete, &bEnableRename](weld::TreeIter& rEntry){ + ++nCount; + + ::ucbhelper::Content aCnt; + try + { + OUString aURL(weld::fromId<SvtContentEntry*>( + mxTreeView->get_id(rEntry))->maURL); + aCnt = ::ucbhelper::Content( aURL, mxCmdEnv, comphelper::getProcessComponentContext() ); + } + catch( Exception const & ) + { + bEnableDelete = bEnableRename = false; + } + + if ( bEnableDelete ) + { + try + { + Reference< XCommandInfo > aCommands = aCnt.getCommands(); + if ( aCommands.is() ) + bEnableDelete = aCommands->hasCommandByName( "delete" ); + else + bEnableDelete = false; + } + catch( Exception const & ) + { + bEnableDelete = false; + } + } + + if ( bEnableRename ) + { + try + { + Reference< XPropertySetInfo > aProps = aCnt.getProperties(); + if ( aProps.is() ) + { + Property aProp = aProps->getPropertyByName("Title"); + bEnableRename + = !( aProp.Attributes & PropertyAttribute::READONLY ); + } + else + bEnableRename = false; + } + catch( Exception const & ) + { + bEnableRename = false; + } + } + + bool bStop = !bEnableDelete && !bEnableRename; + return bStop; + }); + + if (nCount == 0) + bEnableDelete = false; + if (nCount != 1) + bEnableRename = false; + + if (bEnableDelete || bEnableRename) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxTreeView.get(), "svt/ui/fileviewmenu.ui")); + auto xContextMenu = xBuilder->weld_menu("menu"); + xContextMenu->set_visible("delete", bEnableDelete); + xContextMenu->set_visible("rename", bEnableRename); + OString sCommand(xContextMenu->popup_at_rect(mxTreeView.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)))); + ExecuteContextMenuAction(sCommand); + } + + return true; +} + +void ViewTabListBox_Impl::ExecuteContextMenuAction(std::string_view rSelectedPopupEntry) +{ + if (rSelectedPopupEntry == "delete") + DeleteEntries(); + else if (rSelectedPopupEntry == "rename") + { + std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator(); + if (mxTreeView->get_selected(xEntry.get())) + { + mbEditing = true; + + mxTreeView->connect_editing(LINK(this, ViewTabListBox_Impl, EditingEntryHdl), + LINK(this, ViewTabListBox_Impl, EditedEntryHdl)); + + mxTreeView->start_editing(*xEntry); + } + } +} + +void ViewTabListBox_Impl::DeleteEntries() +{ + short eResult = svtools::QUERYDELETE_YES; + + mxTreeView->selected_foreach([this, &eResult](weld::TreeIter& rCurEntry){ + OUString aURL; + if (!mxTreeView->get_id(rCurEntry).isEmpty()) + aURL = weld::fromId<SvtContentEntry*>(mxTreeView->get_id(rCurEntry))->maURL; + if (aURL.isEmpty()) + { + mxTreeView->unselect(rCurEntry); + return false; + } + + bool canDelete = true; + try + { + ::ucbhelper::Content aCnt( aURL, mxCmdEnv, comphelper::getProcessComponentContext() ); + Reference< XCommandInfo > aCommands = aCnt.getCommands(); + if ( aCommands.is() ) + canDelete = aCommands->hasCommandByName( "delete" ); + else + canDelete = false; + } + catch( Exception const & ) + { + canDelete = false; + } + + if (!canDelete) + { + mxTreeView->unselect(rCurEntry); + return false; // process next entry + } + + if ( eResult != svtools::QUERYDELETE_ALL ) + { + INetURLObject aObj( aURL ); + svtools::QueryDeleteDlg_Impl aDlg( + mxTreeView.get(), aObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset)); + + if (mxTreeView->count_selected_rows() > 1) + aDlg.EnableAllButton(); + + eResult = aDlg.run(); + } + + bool bDeleted = false; + + if (eResult == svtools::QUERYDELETE_ALL || eResult == svtools::QUERYDELETE_YES) + { + if ( Kill( aURL ) ) + { + mpParent->EntryRemoved( aURL ); + bDeleted = true; + } + } + + if (!bDeleted) + mxTreeView->unselect(rCurEntry); + + return false; + }); + + mxTreeView->remove_selection(); +} + +IMPL_LINK(ViewTabListBox_Impl, EditedEntryHdl, const IterString&, rIterString, bool) +{ + mbEditing = false; + + mxTreeView->connect_editing(Link<const weld::TreeIter&, bool>(), Link<const IterString&, bool>()); + + const weld::TreeIter& rEntry = rIterString.first; + OUString sNewText = rIterString.second; + + if (sNewText.isEmpty()) + return false; + + bool bRet = false; + + OUString aURL; + SvtContentEntry* pData = weld::fromId<SvtContentEntry*>(mxTreeView->get_id(rEntry)); + + if ( pData ) + aURL = pData->maURL; + + if ( aURL.isEmpty() ) + return bRet; + + try + { + OUString aPropName( "Title" ); + bool canRename = true; + ::ucbhelper::Content aContent( aURL, mxCmdEnv, comphelper::getProcessComponentContext() ); + + try + { + Reference< XPropertySetInfo > aProps = aContent.getProperties(); + if ( aProps.is() ) + { + Property aProp = aProps->getPropertyByName( aPropName ); + canRename = !( aProp.Attributes & PropertyAttribute::READONLY ); + } + else + { + canRename = false; + } + } + catch ( Exception const & ) + { + canRename = false; + } + + if ( canRename ) + { + Any aValue; + aValue <<= sNewText; + aContent.setPropertyValue( aPropName, aValue ); + mpParent->EntryRenamed(aURL, sNewText); + + if (pData) + pData->maURL = aURL; + + mxTreeView->set_id(rEntry, weld::toId(pData)); + + bRet = true; + } + } + catch( Exception const & ) + { + } + + return bRet; +} + +void ViewTabListBox_Impl::DoQuickSearch( sal_Unicode rChar ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + maResetQuickSearch.Stop(); + + OUString aLastText = maQuickSearchText; + sal_uInt32 aLastPos = mnSearchIndex; + + maQuickSearchText += OUString(rChar).toAsciiLowerCase(); + + bool bFound = mpParent->SearchNextEntry( mnSearchIndex, maQuickSearchText, false ); + + if ( !bFound && ( aLastText.getLength() == 1 ) && + ( aLastText == OUStringChar(rChar) ) ) + { + mnSearchIndex = aLastPos + 1; + maQuickSearchText = aLastText; + bFound = mpParent->SearchNextEntry( mnSearchIndex, maQuickSearchText, true ); + } + + if (bFound) + { + mxTreeView->unselect_all(); + mxTreeView->select(mnSearchIndex); + mxTreeView->set_cursor(mnSearchIndex); + mxTreeView->scroll_to_row(mnSearchIndex); + } + + maResetQuickSearch.Start(); +} + +bool ViewTabListBox_Impl::Kill( const OUString& rContent ) +{ + bool bRet = true; + + try + { + ::ucbhelper::Content aCnt( rContent, mxCmdEnv, comphelper::getProcessComponentContext() ); + aCnt.executeCommand( "delete", Any( true ) ); + } + catch( css::ucb::CommandAbortedException const & ) + { + SAL_INFO( "svtools.contnr", "CommandAbortedException" ); + bRet = false; + } + catch( Exception const & ) + { + SAL_INFO( "svtools.contnr", "Any other exception" ); + bRet = false; + } + + return bRet; +} + +SvtFileView::SvtFileView(weld::Window* pTopLevel, + std::unique_ptr<weld::TreeView> xTreeView, + std::unique_ptr<weld::IconView> xIconView, + bool bOnlyFolder, bool bMultiSelection, bool bShowType ) +{ + FileViewFlags nFlags = FileViewFlags::NONE; + if ( bMultiSelection ) + nFlags |= FileViewFlags::MULTISELECTION; + if ( bShowType ) + nFlags |= FileViewFlags::SHOW_TYPE; + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xInteractionHandler( + InteractionHandler::createWithParent(xContext, pTopLevel->GetXWindow()), UNO_QUERY_THROW); + Reference < XCommandEnvironment > xCmdEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, Reference< XProgressHandler >() ); + + mpImpl.reset(new SvtFileView_Impl(this, pTopLevel, std::move(xTreeView), std::move(xIconView), xCmdEnv, nFlags, bOnlyFolder)); + + weld::TreeView* pView = mpImpl->mxView->getWidget(); + pView->connect_column_clicked(LINK(this, SvtFileView, HeaderSelect_Impl)); +} + +void SvtFileView::grab_focus() +{ + mpImpl->grab_focus(); +} + +bool SvtFileView::has_focus() const +{ + return mpImpl->has_focus(); +} + +SvtFileView::~SvtFileView() +{ +} + +void SvtFileView::SetViewMode( FileViewMode eMode ) +{ + mpImpl->SetViewMode( eMode ); +} + +OUString SvtFileView::GetURL(const weld::TreeIter& rEntry) const +{ + SvtContentEntry* pEntry; + if (mpImpl->mxView->get_visible()) + pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(rEntry)); + else + pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(rEntry)); + if (pEntry) + return pEntry->maURL; + return OUString(); +} + +OUString SvtFileView::GetCurrentURL() const +{ + SvtContentEntry* pEntry = nullptr; + OUString aURL; + if (mpImpl->mxView->get_visible()) + { + std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator(); + if (mpImpl->mxView->get_selected(xEntry.get())) + pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(*xEntry)); + } + else + { + std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxIconView->make_iterator(); + if (mpImpl->mxIconView->get_selected(xEntry.get())) + pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(*xEntry)); + } + if (pEntry) + aURL = pEntry->maURL; + return aURL; +} + +void SvtFileView::CreatedFolder( const OUString& rUrl, const OUString& rNewFolder ) +{ + const SortingData_Impl& rEntry = mpImpl->FolderInserted( rUrl, rNewFolder ); + + mpImpl->maEntries.emplace_back(std::make_unique<SvtContentEntry>(rUrl, true)); + OUString sId(weld::toId(mpImpl->maEntries.back().get())); + + std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator(); + mpImpl->mxView->insert(rEntry.maDisplayName, sId, mpImpl->maFolderImage, *xEntry); + mpImpl->mxView->scroll_to_row(*xEntry); + + std::unique_ptr<weld::TreeIter> xIconEntry = mpImpl->mxIconView->make_iterator(); + mpImpl->mxIconView->insert(-1, &rEntry.maDisplayName, &sId, &mpImpl->maFolderImage, xIconEntry.get()); + mpImpl->mxIconView->scroll_to_item(*xIconEntry); +} + +FileViewResult SvtFileView::PreviousLevel( const FileViewAsyncAction* pAsyncDescriptor ) +{ + FileViewResult eResult = eFailure; + + OUString sParentURL; + if ( GetParentURL( sParentURL ) ) + eResult = Initialize( sParentURL, mpImpl->maCurrentFilter, pAsyncDescriptor, maDenyList ); + + return eResult; +} + +bool SvtFileView::GetParentURL( OUString& rParentURL ) const +{ + bool bRet = false; + try + { + ::ucbhelper::Content aCnt( mpImpl->maViewURL, mpImpl->mxCmdEnv, comphelper::getProcessComponentContext() ); + Reference< XContent > xContent( aCnt.get() ); + Reference< css::container::XChild > xChild( xContent, UNO_QUERY ); + if ( xChild.is() ) + { + Reference< XContent > xParent( xChild->getParent(), UNO_QUERY ); + if ( xParent.is() ) + { + rParentURL = xParent->getIdentifier()->getContentIdentifier(); + bRet = !rParentURL.isEmpty() && rParentURL != mpImpl->maViewURL; + } + } + } + catch( Exception const & ) + { + // perhaps an unknown url protocol (e.g. "private:newdoc") + } + + return bRet; +} + +OString SvtFileView::get_help_id() const +{ + return mpImpl->mxView->get_help_id(); +} + +void SvtFileView::set_help_id(const OString& rHelpId) +{ + mpImpl->mxView->set_help_id(rHelpId); +} + +OUString SvtFileView::get_selected_text() const +{ + if (mpImpl->mxView->get_visible()) + return mpImpl->mxView->get_selected_text(); + return mpImpl->mxIconView->get_selected_text(); +} + +FileViewResult SvtFileView::Initialize( + const OUString& rURL, + const OUString& rFilter, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList ) +{ + weld::WaitObject aWaitCursor(mpImpl->m_pTopLevel); + maDenyList = rDenyList; + + OUString sPushURL( mpImpl->maViewURL ); + + mpImpl->maViewURL = rURL; + FileViewResult eResult = ExecuteFilter( rFilter, pAsyncDescriptor ); + switch ( eResult ) + { + case eFailure: + case eTimeout: + mpImpl->maViewURL = sPushURL; + return eResult; + + case eStillRunning: + OSL_ENSURE( pAsyncDescriptor, "SvtFileView::Initialize: we told it to read synchronously!" ); + [[fallthrough]]; + case eSuccess: + return eResult; + } + + OSL_FAIL( "SvtFileView::Initialize: unreachable!" ); + return eFailure; +} + +FileViewResult SvtFileView::ExecuteFilter( const OUString& rFilter, const FileViewAsyncAction* pAsyncDescriptor ) +{ + mpImpl->maCurrentFilter = rFilter.toAsciiLowerCase(); + + mpImpl->Clear(); + FileViewResult eResult = mpImpl->GetFolderContent_Impl(mpImpl->maViewURL, pAsyncDescriptor, maDenyList); + OSL_ENSURE( ( eResult != eStillRunning ) || pAsyncDescriptor, "SvtFileView::ExecuteFilter: we told it to read synchronously!" ); + return eResult; +} + +void SvtFileView::CancelRunningAsyncAction() +{ + mpImpl->CancelRunningAsyncAction(); +} + +void SvtFileView::SetNoSelection() +{ + mpImpl->mxView->unselect_all(); + mpImpl->mxIconView->unselect_all(); +} + +void SvtFileView::SetSelectHdl(const Link<SvtFileView*,void>& rHdl) +{ + mpImpl->SetSelectHandler(rHdl); +} + +void SvtFileView::SetDoubleClickHdl(const Link<SvtFileView*,bool>& rHdl) +{ + mpImpl->SetDoubleClickHandler(rHdl); +} + +sal_uInt32 SvtFileView::GetSelectionCount() const +{ + if (mpImpl->mxView->get_visible()) + return mpImpl->mxView->count_selected_rows(); + return mpImpl->mxIconView->count_selected_items(); +} + +SvtContentEntry* SvtFileView::FirstSelected() const +{ + if (mpImpl->mxView->get_visible()) + { + SvtContentEntry* pRet = nullptr; + std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator(); + if (mpImpl->mxView->get_selected(xEntry.get())) + pRet = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(*xEntry)); + return pRet; + } + + SvtContentEntry* pRet = nullptr; + std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxIconView->make_iterator(); + if (mpImpl->mxIconView->get_selected(xEntry.get())) + pRet = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(*xEntry)); + return pRet; +} + +const OUString& SvtFileView::GetViewURL() const +{ + return mpImpl->maViewURL; +} + +void SvtFileView::SetOpenDoneHdl( const Link<SvtFileView*,void>& rHdl ) +{ + mpImpl->maOpenDoneLink = rHdl; +} + +void SvtFileView::EnableDelete( bool bEnable ) +{ + mpImpl->EnableDelete( bEnable ); +} + +void SvtFileView::EndInplaceEditing() +{ + return mpImpl->EndEditing(); +} + +IMPL_LINK(SvtFileView, HeaderSelect_Impl, int, nColumn, void) +{ + sal_uInt16 nItemID = nColumn + 1; + // skip "TYPE" + if (!mpImpl->mxView->TypeColumnVisible() && nItemID != COLUMN_TITLE) + ++nItemID; + + weld::TreeView* pView = mpImpl->mxView->getWidget(); + bool bSortAtoZ = mpImpl->mbAscending; + + //set new arrow positions in headerbar + if (nItemID != mpImpl->mnSortColumn) + { + // remove old indicator, new will be created in OpenFolder_Impl + pView->set_sort_indicator(TRISTATE_INDET, mpImpl->GetSortColumn()); + } + else + bSortAtoZ = !bSortAtoZ; + + mpImpl->Resort_Impl(nItemID, bSortAtoZ); +} + +OUString SvtFileView::GetConfigString() const +{ + // sort order + OUString sRet = OUString::number( mpImpl->mnSortColumn ) + ";"; + + bool bUp = mpImpl->mbAscending; + sRet += OUString::Concat(bUp ? std::u16string_view(u"1") : std::u16string_view(u"0")) + ";"; + + weld::TreeView* pView = mpImpl->mxView->getWidget(); + sal_uInt16 nCount = mpImpl->mxView->TypeColumnVisible() ? 4 : 3; + for (sal_uInt16 i = 0; i < nCount; ++i) + { + sal_uInt16 nId = i + 1; + // skip "TYPE" + if (!mpImpl->mxView->TypeColumnVisible() && nId != COLUMN_TITLE) + ++nId; + + sRet += OUString::number( nId ) + + ";" + + OUString::number(pView->get_column_width(i)) + + ";"; + } + + return comphelper::string::stripEnd(sRet, ';'); +} + +::std::vector< SvtContentEntry > SvtFileView::GetContent() +{ + ::std::vector< SvtContentEntry > aContent; + + for(auto const& elem : mpImpl->maContent) + { + SvtContentEntry aEntry( elem->maTargetURL, elem->mbIsFolder ); + aContent.push_back( aEntry ); + } + + return aContent; +} + +void SvtFileView::SetConfigString(std::u16string_view rCfgStr) +{ + sal_Int32 nIdx = 0; + sal_uInt16 nSortColumn = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx ))); + bool bAscending = static_cast<bool>(static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx )))); + + std::vector<int> aWidths(mpImpl->mxView->TypeColumnVisible() ? 4 : 3, -1); + + while ( nIdx != -1 ) + { + sal_uInt16 nItemId = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx ))); + + int nWidth = o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx )); + + // skip "TYPE" + if (!mpImpl->mxView->TypeColumnVisible() && nItemId != COLUMN_TITLE) + --nItemId; + int nColumn = nItemId - 1; + + if (nColumn >= 0 && o3tl::make_unsigned(nColumn) < aWidths.size()) + aWidths[nColumn] = nWidth; + } + + weld::TreeView* pView = mpImpl->mxView->getWidget(); + pView->set_column_fixed_widths(aWidths); + if (mpImpl->mnSortColumn != nSortColumn) + pView->set_sort_indicator(TRISTATE_INDET, mpImpl->GetSortColumn()); + mpImpl->Resort_Impl(nSortColumn, bAscending); +} + +SvtFileView_Impl::SvtFileView_Impl(SvtFileView* pAntiImpl, weld::Window* pTopLevel, + std::unique_ptr<weld::TreeView> xTreeView, + std::unique_ptr<weld::IconView> xIconView, + Reference < XCommandEnvironment > const & xEnv, + FileViewFlags nFlags, bool bOnlyFolder) + : m_pAntiImpl ( pAntiImpl ) + , m_eAsyncActionResult ( ::svt::EnumerationResult::ERROR ) + , m_bRunningAsyncAction ( false ) + , m_bAsyncActionCancelled ( false ) + , m_pTopLevel ( pTopLevel ) + , mxView(new ViewTabListBox_Impl(std::move(xTreeView), pTopLevel, this, nFlags)) + , mxIconView(std::move(xIconView)) + , mnSortColumn ( COLUMN_TITLE ) + , mbAscending ( true ) + , mbOnlyFolder ( bOnlyFolder ) + , mnSuspendSelectCallback ( 0 ) + , mbIsFirstResort ( true ) + , aIntlWrapper ( Application::GetSettings().GetLanguageTag() ) + , maFolderImage (RID_BMP_FOLDER) + , mxCmdEnv ( xEnv ) +{ + weld::TreeView* pWidget = mxView->getWidget(); + + // set the width to something small so it's the parent that decides the final + // width + Size aSize(42, pWidget->get_height_rows(7)); + pWidget->set_size_request(aSize.Width(), aSize.Height()); + mxIconView->set_size_request(aSize.Width(), aSize.Height()); +} + +SvtFileView_Impl::~SvtFileView_Impl() +{ + Clear(); +} + +void SvtFileView_Impl::Clear() +{ + ::osl::MutexGuard aGuard( maMutex ); + + maContent.clear(); +} + +FileViewResult SvtFileView_Impl::GetFolderContent_Impl( + std::u16string_view rFolder, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList ) +{ + ::osl::ClearableMutexGuard aGuard( maMutex ); + INetURLObject aFolderObj( rFolder ); + DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" ); + + FolderDescriptor aFolder( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + aGuard.clear(); + return GetFolderContent_Impl( aFolder, pAsyncDescriptor, rDenyList ); +} + +FileViewResult SvtFileView_Impl::GetFolderContent_Impl( + const FolderDescriptor& _rFolder, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList ) +{ + DBG_TESTSOLARMUTEX(); + ::osl::ClearableMutexGuard aGuard( maMutex ); + + OSL_ENSURE( !m_xContentEnumerator.is(), "SvtFileView_Impl::GetFolderContent_Impl: still running another enumeration!" ); + m_xContentEnumerator.set(new ::svt::FileViewContentEnumerator( + mxView->GetCommandEnvironment(), maContent, maMutex)); + // TODO: should we cache and re-use this thread? + + if ( !pAsyncDescriptor ) + { + ::svt::EnumerationResult eResult = m_xContentEnumerator->enumerateFolderContentSync( _rFolder, rDenyList ); + if ( ::svt::EnumerationResult::SUCCESS == eResult ) + { + implEnumerationSuccess(); + m_xContentEnumerator.clear(); + return eSuccess; + } + m_xContentEnumerator.clear(); + return eFailure; + } + + m_bRunningAsyncAction = true; + m_bAsyncActionCancelled = false; + m_eAsyncActionResult = ::svt::EnumerationResult::ERROR; + m_aAsyncActionFinished.reset(); + + // don't (yet) set m_aCurrentAsyncActionHandler to pTimeout->aFinishHandler. + // By definition, this handler *only* gets called when the result cannot be obtained + // during the minimum wait time, so it is only set below, when needed. + m_aCurrentAsyncActionHandler = Link<void*,void>(); + + // minimum time to wait + TimeValue aTimeout; + sal_Int32 nMinTimeout = pAsyncDescriptor->nMinTimeout; + OSL_ENSURE( nMinTimeout > 0, "SvtFileView_Impl::GetFolderContent_Impl: invalid minimum timeout!" ); + if ( nMinTimeout <= 0 ) + nMinTimeout = sal_Int32( 1000 ); + aTimeout.Seconds = nMinTimeout / 1000; + aTimeout.Nanosec = ( nMinTimeout % 1000 ) * 1000000; + + m_xContentEnumerator->enumerateFolderContent( _rFolder, this ); + + // wait until the enumeration is finished + // for this, release our own mutex (which is used by the enumerator thread) + aGuard.clear(); + + ::osl::Condition::Result eResult = ::osl::Condition::result_ok; + { + // also release the SolarMutex. Not all code which is needed during the enumeration + // is Solar-Thread-Safe, in particular there is some code which needs to access + // string resources (and our resource system relies on the SolarMutex :() + SolarMutexReleaser aSolarRelease; + + // now wait. Note that if we didn't get a pAsyncDescriptor, then this is an infinite wait. + eResult = m_aAsyncActionFinished.wait( &aTimeout ); + } + + ::osl::MutexGuard aGuard2( maMutex ); + if ( ::osl::Condition::result_timeout == eResult ) + { + // maximum time to wait + OSL_ENSURE(!m_xCancelAsyncTimer, + "SvtFileView_Impl::GetFolderContent_Impl: there's still a previous timer!"); + m_xCancelAsyncTimer.set(new CallbackTimer(this)); + sal_Int32 nMaxTimeout = pAsyncDescriptor->nMaxTimeout; + OSL_ENSURE( nMaxTimeout > nMinTimeout, + "SvtFileView_Impl::GetFolderContent_Impl: invalid maximum timeout!" ); + if ( nMaxTimeout <= nMinTimeout ) + nMaxTimeout = nMinTimeout + 5000; + m_xCancelAsyncTimer->setRemainingTime( salhelper::TTimeValue( nMaxTimeout - nMinTimeout ) ); + // we already waited for nMinTimeout milliseconds, so take this into account + m_xCancelAsyncTimer->start(); + + m_aCurrentAsyncActionHandler = pAsyncDescriptor->aFinishHandler; + DBG_ASSERT( m_aCurrentAsyncActionHandler.IsSet(), "SvtFileView_Impl::GetFolderContent_Impl: nobody interested when it's finished?" ); + maEntries.clear(); + mxView->clear(); + mxIconView->clear(); + return eStillRunning; + } + + m_bRunningAsyncAction = false; + switch ( m_eAsyncActionResult ) + { + case ::svt::EnumerationResult::SUCCESS: + return eSuccess; + + case ::svt::EnumerationResult::ERROR: + return eFailure; + } + + SAL_WARN( "svtools.contnr", "SvtFileView_Impl::GetFolderContent_Impl: unreachable!" ); + return eFailure; +} + +void SvtFileView_Impl::FilterFolderContent_Impl( const OUString &rFilter ) +{ + if ( rFilter.isEmpty() || ( rFilter == ALL_FILES_FILTER ) ) + // when replacing names, there is always something to filter (no view of ".nametranslation.table") + return; + + ::osl::MutexGuard aGuard( maMutex ); + + if ( maContent.empty() ) + return; + + // collect the filter tokens + ::std::vector< WildCard > aFilters; + FilterMatch::createWildCardFilterList(rFilter,aFilters); + + + // do the filtering + maContent.erase(std::remove_if(maContent.begin(), maContent.end(), + [&aFilters](const std::unique_ptr<SortingData_Impl>& rxContent) { + if (rxContent->mbIsFolder) + return false; + // normalize the content title (we always match case-insensitive) + // 91872 - 11.09.2001 - frank.schoenheit@sun.com + OUString sCompareString = rxContent->GetFileName(); // filter works on file name, not on title! + return std::none_of(aFilters.begin(), aFilters.end(), FilterMatch(sCompareString)); + }), + maContent.end()); +} + +IMPL_LINK_NOARG(SvtFileView_Impl, ChangedHdl, weld::TreeView&, void) +{ + if (!mnSuspendSelectCallback) + m_aSelectHandler.Call(m_pAntiImpl); +} + +IMPL_LINK_NOARG(SvtFileView_Impl, SelectionChangedHdl, weld::IconView&, void) +{ + if (!mnSuspendSelectCallback) + m_aSelectHandler.Call(m_pAntiImpl); +} + +void SvtFileView_Impl::SetSelectHandler(const Link<SvtFileView*,void>& rHdl) +{ + m_aSelectHandler = rHdl; + + mxView->connect_changed(LINK(this, SvtFileView_Impl, ChangedHdl)); + mxIconView->connect_selection_changed(LINK(this, SvtFileView_Impl, SelectionChangedHdl)); +} + +IMPL_LINK_NOARG(SvtFileView_Impl, RowActivatedHdl, weld::TreeView&, bool) +{ + return maDoubleClickHandler.Call(m_pAntiImpl); +} + +IMPL_LINK_NOARG(SvtFileView_Impl, ItemActivatedHdl, weld::IconView&, bool) +{ + return maDoubleClickHandler.Call(m_pAntiImpl); +} + +void SvtFileView_Impl::SetDoubleClickHandler(const Link<SvtFileView*,bool>& rHdl) +{ + maDoubleClickHandler = rHdl; + + mxView->connect_row_activated(LINK(this, SvtFileView_Impl, RowActivatedHdl)); + mxIconView->connect_item_activated(LINK(this, SvtFileView_Impl, ItemActivatedHdl)); +} + +void SvtFileView_Impl::OpenFolder_Impl() +{ + ::osl::MutexGuard aGuard( maMutex ); + + mxView->freeze(); + mxIconView->freeze(); + maEntries.clear(); + mxView->clear(); + mxIconView->clear(); + + for (auto const& elem : maContent) + { + if (mbOnlyFolder && !elem->mbIsFolder) + continue; + + // insert entry and set user data + maEntries.emplace_back(std::make_unique<SvtContentEntry>(elem->maTargetURL, elem->mbIsFolder)); + OUString sId(weld::toId(maEntries.back().get())); + mxView->append(sId, elem->maDisplayName, elem->maType, elem->maDisplaySize, elem->maDisplayDate, elem->maImage); + mxIconView->append(sId, elem->maDisplayName, elem->maImage); + } + + ++mnSuspendSelectCallback; + mxView->thaw(); + + //set sort indicator + weld::TreeView* pView = mxView->getWidget(); + pView->set_sort_indicator(mbAscending ? TRISTATE_TRUE : TRISTATE_FALSE, GetSortColumn()); + + mxIconView->thaw(); + --mnSuspendSelectCallback; + + ResetCursor(); +} + +void SvtFileView_Impl::ResetCursor() +{ + if (mxView->get_visible()) + { + std::unique_ptr<weld::TreeIter> xFirst = mxView->make_iterator(); + if (mxView->get_iter_first(*xFirst)) + { + // set cursor to the first entry + mxView->set_cursor(*xFirst); + } + // deselect + mxView->unselect_all(); + } + else + { + std::unique_ptr<weld::TreeIter> xFirst = mxIconView->make_iterator(); + if (mxIconView->get_iter_first(*xFirst)) + { + // set cursor to the first entry + mxIconView->set_cursor(*xFirst); + } + // deselect + mxIconView->unselect_all(); + } +} + +void SvtFileView_Impl::CancelRunningAsyncAction() +{ + DBG_TESTSOLARMUTEX(); + ::osl::MutexGuard aGuard( maMutex ); + if ( !m_xContentEnumerator.is() ) + return; + + m_bAsyncActionCancelled = true; + m_xContentEnumerator->cancel(); + m_bRunningAsyncAction = false; + + m_xContentEnumerator.clear(); + if ( m_xCancelAsyncTimer.is() && m_xCancelAsyncTimer->isTicking() ) + m_xCancelAsyncTimer->stop(); + m_xCancelAsyncTimer.clear(); +} + + +void SvtFileView_Impl::onTimeout() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( maMutex ); + if ( !m_bRunningAsyncAction ) + // there might have been a race condition while we waited for the mutex + return; + + CancelRunningAsyncAction(); + + if ( m_aCurrentAsyncActionHandler.IsSet() ) + { + Application::PostUserEvent( m_aCurrentAsyncActionHandler, reinterpret_cast< void* >( eTimeout ) ); + m_aCurrentAsyncActionHandler = Link<void*,void>(); + } +} + + +void SvtFileView_Impl::enumerationDone( ::svt::EnumerationResult eResult ) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( maMutex ); + + m_xContentEnumerator.clear(); + if ( m_xCancelAsyncTimer.is() && m_xCancelAsyncTimer->isTicking() ) + m_xCancelAsyncTimer->stop(); + m_xCancelAsyncTimer.clear(); + + if ( m_bAsyncActionCancelled ) + // this is to prevent race conditions + return; + + m_eAsyncActionResult = eResult; + m_bRunningAsyncAction = false; + + m_aAsyncActionFinished.set(); + + if ( svt::EnumerationResult::SUCCESS == eResult ) + implEnumerationSuccess(); + + if ( m_aCurrentAsyncActionHandler.IsSet() ) + { + Application::PostUserEvent( m_aCurrentAsyncActionHandler, reinterpret_cast< void* >( m_eAsyncActionResult ) ); + m_aCurrentAsyncActionHandler = Link<void*,void>(); + } +} + + +void SvtFileView_Impl::implEnumerationSuccess() +{ + FilterFolderContent_Impl( maCurrentFilter ); + SortFolderContent_Impl(); + CreateDisplayText_Impl(); + OpenFolder_Impl(); + maOpenDoneLink.Call( m_pAntiImpl ); +} + +OUString SvtFileView_Impl::ReplaceTabWithString(const OUString& rValue) +{ + OUString const aTab( "\t" ); + OUString const aTabString( "%09" ); + + sal_Int32 iPos; + OUString aValue(rValue); + while ( ( iPos = aValue.indexOf( aTab ) ) >= 0 ) + aValue = aValue.replaceAt( iPos, 1, aTabString ); + return aValue; +} + +void SvtFileView_Impl::CreateDisplayText_Impl() +{ + ::osl::MutexGuard aGuard( maMutex ); + + OUString const aDateSep( ", " ); + + for (auto const& elem : maContent) + { + // title, type, size, date + elem->maDisplayName = ReplaceTabWithString(elem->GetTitle()); + // folders don't have a size + if ( ! elem->mbIsFolder ) + elem->maDisplaySize = CreateExactSizeText( elem->maSize ); + // set the date, but volumes have no date + if ( ! elem->mbIsFolder || ! elem->mbIsVolume ) + { + SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + elem->maDisplayDate = rLocaleData.getDate( elem->maModDate ) + + aDateSep + + rLocaleData.getTime( elem->maModDate, false ); + } + + // detect image + if ( elem->mbIsFolder ) + { + ::svtools::VolumeInfo aVolInfo( elem->mbIsVolume, elem->mbIsRemote, + elem->mbIsRemoveable, elem->mbIsFloppy, + elem->mbIsCompactDisc ); + elem->maImage = SvFileInformationManager::GetFolderImageId(aVolInfo); + } + else + elem->maImage = SvFileInformationManager::GetFileImageId(INetURLObject(elem->maTargetURL)); + } +} + +void SvtFileView_Impl::Resort_Impl( sal_Int16 nColumn, bool bAscending ) +{ + // TODO: IconView () + ::osl::MutexGuard aGuard( maMutex ); + + if ( ( nColumn == mnSortColumn ) && + ( bAscending == mbAscending ) ) + return; + + // reset the quick search index + mxView->ResetQuickSearch_Impl( nullptr ); + + std::unique_ptr<weld::TreeIter> xEntry(mxView->make_iterator()); + bool bEntry = mxView->get_cursor(xEntry.get()); + + OUString aEntryURL; + if (bEntry && !mxView->get_id(*xEntry).isEmpty()) + aEntryURL = weld::fromId<SvtContentEntry*>(mxView->get_id(*xEntry))->maURL; + + mnSortColumn = nColumn; + mbAscending = bAscending; + + SortFolderContent_Impl(); + OpenFolder_Impl(); + + if ( !mbIsFirstResort ) + { + int nPos = GetEntryPos( aEntryURL ); + if (nPos != -1 && nPos < mxView->n_children()) + { + ++mnSuspendSelectCallback; // #i15668# + mxView->set_cursor(nPos); + --mnSuspendSelectCallback; + } + } + else + mbIsFirstResort = false; +} + +static bool gbAscending = true; +static sal_Int16 gnColumn = COLUMN_TITLE; +static const CollatorWrapper* pCollatorWrapper = nullptr; + +/* this function returns true, if aOne is less than aTwo +*/ +static bool CompareSortingData_Impl( std::unique_ptr<SortingData_Impl> const & aOne, std::unique_ptr<SortingData_Impl> const & aTwo ) +{ + DBG_ASSERT( pCollatorWrapper, "*CompareSortingData_Impl(): Can't work this way!" ); + + sal_Int32 nComp; + bool bRet = false; + bool bEqual = false; + + if ( aOne->mbIsFolder != aTwo->mbIsFolder ) + { + bRet = aOne->mbIsFolder; + + // !!! pb: #100376# folder always on top + if ( !gbAscending ) + bRet = !bRet; + } + else + { + switch ( gnColumn ) + { + case COLUMN_TITLE: + // compare case insensitive first + nComp = pCollatorWrapper->compareString( aOne->GetLowerTitle(), aTwo->GetLowerTitle() ); + + if ( nComp == 0 ) + nComp = pCollatorWrapper->compareString( aOne->GetTitle(), aTwo->GetTitle() ); + + if ( nComp < 0 ) + bRet = true; + else if ( nComp > 0 ) + bRet = false; + else + bEqual = true; + break; + case COLUMN_TYPE: + nComp = pCollatorWrapper->compareString( aOne->maType, aTwo->maType ); + if ( nComp < 0 ) + bRet = true; + else if ( nComp > 0 ) + bRet = false; + else + bEqual = true; + break; + case COLUMN_SIZE: + if ( aOne->maSize < aTwo->maSize ) + bRet = true; + else if ( aOne->maSize > aTwo->maSize ) + bRet = false; + else + bEqual = true; + break; + case COLUMN_DATE: + if ( aOne->maModDate < aTwo->maModDate ) + bRet = true; + else if ( aOne->maModDate > aTwo->maModDate ) + bRet = false; + else + bEqual = true; + break; + default: + SAL_INFO( "svtools.contnr", "CompareSortingData_Impl: Compare unknown type!" ); + bRet = false; + } + } + + // when the two elements are equal, we must not return sal_True (which would + // happen if we just return ! ( a < b ) when not sorting ascending ) + if ( bEqual ) + return false; + + return gbAscending ? bRet : !bRet; +} + + +void SvtFileView_Impl::SortFolderContent_Impl() +{ + ::osl::MutexGuard aGuard( maMutex ); + + if ( maContent.size() > 1 ) + { + gbAscending = mbAscending; + gnColumn = mnSortColumn; + pCollatorWrapper = aIntlWrapper.getCaseCollator(); + + std::stable_sort( maContent.begin(), maContent.end(), CompareSortingData_Impl ); + + pCollatorWrapper = nullptr; + } +} + + +void SvtFileView_Impl::EntryRemoved( std::u16string_view rURL ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + maContent.erase(std::find_if(maContent.begin(), maContent.end(), + [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; })); +} + + +void SvtFileView_Impl::EntryRenamed( OUString& rURL, + const OUString& rTitle ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + auto aFoundElem = std::find_if(maContent.begin(), maContent.end(), + [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; }); + if (aFoundElem != maContent.end()) + { + (*aFoundElem)->SetNewTitle( rTitle ); + (*aFoundElem)->maDisplayName = ReplaceTabWithString(rTitle); + + INetURLObject aURLObj( rURL ); + aURLObj.setName( rTitle, INetURLObject::EncodeMechanism::All ); + + rURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + (*aFoundElem)->maTargetURL = rURL; + } +} + +const SortingData_Impl& SvtFileView_Impl::FolderInserted( const OUString& rURL, const OUString& rTitle ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + std::unique_ptr<SortingData_Impl> pData(new SortingData_Impl); + + pData->SetNewTitle( rTitle ); + pData->maSize = 0; + pData->mbIsFolder = true; + pData->maTargetURL = rURL; + + ::svtools::VolumeInfo aVolInfo; + pData->maType = SvFileInformationManager::GetFolderDescription( aVolInfo ); + pData->maImage = SvFileInformationManager::GetFolderImageId( aVolInfo ); + + // title, type, size, date + pData->maDisplayName = ReplaceTabWithString(pData->GetTitle()); + // set the date + SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + pData->maDisplayDate = rLocaleData.getDate( pData->maModDate ) + + ", " + + rLocaleData.getTime( pData->maModDate ); + + maContent.push_back( std::move(pData) ); + + return *maContent.back(); +} + +int SvtFileView_Impl::GetEntryPos(std::u16string_view rURL) +{ + ::osl::MutexGuard aGuard( maMutex ); + + auto aFoundElem = std::find_if(maContent.begin(), maContent.end(), + [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; }); + return aFoundElem != maContent.end() ? std::distance(maContent.begin(), aFoundElem) : -1; +} + +void SvtFileView_Impl::SetViewMode( FileViewMode eMode ) +{ + switch ( eMode ) + { + case eDetailedList: + mxView->show(); + mxIconView->hide(); + break; + + case eIcon: + mxView->hide(); + mxIconView->show(); + break; + + default: + mxView->show(); + mxIconView->hide(); + }; +} + +bool SvtFileView_Impl::SearchNextEntry( sal_uInt32& nIndex, std::u16string_view rTitle, bool bWrapAround ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + sal_uInt32 nEnd = maContent.size(); + sal_uInt32 nStart = nIndex; + while ( nIndex < nEnd ) + { + SortingData_Impl* pData = maContent[ nIndex ].get(); + if ( pData->GetLowerTitle().startsWith( rTitle ) ) + return true; + ++nIndex; + } + + if ( bWrapAround ) + { + nIndex = 0; + while ( nIndex < nEnd && nIndex <= nStart ) + { + SortingData_Impl* pData = maContent[ nIndex ].get(); + if ( pData->GetLowerTitle().startsWith( rTitle ) ) + return true; + ++nIndex; + } + } + + return false; +} + +namespace { + void SAL_CALL CallbackTimer::onShot() + { + OSL_ENSURE( m_pTimeoutHandler, "CallbackTimer::onShot: nobody interested in?" ); + SvtFileView_Impl* pHandler( m_pTimeoutHandler ); + if ( pHandler ) + pHandler->onTimeout(); + } +} + +void SvtFileView::selected_foreach(const std::function<bool(weld::TreeIter&)>& func) +{ + if (mpImpl->mxView->get_visible()) + mpImpl->mxView->selected_foreach(func); + else + mpImpl->mxIconView->selected_foreach(func); +} + +weld::Widget* SvtFileView::identifier() const +{ + return mpImpl->mxView->getWidget(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fileview.hxx b/fpicker/source/office/fileview.hxx new file mode 100644 index 000000000..e2f132029 --- /dev/null +++ b/fpicker/source/office/fileview.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 <memory> +#include <com/sun/star/uno/Sequence.h> +#include <utility> +#include <vcl/weld.hxx> +#include <rtl/ustring.hxx> + +namespace com :: sun :: star :: ucb { class XContent; } + +// class SvtFileView ----------------------------------------------------- + +class SvtFileView_Impl; +struct SvtContentEntry; + +/// the result of an action in the FileView +enum FileViewResult +{ + eSuccess, + eFailure, + eTimeout, + eStillRunning +}; + +enum FileViewMode +{ + eDetailedList, + eIcon +}; + +/// describes parameters for doing an action on the FileView asynchronously +struct FileViewAsyncAction +{ + sal_uInt32 nMinTimeout; /// minimum time to wait for a result, in milliseconds + sal_uInt32 nMaxTimeout; /// maximum time to wait for a result, in milliseconds, until eTimeout is returned + Link<void*,void> aFinishHandler; /// the handler to be called when the action is finished. Called in every case, no matter of the result + + FileViewAsyncAction() : nMinTimeout(0), nMaxTimeout (0) + { + } +}; + +class SvtFileView +{ +private: + std::unique_ptr<SvtFileView_Impl> mpImpl; + css::uno::Sequence<OUString> maDenyList; + + DECL_LINK(HeaderSelect_Impl, int, void); + +public: + SvtFileView(weld::Window* pTopLevel, + std::unique_ptr<weld::TreeView> xTreeView, + std::unique_ptr<weld::IconView> xIconView, + bool bOnlyFolder, bool bMultiSelection, bool bShowType = true); + ~SvtFileView(); + + void SetViewMode( FileViewMode eMode ); + + const OUString& GetViewURL() const; + OUString GetURL(const weld::TreeIter& rEntry) const; + OUString GetCurrentURL() const; + + bool GetParentURL( OUString& _rParentURL ) const; + void CreatedFolder( const OUString& rUrl, const OUString& rNewFolder ); + + void set_help_id(const OString& rHelpId); + OString get_help_id() const; + + void grab_focus(); + bool has_focus() const; + + OUString get_selected_text() const; + + weld::Widget* identifier() const; // just to uniquely identify this widget + + /** initialize the view with the content of a folder given by URL, and apply an immediate filter + + @param rFolderURL + the URL of the folder whose content is to be read + @param rFilter + the initial filter to be applied + @param pAsyncDescriptor + If not <NULL/>, this struct describes the parameters for doing the + action asynchronously. + */ + FileViewResult Initialize( + const OUString& rFolderURL, + const OUString& rFilter, + const FileViewAsyncAction* pAsyncDescriptor, + const css::uno::Sequence< OUString >& rDenyList + ); + + /** reads the current content of the current folder again, and applies the given filter to it + + Note 1: The folder is really read a second time. This implies that any new elements (which were + not present when you called Initialize the last time) are now displayed. + + Note 2: This method must not be called when you previously initialized the view from a sequence + of strings, or a UNO content object. + + @param rFilter + the filter to be applied + @param pAsyncDescriptor + If not <NULL/>, this struct describes the parameters for doing the + action asynchronously. + */ + FileViewResult ExecuteFilter( + const OUString& rFilter, + const FileViewAsyncAction* pAsyncDescriptor + ); + + /** cancels a running async action (if any) + + @seealso Initialize + @seealso ExecuteFilter + @seealso FileViewAsyncAction + */ + void CancelRunningAsyncAction(); + + /** initializes the view with the parent folder of the current folder + + @param rNewURL + the URL of the folder which we just navigated to + @param pAsyncDescriptor + If not <NULL/>, this struct describes the parameters for doing the + action asynchronously. + */ + FileViewResult PreviousLevel( + const FileViewAsyncAction* pAsyncDescriptor + ); + + void SetNoSelection(); + + void SetSelectHdl( const Link<SvtFileView*,void>& rHdl ); + void SetDoubleClickHdl( const Link<SvtFileView*,bool>& rHdl ); + void SetOpenDoneHdl( const Link<SvtFileView*,void>& rHdl ); + + sal_uInt32 GetSelectionCount() const; + SvtContentEntry* FirstSelected() const; + + void selected_foreach(const std::function<bool(weld::TreeIter&)>& func); + + void EnableDelete( bool bEnable ); + + // save and load column size and sort order + OUString GetConfigString() const; + void SetConfigString( std::u16string_view rCfgStr ); + + void EndInplaceEditing(); + + ::std::vector< SvtContentEntry > GetContent(); +}; + +// struct SvtContentEntry ------------------------------------------------ + +struct SvtContentEntry +{ + bool mbIsFolder; + OUString maURL; + + SvtContentEntry( OUString aURL, bool bIsFolder ) : + mbIsFolder( bIsFolder ), maURL(std::move( aURL )) {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/foldertree.cxx b/fpicker/source/office/foldertree.cxx new file mode 100644 index 000000000..9b704d6c4 --- /dev/null +++ b/fpicker/source/office/foldertree.cxx @@ -0,0 +1,178 @@ +/* -*- 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/. + */ + +#include <comphelper/processfactory.hxx> +#include <tools/urlobj.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <com/sun/star/task/InteractionHandler.hpp> +#include "contentenumeration.hxx" +#include "foldertree.hxx" +#include <bitmaps.hlst> + +using namespace ::com::sun::star::task; + +using namespace ::svt; + +FolderTree::FolderTree(std::unique_ptr<weld::TreeView> xTreeView, weld::Window* pTopLevel) + : m_xTreeView(std::move(xTreeView)) + , m_xScratchIter(m_xTreeView->make_iterator()) + , m_pTopLevel(pTopLevel) +{ + m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 24, + m_xTreeView->get_height_rows(7)); + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xInteractionHandler( + InteractionHandler::createWithParent(xContext, pTopLevel->GetXWindow()), UNO_QUERY_THROW); + m_xEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, Reference< XProgressHandler >() ); + + m_xTreeView->connect_expanding(LINK(this, FolderTree, RequestingChildrenHdl)); +} + +IMPL_LINK(FolderTree, RequestingChildrenHdl, const weld::TreeIter&, rEntry, bool) +{ + weld::WaitObject aWait(m_pTopLevel); + + FillTreeEntry(rEntry); + + return true; +} + +void FolderTree::InsertRootEntry(const OUString& rId, const OUString& rRootLabel) +{ + m_xTreeView->insert(nullptr, -1, &rRootLabel, &rId, nullptr, nullptr, + true, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, RID_BMP_FOLDER); + m_xTreeView->set_cursor(*m_xScratchIter); +} + +void FolderTree::FillTreeEntry(const weld::TreeIter& rEntry) +{ + OUString sURL = m_xTreeView->get_id(rEntry); + OUString sFolderImage(RID_BMP_FOLDER); + + if (m_sLastUpdatedDir != sURL) + { + while (m_xTreeView->iter_has_child(rEntry)) + { + std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(&rEntry)); + (void)m_xTreeView->iter_children(*xChild); + m_xTreeView->remove(*xChild); + } + + ::std::vector< std::unique_ptr<SortingData_Impl> > aContent; + + ::rtl::Reference< ::svt::FileViewContentEnumerator > + xContentEnumerator(new FileViewContentEnumerator( + m_xEnv, aContent, m_aMutex)); + + FolderDescriptor aFolder(sURL); + + EnumerationResult eResult = + xContentEnumerator->enumerateFolderContentSync( aFolder, m_aDenyList ); + + if (EnumerationResult::SUCCESS == eResult) + { + for(const auto & i : aContent) + { + if (!i->mbIsFolder) + continue; + m_xTreeView->insert(&rEntry, -1, &i->GetTitle(), &i->maTargetURL, + nullptr, nullptr, true, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, sFolderImage); + } + } + } + else + { + // this dir was updated recently + // next time read this remote folder + m_sLastUpdatedDir.clear(); + } +} + +void FolderTree::FillTreeEntry( const OUString & rUrl, const ::std::vector< std::pair< OUString, OUString > >& rFolders ) +{ + SetTreePath(rUrl); + + std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator()); + bool bParent = m_xTreeView->get_cursor(xParent.get()); + + if (!bParent || m_xTreeView->get_row_expanded(*xParent)) + return; + + OUString sFolderImage(RID_BMP_FOLDER); + while (m_xTreeView->iter_has_child(*xParent)) + { + std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(xParent.get())); + (void)m_xTreeView->iter_children(*xChild); + m_xTreeView->remove(*xChild); + } + + for (auto const& folder : rFolders) + { + m_xTreeView->insert(xParent.get(), -1, &folder.first, &folder.second, + nullptr, nullptr, true, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, sFolderImage); + } + + m_sLastUpdatedDir = rUrl; + m_xTreeView->expand_row(*xParent); +} + +void FolderTree::SetTreePath( std::u16string_view sUrl ) +{ + INetURLObject aUrl( sUrl ); + aUrl.setFinalSlash(); + + OUString sPath = aUrl.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ); + + std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator()); + bool bEntry = m_xTreeView->get_iter_first(*xEntry); + bool bEnd = false; + + while (bEntry && !bEnd) + { + if (!m_xTreeView->get_id(*xEntry).isEmpty()) + { + OUString sNodeUrl = m_xTreeView->get_id(*xEntry); + + INetURLObject aUrlObj( sNodeUrl ); + aUrlObj.setFinalSlash(); + + sNodeUrl = aUrlObj.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ); + + if( sPath == sNodeUrl ) + { + m_xTreeView->select(*xEntry); + bEnd = true; + } + else if( sPath.startsWith( sNodeUrl ) ) + { + if (!m_xTreeView->get_row_expanded(*xEntry)) + m_xTreeView->expand_row(*xEntry); + + bEntry = m_xTreeView->iter_children(*xEntry); + } + else + { + bEntry = m_xTreeView->iter_next_sibling(*xEntry); + } + } + else + break; + } +} + +void FolderTree::SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) +{ + m_aDenyList = rDenyList; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/foldertree.hxx b/fpicker/source/office/foldertree.hxx new file mode 100644 index 000000000..e1ba25699 --- /dev/null +++ b/fpicker/source/office/foldertree.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/. + */ + +#pragma once + +#include <com/sun/star/uno/Sequence.hxx> +#include <vcl/weld.hxx> + +namespace com :: sun :: star :: ucb { class XCommandEnvironment; } + +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; + +class FolderTree +{ +private: + std::unique_ptr<weld::TreeView> m_xTreeView; + std::unique_ptr<weld::TreeIter> m_xScratchIter; + weld::Window* m_pTopLevel; + Reference< XCommandEnvironment > m_xEnv; + ::osl::Mutex m_aMutex; + Sequence< OUString > m_aDenyList; + + OUString m_sLastUpdatedDir; + + DECL_LINK(RequestingChildrenHdl, const weld::TreeIter&, bool); + +public: + FolderTree(std::unique_ptr<weld::TreeView> xTreeView, weld::Window* pTopLevel); + + void clear() { m_xTreeView->clear(); } + + void connect_changed(const Link<weld::TreeView&, void>& rLink) { m_xTreeView->connect_changed(rLink); } + + void InsertRootEntry(const OUString& rId, const OUString& rRootLabel); + void FillTreeEntry(const weld::TreeIter& rEntry); + void FillTreeEntry(const OUString & rUrl, const ::std::vector< std::pair< OUString, OUString > >& rFolders); + void SetTreePath(std::u16string_view sUrl); + void SetDenyList(const css::uno::Sequence< OUString >& rDenyList); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fpdialogbase.hxx b/fpicker/source/office/fpdialogbase.hxx new file mode 100644 index 000000000..20ab36297 --- /dev/null +++ b/fpicker/source/office/fpdialogbase.hxx @@ -0,0 +1,112 @@ +/* -*- 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 <vcl/weld.hxx> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "pickercallbacks.hxx" + +class SvTabListBox; +class SvtFileView; +class SvtFileDialogFilter_Impl; + + +enum class PickerFlags { + NONE = 0x000000, + AutoExtension = 0x000001, + FilterOptions = 0x000002, + ShowVersions = 0x000004, + InsertAsLink = 0x000008, + ShowPreview = 0x000010, + Templates = 0x000020, + PlayButton = 0x000040, + Selection = 0x000080, + ImageTemplate = 0x000100, + PathDialog = 0x000200, + Open = 0x000400, + SaveAs = 0x000800, + Password = 0x001000, + ReadOnly = 0x002000, + MultiSelection = 0x004000, + ImageAnchor = 0x008000, +}; +namespace o3tl { + template<> struct typed_flags<PickerFlags> : is_typed_flags<PickerFlags, 0x00ffff> {}; +} + +inline constexpr OUStringLiteral FILEDIALOG_FILTER_ALL = u"*.*"; + +// SvtFileDialog_Base + +class SvtFileDialog_Base : public weld::GenericDialogController, public ::svt::IFilePickerController +{ +public: + SvtFileDialog_Base(weld::Window* pParent, const OUString& rUIXMLDescription, const OString& rID) + : weld::GenericDialogController(pParent, rUIXMLDescription, rID) + { + } + + virtual bool PrepareExecute() { return true ; } + + virtual SvtFileView* GetView() = 0; + + virtual void SetHasFilename( bool bHasFilename ) = 0; + virtual void SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) = 0; + virtual const css::uno::Sequence< OUString >& GetDenyList() const = 0; + virtual void SetStandardDir( const OUString& rStdDir ) = 0; + virtual const OUString& GetStandardDir() const = 0; + virtual void SetPath( const OUString& rNewURL ) = 0; + virtual const OUString& GetPath() = 0; + virtual std::vector<OUString> GetPathList() const = 0; + virtual bool ContentIsFolder( const OUString& rURL ) = 0; + + virtual OUString getCurrentFileText() const = 0; + virtual void setCurrentFileText( const OUString& rText, bool bSelectAll = false ) = 0; + + virtual void AddFilter( const OUString& rFilter, const OUString& rType ) = 0; + virtual void AddFilterGroup( const OUString& _rFilter, + const css::uno::Sequence< css::beans::StringPair >& rFilters ) = 0; + virtual OUString GetCurFilter() const = 0; + virtual void SetCurFilter( const OUString& rFilter ) = 0; + virtual void FilterSelect() = 0; + + virtual void SetFileCallback( ::svt::IFilePickerListener *pNotifier ) = 0; + virtual void onAsyncOperationStarted() = 0; + virtual void onAsyncOperationFinished() = 0; + virtual void UpdateControls( const OUString& rURL ) = 0; + + virtual void EnableAutocompletion( bool _bEnable = true ) = 0; + + virtual sal_Int32 getAvailableWidth() = 0; + virtual sal_Int32 getAvailableHeight() = 0; + + virtual void setImage( const css::uno::Any& rImage ) = 0; + + virtual bool getShowState() = 0; +}; + +#define FILE_SELECTION_CHANGED 1 +#define DIRECTORY_CHANGED 2 +#define CTRL_STATE_CHANGED 4 +#define DIALOG_SIZE_CHANGED 5 + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fpinteraction.cxx b/fpicker/source/office/fpinteraction.cxx new file mode 100644 index 000000000..9d3591d75 --- /dev/null +++ b/fpicker/source/office/fpinteraction.cxx @@ -0,0 +1,142 @@ +/* -*- 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 "fpinteraction.hxx" +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/task/XInteractionDisapprove.hpp> +#include <com/sun/star/task/XInteractionRetry.hpp> + +#include <sal/log.hxx> +#include <utility> + + +namespace svt +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::task; + using namespace ::com::sun::star::ucb; + + OFilePickerInteractionHandler::OFilePickerInteractionHandler( css::uno::Reference< css::task::XInteractionHandler > _xMaster ) + :m_xMaster(std::move( _xMaster )) + ,m_bUsed( false ) + ,m_eInterceptions( OFilePickerInteractionHandler::E_NOINTERCEPTION ) + { + SAL_WARN_IF( !m_xMaster.is(), "fpicker.office", "OFilePickerInteractionHandler::OFilePickerInteractionHandler: invalid master handler!" ); + } + + + OFilePickerInteractionHandler::~OFilePickerInteractionHandler( ) + { + } + + + void SAL_CALL OFilePickerInteractionHandler::handle( const Reference< XInteractionRequest >& _rxRequest ) + { + if (!_rxRequest.is()) + return; + + m_bUsed = true; + + // extract some generic continuations ... might we need it later + // if something goes wrong. + Reference< XInteractionAbort > xAbort; + Reference< XInteractionApprove > xApprove; + Reference< XInteractionDisapprove > xDisapprove; + Reference< XInteractionRetry > xRetry; + + const Sequence< Reference< XInteractionContinuation > > lConts = _rxRequest->getContinuations(); + for (const Reference< XInteractionContinuation >& rCont : lConts) + { + if (!xAbort.is()) + xAbort.set(rCont, UNO_QUERY); + if (!xApprove.is()) + xApprove.set(rCont, UNO_QUERY); + if (!xDisapprove.is()) + xDisapprove.set(rCont, UNO_QUERY); + if (!xRetry.is()) + xRetry.set(rCont, UNO_QUERY); + } + + // safe the original request for later analyzing! + m_aException = _rxRequest->getRequest(); + + // intercept some interesting interactions + + // The "does not exist" interaction will be suppressed here completely. + if (m_eInterceptions & OFilePickerInteractionHandler::E_DOESNOTEXIST) + { + InteractiveIOException aIoException; + if ( + (m_aException >>= aIoException ) && + (IOErrorCode_NOT_EXISTING == aIoException.Code) + ) + { + if (xAbort.is()) + xAbort->select(); + return; + } + } + + // no master => abort this operation ... + if (!m_xMaster.is()) + { + if (xAbort.is()) + xAbort->select(); + return; + } + + // forward it to our master - so he can handle all + // not interesting interactions :-) + m_xMaster->handle(_rxRequest); + } + + + void OFilePickerInteractionHandler::enableInterceptions( EInterceptedInteractions eInterceptions ) + { + m_eInterceptions = eInterceptions; + } + + + void OFilePickerInteractionHandler::resetUseState() + { + m_bUsed = false; + } + + + void OFilePickerInteractionHandler::forgetRequest() + { + m_aException = Any(); + } + + + bool OFilePickerInteractionHandler::wasAccessDenied() const + { + InteractiveIOException aIoException; + return (m_aException >>= aIoException ) && + (IOErrorCode_ACCESS_DENIED == aIoException.Code); + } + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fpinteraction.hxx b/fpicker/source/office/fpinteraction.hxx new file mode 100644 index 000000000..84f4a43d6 --- /dev/null +++ b/fpicker/source/office/fpinteraction.hxx @@ -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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/task/XInteractionHandler.hpp> + + +namespace svt +{ + + + //= OFilePickerInteractionHandler + + typedef ::cppu::WeakImplHelper < css::task::XInteractionHandler + > OFilePickerInteractionHandler_Base; + + /** an InteractionHandler implementation which extends another handler with some customizability + */ + class OFilePickerInteractionHandler final : public OFilePickerInteractionHandler_Base + { + public: + /** flags, which indicates special handled interactions + These values will be used combined as flags - so they must + in range [2^n]! + */ + enum EInterceptedInteractions + { + E_NOINTERCEPTION = 0, + E_DOESNOTEXIST = 1 + // next values [2,4,8,16 ...]! + }; + + private: + css::uno::Reference< css::task::XInteractionHandler > m_xMaster; // our master handler + css::uno::Any m_aException; // the last handled request + bool m_bUsed; // indicates using of this interaction handler instance + EInterceptedInteractions m_eInterceptions; // enable/disable interception of some special interactions + + public: + explicit OFilePickerInteractionHandler( css::uno::Reference< css::task::XInteractionHandler > _xMaster ); + + // some generic functions + void enableInterceptions( EInterceptedInteractions eInterceptions ); + bool wasUsed () const { return m_bUsed; } + void resetUseState (); + void forgetRequest (); + + // functions to analyze last cached request + bool wasAccessDenied() const; + + private: + // XInteractionHandler + virtual void SAL_CALL handle( const css::uno::Reference< css::task::XInteractionRequest >& _rxRequest ) override; + + virtual ~OFilePickerInteractionHandler() override; + }; + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fps_office.component b/fpicker/source/office/fps_office.component new file mode 100644 index 000000000..cdf5f8247 --- /dev/null +++ b/fpicker/source/office/fps_office.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.svtools.OfficeFilePicker" + constructor="fpicker_SvtFilePicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.OfficeFilePicker"/> + </implementation> + <implementation name="com.sun.star.svtools.RemoteFilePicker" + constructor="fpicker_SvtRemoteFilePicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.RemoteFilePicker"/> + </implementation> + <implementation name="com.sun.star.svtools.OfficeFolderPicker" + constructor="fpicker_SvtFolderPicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.OfficeFolderPicker"/> + </implementation> +</component> diff --git a/fpicker/source/office/fpsmartcontent.cxx b/fpicker/source/office/fpsmartcontent.cxx new file mode 100644 index 000000000..3f9cad855 --- /dev/null +++ b/fpicker/source/office/fpsmartcontent.cxx @@ -0,0 +1,325 @@ +/* -*- 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 "fpsmartcontent.hxx" + +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/XContent.hpp> + +#include <comphelper/processfactory.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <tools/diagnose_ex.h> + + +namespace svt +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::task; + using namespace ::com::sun::star::ucb; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + + + //= SmartContent + + + SmartContent::SmartContent() + :m_eState( NOT_BOUND ) + { + } + + + SmartContent::SmartContent( const OUString& _rInitialURL ) + :m_eState( NOT_BOUND ) + { + bindTo( _rInitialURL ); + } + + + SmartContent::~SmartContent() + { + /* This destructor originally contained the following blurb: "Do + not delete the content. Because the content will be used by + the cache." This is just plain silly, because it relies on + the provider caching created contents (which is done by + ucbhelper::ContentProviderImplHelper, but we do not actually + expect all providers to use that, right?) Otherwise we are + just leaking memory. + + TODO: If there is real need for caching the content, it must + be done here. + */ + } + + + void SmartContent::enableOwnInteractionHandler(::svt::OFilePickerInteractionHandler::EInterceptedInteractions eInterceptions) + { + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xGlobalInteractionHandler( + InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW ); + + m_xOwnInteraction = new ::svt::OFilePickerInteractionHandler(xGlobalInteractionHandler); + m_xOwnInteraction->enableInterceptions(eInterceptions); + + m_xCmdEnv = new ::ucbhelper::CommandEnvironment( m_xOwnInteraction, Reference< XProgressHandler >() ); + } + + + void SmartContent::enableDefaultInteractionHandler() + { + m_xOwnInteraction.clear(); + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xGlobalInteractionHandler( + InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW ); + m_xCmdEnv = new ucbhelper::CommandEnvironment( xGlobalInteractionHandler, Reference< XProgressHandler >() ); + } + + + ::svt::OFilePickerInteractionHandler* SmartContent::getOwnInteractionHandler() const + { + return m_xOwnInteraction.get(); + } + + + SmartContent::InteractionHandlerType SmartContent::queryCurrentInteractionHandler() const + { + if (m_xOwnInteraction.is()) + return IHT_OWN; + + if (!m_xCmdEnv.is()) + return IHT_NONE; + + return IHT_DEFAULT; + } + + + void SmartContent::disableInteractionHandler() + { + m_xOwnInteraction.clear(); + m_xCmdEnv.clear(); + } + + + void SmartContent::bindTo( const OUString& _rURL ) + { + if ( getURL() == _rURL ) + // nothing to do, regardless of the state + return; + + m_pContent.reset(); + m_eState = INVALID; // default to INVALID + m_sURL = _rURL; + + if ( !m_sURL.isEmpty() ) + { + try + { + m_pContent.reset( new ::ucbhelper::Content( _rURL, m_xCmdEnv, comphelper::getProcessComponentContext() ) ); + m_eState = UNKNOWN; + // from now on, the state is unknown -> we cannot know for sure if the content + // is really valid (some UCP's only tell this when asking for properties, not upon + // creation) + } + catch( const ContentCreationException& ) + { + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "fpicker", "SmartContent::bindTo: unexpected exception caught!" ); + } + } + else + { + m_eState = NOT_BOUND; + } + + + // don't forget to reset the may internal used interaction handler ... + // But do it only for our own specialized interaction helper! + ::svt::OFilePickerInteractionHandler* pHandler = getOwnInteractionHandler(); + if (pHandler) + { + pHandler->resetUseState(); + pHandler->forgetRequest(); + } + } + + + bool SmartContent::implIs( const OUString& _rURL, Type _eType ) + { + // bind to this content + bindTo( _rURL ); + + // did we survive this? + if ( isInvalid() || !isBound() ) + return false; + + assert( m_pContent && "SmartContent::implIs: inconsistence!" ); + // if, after a bindTo, we don't have a content, then we should be INVALID, or at least + // NOT_BOUND (the latter happens, for example, if somebody tries to ask for an empty URL) + + bool bIs = false; + try + { + if ( Folder == _eType ) + bIs = m_pContent->isFolder(); + else + bIs = m_pContent->isDocument(); + + // from here on, we definitely know that the content is valid + m_eState = VALID; + } + catch( const Exception& ) + { + // now we're definitely invalid + m_eState = INVALID; + } + return bIs; + } + + + void SmartContent::getTitle( OUString& /* [out] */ _rTitle ) + { + if ( !isBound() || isInvalid() ) + return; + + try + { + OUString sTitle; + m_pContent->getPropertyValue("Title") >>= sTitle; + _rTitle = sTitle; + + // from here on, we definitely know that the content is valid + m_eState = VALID; + } + catch( const css::uno::Exception& ) + { + // now we're definitely invalid + m_eState = INVALID; + } + } + + + bool SmartContent::hasParentFolder( ) + { + if ( !isBound() || isInvalid() ) + return false; + + bool bRet = false; + try + { + Reference< XChild > xChild( m_pContent->get(), UNO_QUERY ); + if ( xChild.is() ) + { + Reference< XContent > xParent( xChild->getParent(), UNO_QUERY ); + if ( xParent.is() ) + { + const OUString aParentURL( xParent->getIdentifier()->getContentIdentifier() ); + bRet = ( !aParentURL.isEmpty() && aParentURL != m_pContent->getURL() ); + + // now we're definitely valid + m_eState = VALID; + } + } + } + catch( const Exception& ) + { + // now we're definitely invalid + m_eState = INVALID; + } + return bRet; + } + + + bool SmartContent::canCreateFolder( ) + { + if ( !isBound() || isInvalid() ) + return false; + + bool bRet = false; + try + { + const css::uno::Sequence<css::ucb::ContentInfo> aContentsInfo = m_pContent->queryCreatableContentsInfo(); + for ( auto const& rInfo : aContentsInfo ) + { + // Simply look for the first KIND_FOLDER... + if ( rInfo.Attributes & ContentInfoAttribute::KIND_FOLDER ) + { + bRet = true; + break; + } + } + + // now we're definitely valid + m_eState = VALID; + } + catch( const Exception& ) + { + // now we're definitely invalid + m_eState = INVALID; + } + return bRet; + } + + OUString SmartContent::createFolder( const OUString& _rTitle ) + { + OUString aCreatedUrl; + try + { + OUString sFolderType; + + const css::uno::Sequence<css::ucb::ContentInfo> aContentsInfo = m_pContent->queryCreatableContentsInfo(); + for ( auto const& rInfo : aContentsInfo ) + { + // Simply look for the first KIND_FOLDER... + if ( rInfo.Attributes & ContentInfoAttribute::KIND_FOLDER ) + { + sFolderType = rInfo.Type; + break; + } + } + + if ( !sFolderType.isEmpty() ) + { + ucbhelper::Content aCreated; + Sequence< OUString > aNames { "Title" }; + Sequence< Any > aValues { Any(_rTitle) }; + m_pContent->insertNewContent( sFolderType, aNames, aValues, aCreated ); + + aCreatedUrl = aCreated.getURL(); + } + } + catch( const Exception& ) + { + } + return aCreatedUrl; + } + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/fpsmartcontent.hxx b/fpicker/source/office/fpsmartcontent.hxx new file mode 100644 index 000000000..5f9d4e32f --- /dev/null +++ b/fpicker/source/office/fpsmartcontent.hxx @@ -0,0 +1,195 @@ +/* -*- 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 "fpinteraction.hxx" + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <ucbhelper/content.hxx> +#include <rtl/ref.hxx> +#include <memory> + + +namespace svt +{ + + + //= SmartContent + + /** a "smart content" which basically wraps a UCB content, but caches some information + so that repeatedly recreating it may be faster + */ + class SmartContent + { + public: + enum State + { + NOT_BOUND, // never bound + UNKNOWN, // bound, but validity is unknown + VALID, // bound to a URL, and valid + INVALID // bound to a URL, and invalid + }; + + private: + OUString m_sURL; + std::unique_ptr<::ucbhelper::Content> m_pContent; + State m_eState; + css::uno::Reference < css::ucb::XCommandEnvironment > m_xCmdEnv; + rtl::Reference<::svt::OFilePickerInteractionHandler> m_xOwnInteraction; + + private: + enum Type { Folder, Document }; + /// checks if the currently bound content is a folder or document + bool implIs( const OUString& _rURL, Type _eType ); + + SmartContent( const SmartContent& _rSource ) = delete; + SmartContent& operator=( const SmartContent& _rSource ) = delete; + + public: + SmartContent(); + explicit SmartContent( const OUString& _rInitialURL ); + ~SmartContent(); + + public: + + /** create and set a specialized interaction handler at the internal used command environment. + + @param eInterceptions + will be directly forwarded to OFilePickerInteractionHandler::enableInterceptions() + */ + void enableOwnInteractionHandler(::svt::OFilePickerInteractionHandler::EInterceptedInteractions eInterceptions); + + /** disable the specialized interaction handler and use the global UI interaction handler only. + */ + void enableDefaultInteractionHandler(); + + /** return the internal used interaction handler object ... + Because this pointer will be valid only, if the uno object is hold + alive by its uno reference (and this reference is set on the + command environment) we must return NULL, in case this environment does + not exist! + */ + ::svt::OFilePickerInteractionHandler* getOwnInteractionHandler() const; + + /** describes different types of interaction handlers + */ + enum InteractionHandlerType + { + IHT_NONE, + IHT_OWN, + IHT_DEFAULT + }; + + /** return the type of the internal used interaction handler object ... + + @seealso InteractionHandlerType + */ + InteractionHandlerType queryCurrentInteractionHandler() const; + + /** disable internal used interaction handler object ... + */ + void disableInteractionHandler(); + + /** returns the current state of the content + + @seealso State + */ + State getState( ) const { return m_eState; } + + /** checks if the content is valid + <p>Note that "not (is valid)" is not the same as "is invalid"</p> + */ + bool isValid( ) const { return VALID == getState(); } + + /** checks if the content is valid + <p>Note that "not (is invalid)" is not the same as "is valid"</p> + */ + bool isInvalid( ) const { return INVALID == getState(); } + + /** checks if the content is bound + */ + bool isBound( ) const { return NOT_BOUND != getState(); } + + /** returns the URL of the content + */ + OUString const & getURL() const { return m_pContent ? m_pContent->getURL() : m_sURL; } + + /** (re)creates the content for the given URL + + <p>Note that getState will return either UNKNOWN or INVALID after the call returns, + but never VALID. The reason is that there are content providers which allow to construct + content objects, even if the respective contents are not accessible. They tell about this + only upon working with the content object (e.g. when asking for the IsFolder).</p> + + @postcond + <member>getState</member> does not return NOT_BOUND after the call returns + */ + void bindTo( const OUString& _rURL ); + + /** retrieves the title of the content + @precond + the content is bound and not invalid + */ + void getTitle( OUString& /* [out] */ _rTitle ); + + /** checks if the content has a parent folder + @precond + the content is bound and not invalid + */ + bool hasParentFolder( ); + + /** checks if sub folders below the content can be created + @precond + the content is bound and not invalid + */ + bool canCreateFolder( ); + + /** creates a new folder with the given title and return the corresponding URL. + + @return + the URL of the created folder or an empty string + */ + OUString createFolder( const OUString& _rTitle ); + + /** binds to the given URL, checks whether or not it refers to a folder + + @postcond + the content is not in the state UNKNOWN + */ + bool isFolder( const OUString& _rURL ) + { + return implIs( _rURL, Folder ); + } + + /** checks if the content is existent (it is if and only if it is a document or a folder) + */ + bool is( const OUString& _rURL ) + { + return implIs( _rURL, Folder ) || implIs( _rURL, Document ); + } + + bool isFolder( ) { return isFolder( getURL() ); } + }; + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/iodlg.cxx b/fpicker/source/office/iodlg.cxx new file mode 100644 index 000000000..a7addbb10 --- /dev/null +++ b/fpicker/source/office/iodlg.cxx @@ -0,0 +1,2331 @@ +/* -*- 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 <sal/config.h> + +#include <sal/log.hxx> +#include "fileview.hxx" +#include "iodlg.hxx" +#include <svtools/PlaceEditDialog.hxx> +#include "OfficeControlAccess.hxx" +#include "PlacesListBox.hxx" +#include <fpicker/fpsofficeResMgr.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <vcl/errinf.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <unotools/ucbhelper.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/viewoptions.hxx> +#include <svtools/sfxecode.hxx> + +#include <fpicker/strings.hrc> +#include <svtools/helpids.h> +#include <strings.hrc> +#include "asyncfilepicker.hxx" +#include "iodlgimp.hxx" +#include <svtools/inettbc.hxx> +#include "QueryFolderName.hxx" +#include <rtl/ustring.hxx> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <comphelper/interaction.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> + +#include <osl/file.hxx> +#include <vcl/dibtools.hxx> + +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> +#include "fpinteraction.hxx" +#include <osl/process.h> +#include <o3tl/string_view.hxx> + +#include <officecfg/Office/Common.hxx> + +#include <algorithm> +#include <memory> +#include <string_view> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::sdbc; +using namespace ::utl; +using namespace ::svt; + +using namespace ExtendedFilePickerElementIds; +using namespace CommonFilePickerElementIds; +using namespace InternalFilePickerElementIds; + +// functions ------------------------------------------------------------- + +namespace +{ + OUString getMostCurrentFilter( std::unique_ptr<SvtExpFileDlg_Impl> const & pImpl ) + { + assert( pImpl && "invalid impl pointer" ); + const SvtFileDialogFilter_Impl* pFilter = pImpl->m_xUserFilter.get(); + + if ( !pFilter ) + pFilter = pImpl->GetCurFilter(); + + if ( !pFilter ) + return OUString(); + + return pFilter->GetType(); + } + + void restoreCurrentFilter( std::unique_ptr<SvtExpFileDlg_Impl> const & pImpl ) + { + SAL_WARN_IF( !pImpl->GetCurFilter(), "fpicker.office", "restoreCurrentFilter: no current filter!" ); + SAL_WARN_IF( pImpl->GetCurFilterDisplayName().isEmpty(), "fpicker.office", "restoreCurrentFilter: no current filter (no display name)!" ); + + pImpl->SelectFilterListEntry( pImpl->GetCurFilterDisplayName() ); + +#ifdef DBG_UTIL + OUString sSelectedDisplayName; + DBG_ASSERT( ( pImpl->GetSelectedFilterEntry( sSelectedDisplayName ) == pImpl->GetCurFilter() ) + && ( sSelectedDisplayName == pImpl->GetCurFilterDisplayName() ), + "restoreCurrentFilter: inconsistence!" ); +#endif + } + + + OUString GetFsysExtension_Impl( std::u16string_view rFile, const OUString& rLastFilterExt ) + { + size_t nDotPos = rFile.rfind( '.' ); + if ( nDotPos != std::u16string_view::npos ) + { + if ( !rLastFilterExt.isEmpty() ) + { + if ( o3tl::equalsIgnoreAsciiCase(rFile.substr( nDotPos + 1 ), rLastFilterExt ) ) + return rLastFilterExt; + } + else + return OUString(rFile.substr( nDotPos )); + } + return OUString(); + } + + + void SetFsysExtension_Impl( OUString& rFile, std::u16string_view rExtension ) + { + const sal_Int32 nDotPos{ rFile.lastIndexOf('.') }; + if (nDotPos>=0) + { + if (!rExtension.empty()) + rFile = OUString::Concat(rFile.subView(0, nDotPos)) + rExtension; // replace old extension with new (not empty) one + else if (nDotPos) + rFile = rFile.copy(0, nDotPos-1); // truncate extension (new one is empty) + else + rFile.clear(); // Filename was just an extension + } + else if (!rExtension.empty()) + rFile += OUString::Concat(".") + rExtension; + // no extension was present, append new one if not empty + } + + void lcl_autoUpdateFileExtension( SvtFileDialog* _pDialog, const OUString& _rLastFilterExt ) + { + // if auto extension is enabled... + if ( !_pDialog->isAutoExtensionEnabled() ) + return; + + // automatically switch to the extension of the (maybe just newly selected) extension + OUString aNewFile = _pDialog->getCurrentFileText( ); + OUString aExt = GetFsysExtension_Impl( aNewFile, _rLastFilterExt ); + + // but only if there already is an extension + if ( aExt.isEmpty() ) + return; + + // check if it is a real file extension, and not only the "post-dot" part in + // a directory name + bool bRealExtensions = true; + if ( -1 != aExt.indexOf( '/' ) ) + bRealExtensions = false; + else if ( -1 != aExt.indexOf( '\\' ) ) + bRealExtensions = false; + else + { + // no easy way to tell, because the part containing the dot already is the last + // segment of the complete file name + // So we have to check if the file name denotes a folder or a file. + // For performance reasons, we do this for file urls only + INetURLObject aURL( aNewFile ); + if ( INetProtocol::NotValid == aURL.GetProtocol() ) + { + OUString sURL; + if ( osl::FileBase::getFileURLFromSystemPath( aNewFile, sURL ) + == osl::FileBase::E_None ) + aURL = INetURLObject( sURL ); + } + if ( INetProtocol::File == aURL.GetProtocol() ) + { + try + { + bRealExtensions = !_pDialog->ContentIsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + catch( const css::uno::Exception& ) + { + SAL_INFO( "fpicker.office", "Exception in lcl_autoUpdateFileExtension" ); + } + } + } + + if ( bRealExtensions ) + { + SetFsysExtension_Impl( aNewFile, _pDialog->GetDefaultExt() ); + _pDialog->setCurrentFileText( aNewFile ); + } + } + +#if defined( UNX ) + bool lcl_getHomeDirectory( const OUString& _rForURL, OUString& /* [out] */ _rHomeDir ) + { + _rHomeDir.clear(); + + // now ask the content broker for a provider for this scheme + + try + { + // get the provider for the current scheme + Reference< XContentProvider > xProvider( + UniversalContentBroker::create( + comphelper::getProcessComponentContext() )-> + queryContentProvider( _rForURL ) ); + + SAL_WARN_IF( !xProvider.is(), "fpicker.office", "lcl_getHomeDirectory: could not find a (valid) content provider for the current URL!" ); + Reference< XPropertySet > xProviderProps( xProvider, UNO_QUERY ); + if ( xProviderProps.is() ) + { + Reference< XPropertySetInfo > xPropInfo = xProviderProps->getPropertySetInfo(); + static const OUStringLiteral sHomeDirPropertyName( u"HomeDirectory" ); + if ( !xPropInfo.is() || xPropInfo->hasPropertyByName( sHomeDirPropertyName ) ) + { + OUString sHomeDirectory; + xProviderProps->getPropertyValue( sHomeDirPropertyName ) >>= sHomeDirectory; + _rHomeDir = sHomeDirectory; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "fpicker", "lcl_getHomeDirectory" ); + } + return !_rHomeDir.isEmpty(); + } +#endif + + OUString lcl_ensureFinalSlash( std::u16string_view _rDir ) + { + INetURLObject aWorkPathObj( _rDir, INetProtocol::File ); + aWorkPathObj.setFinalSlash(); + return aWorkPathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + + /** retrieves the value of an environment variable + @return <TRUE/> if and only if the retrieved string value is not empty + */ + bool getEnvironmentValue( const char* _pAsciiEnvName, OUString& _rValue ) + { + _rValue.clear(); + OUString sEnvName = OUString::createFromAscii( _pAsciiEnvName ); + osl_getEnvironment( sEnvName.pData, &_rValue.pData ); + return !_rValue.isEmpty(); + } +} + +// SvtFileDialog +SvtFileDialog::SvtFileDialog(weld::Window* pParent, PickerFlags nStyle) + : SvtFileDialog_Base(pParent, "fps/ui/explorerfiledialog.ui", "ExplorerFileDialog") + , m_xCbReadOnly(m_xBuilder->weld_check_button("readonly")) + , m_xCbLinkBox(m_xBuilder->weld_check_button("link")) + , m_xCbPreviewBox(m_xBuilder->weld_check_button("cb_preview")) + , m_xCbSelection(m_xBuilder->weld_check_button("selection")) + , m_xPbPlay(m_xBuilder->weld_button("play")) + , m_xPreviewFrame(m_xBuilder->weld_widget("previewframe")) + , m_xPrevBmp(m_xBuilder->weld_image("preview")) + , m_pFileNotifier(nullptr) + , m_xImpl(new SvtExpFileDlg_Impl) + , m_nPickerFlags(nStyle) + , m_bIsInExecute(false) + , m_bInExecuteAsync(false) + , m_bHasFilename(false) +{ + m_xImpl->m_xCbOptions = m_xBuilder->weld_check_button("options"); + m_xImpl->m_xFtFileName = m_xBuilder->weld_label("file_name_label"); + m_xImpl->m_xEdFileName.reset(new SvtURLBox(m_xBuilder->weld_combo_box("file_name"))); + m_xImpl->m_xFtFileType = m_xBuilder->weld_label("file_type_label"); + m_xImpl->m_xLbFilter = m_xBuilder->weld_combo_box("file_type"); + m_xImpl->m_xEdCurrentPath.reset(new SvtURLBox(m_xBuilder->weld_combo_box("current_path"))); + m_xImpl->m_xBtnFileOpen = m_xBuilder->weld_button("open"); + m_xImpl->m_xBtnCancel = m_xBuilder->weld_button("cancel"); + m_xImpl->m_xBtnHelp = m_xBuilder->weld_button("help"); + m_xImpl->m_xBtnConnectToServer = m_xBuilder->weld_button("connect_to_server"); + m_xImpl->m_xBtnNewFolder = m_xBuilder->weld_button("new_folder"); + m_xImpl->m_xCbPassword = m_xBuilder->weld_check_button("password"); + m_xImpl->m_xCbGPGEncrypt = m_xBuilder->weld_check_button("gpgencrypt"); + m_xImpl->m_xCbAutoExtension = m_xBuilder->weld_check_button("extension"); + m_xImpl->m_xSharedLabel = m_xBuilder->weld_label("shared_label"); + m_xImpl->m_xSharedListBox = m_xBuilder->weld_combo_box("shared"); + + // because the "<All Formats> (*.bmp,*...)" entry is too wide, + // we need to disable the auto width feature of the filter box + int nWidth = m_xImpl->m_xLbFilter->get_approximate_digit_width() * 60; + m_xImpl->m_xSharedListBox->set_size_request(nWidth, -1); + m_xImpl->m_xLbFilter->set_size_request(nWidth, -1); + + m_xImpl->m_xBtnUp.reset(new SvtUpButton_Impl(m_xBuilder->weld_toolbar("up_bar"), + m_xBuilder->weld_menu("up_menu"), + this)); + m_xImpl->m_xBtnUp->set_help_id(HID_FILEOPEN_LEVELUP); + m_xImpl->m_xBtnUp->show(); + + m_xImpl->m_nStyle = nStyle; + m_xImpl->m_eMode = ( nStyle & PickerFlags::SaveAs ) ? FILEDLG_MODE_SAVE : FILEDLG_MODE_OPEN; + m_xImpl->m_eDlgType = FILEDLG_TYPE_FILEDLG; + + if (nStyle & PickerFlags::PathDialog) + m_xImpl->m_eDlgType = FILEDLG_TYPE_PATHDLG; + + // Set the directory for the "back to the default dir" button + INetURLObject aStdDirObj( SvtPathOptions().GetWorkPath() ); + SetStandardDir( aStdDirObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + // Create control element, the order defines the tab control. + m_xImpl->m_xEdFileName->connect_changed( LINK( this, SvtFileDialog, EntrySelectHdl_Impl ) ); + m_xImpl->m_xEdFileName->connect_entry_activate( LINK( this, SvtFileDialog, OpenUrlHdl_Impl ) ); + + // in folder picker mode, only auto-complete directories (no files) + bool bIsFolderPicker = m_xImpl->m_eDlgType == FILEDLG_TYPE_PATHDLG; + m_xImpl->m_xEdFileName->SetOnlyDirectories( bIsFolderPicker ); + + // in save mode, don't use the autocompletion as selection in the edit part + bool bSaveMode = FILEDLG_MODE_SAVE == m_xImpl->m_eMode; + m_xImpl->m_xEdFileName->SetNoURLSelection( bSaveMode ); + + if (nStyle & PickerFlags::MultiSelection) + m_xImpl->m_bMultiSelection = true; + + m_xContainer = m_xBuilder->weld_container("container"); + m_xContainer->set_size_request(m_xContainer->get_approximate_digit_width() * 95, -1); + + m_xFileView.reset(new SvtFileView(m_xDialog.get(), + m_xBuilder->weld_tree_view("fileview"), + m_xBuilder->weld_icon_view("iconview"), + FILEDLG_TYPE_PATHDLG == m_xImpl->m_eDlgType, + m_xImpl->m_bMultiSelection)); + m_xFileView->set_help_id( HID_FILEDLG_STANDARD ); + + if ( nStyle & PickerFlags::ReadOnly ) + { + m_xCbReadOnly->set_help_id( HID_FILEOPEN_READONLY ); + m_xCbReadOnly->set_label( FpsResId( STR_SVT_FILEPICKER_READONLY ) ); + m_xCbReadOnly->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xCbReadOnly->show(); + } + + if ( nStyle & PickerFlags::Password ) + { + m_xImpl->m_xCbPassword->set_label( FpsResId( STR_SVT_FILEPICKER_PASSWORD ) ); + m_xImpl->m_xCbPassword->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xImpl->m_xCbPassword->show(); + m_xImpl->m_xCbGPGEncrypt->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xImpl->m_xCbGPGEncrypt->show(); + } + + // set the ini file for extracting the size + m_xImpl->m_aIniKey = "FileDialog"; + + AddControls_Impl( ); + + // adjust the labels to the mode + TranslateId pResId = STR_EXPLORERFILE_OPEN; + TranslateId pButtonResId; + + if ( nStyle & PickerFlags::SaveAs ) + { + pResId = STR_EXPLORERFILE_SAVE; + pButtonResId = STR_EXPLORERFILE_BUTTONSAVE; + } + + if ( nStyle & PickerFlags::PathDialog ) + { + m_xImpl->m_xFtFileName->set_label( FpsResId( STR_PATHNAME ) ); + pResId = STR_PATHSELECT; + pButtonResId = STR_BUTTONSELECT; + } + + m_xDialog->set_title(FpsResId(pResId)); + + if ( pButtonResId ) + m_xImpl->m_xBtnFileOpen->set_label( FpsResId( pButtonResId ) ); + + if ( FILEDLG_TYPE_FILEDLG != m_xImpl->m_eDlgType ) + { + m_xImpl->m_xFtFileType->hide(); + m_xImpl->GetFilterListControl()->hide(); + } + + // Setting preferences of the control elements. + m_xImpl->m_xBtnNewFolder->connect_clicked( LINK( this, SvtFileDialog, NewFolderHdl_Impl ) ); + m_xImpl->m_xBtnFileOpen->connect_clicked( LINK( this, SvtFileDialog, OpenClickHdl_Impl ) ); + m_xImpl->m_xBtnCancel->connect_clicked( LINK( this, SvtFileDialog, CancelHdl_Impl ) ); + m_xImpl->SetFilterListSelectHdl( LINK( this, SvtFileDialog, FilterSelectHdl_Impl ) ); + m_xImpl->m_xEdFileName->connect_focus_in( LINK( this, SvtFileDialog, FileNameGetFocusHdl_Impl ) ); + m_xImpl->m_xEdFileName->connect_changed( LINK( this, SvtFileDialog, FileNameModifiedHdl_Impl ) ); + m_xImpl->m_xEdCurrentPath->connect_entry_activate( LINK( this, SvtFileDialog, URLBoxModifiedHdl_Impl ) ); + m_xImpl->m_xBtnConnectToServer->connect_clicked( LINK ( this, SvtFileDialog, ConnectToServerPressed_Hdl ) ); + + m_xFileView->SetSelectHdl( LINK( this, SvtFileDialog, SelectHdl_Impl ) ); + m_xFileView->SetDoubleClickHdl( LINK( this, SvtFileDialog, DblClickHdl_Impl ) ); + m_xFileView->SetOpenDoneHdl( LINK( this, SvtFileDialog, OpenDoneHdl_Impl ) ); + + // set timer for the filterbox travel + m_xImpl->m_aFilterIdle.SetPriority(TaskPriority::LOWEST); + m_xImpl->m_aFilterIdle.SetInvokeHandler( LINK( this, SvtFileDialog, FilterSelectTimerHdl_Impl ) ); + + if ( PickerFlags::SaveAs & nStyle ) + { + // different help ids if in save-as mode + m_xDialog->set_help_id( HID_FILESAVE_DIALOG ); + + m_xImpl->m_xEdFileName->set_help_id( HID_FILESAVE_FILEURL ); + m_xImpl->m_xBtnFileOpen->set_help_id( HID_FILESAVE_DOSAVE ); + m_xImpl->m_xBtnNewFolder->set_help_id( HID_FILESAVE_CREATEDIRECTORY ); + m_xImpl->m_xBtnUp->set_help_id( HID_FILESAVE_LEVELUP ); + m_xImpl->GetFilterListControl()->set_help_id( HID_FILESAVE_FILETYPE ); + m_xFileView->set_help_id( HID_FILESAVE_FILEVIEW ); + + // formerly, there was only _pLbFileVersion, which was used for 3 different + // use cases. For reasons of maintainability, I introduced extra members (_pLbTemplates, _pLbImageTemplates) + // for the extra use cases, and separated _pLbFileVersion + // I did not find out in which cases the help ID is really needed HID_FILESAVE_TEMPLATE - all + // tests I made lead to a dialog where _no_ of the three list boxes was present. + if (m_xImpl->m_xSharedListBox) + m_xImpl->m_xSharedListBox->set_help_id( HID_FILESAVE_TEMPLATE ); + + if ( m_xImpl->m_xCbPassword ) m_xImpl->m_xCbPassword->set_help_id( HID_FILESAVE_SAVEWITHPASSWORD ); + if ( m_xImpl->m_xCbAutoExtension ) m_xImpl->m_xCbAutoExtension->set_help_id( HID_FILESAVE_AUTOEXTENSION ); + if ( m_xImpl->m_xCbOptions ) m_xImpl->m_xCbOptions->set_help_id( HID_FILESAVE_CUSTOMIZEFILTER ); + if ( m_xCbSelection ) m_xCbSelection->set_help_id( HID_FILESAVE_SELECTION ); + } + + /// read our settings from the configuration + m_aConfiguration = OConfigurationTreeRoot::createWithComponentContext( + ::comphelper::getProcessComponentContext(), + "/org.openoffice.Office.UI/FilePicker" + ); + + m_xDialog->connect_size_allocate(LINK(this, SvtFileDialog, SizeAllocHdl)); + SizeAllocHdl(Size()); + + m_xImpl->m_xEdFileName->grab_focus(); +} + +SvtFileDialog::~SvtFileDialog() +{ + if (!m_xImpl->m_aIniKey.isEmpty()) + { + // save window state + SvtViewOptions aDlgOpt( EViewType::Dialog, m_xImpl->m_aIniKey ); + aDlgOpt.SetWindowState(OStringToOUString(m_xDialog->get_window_state(WindowStateMask::All), RTL_TEXTENCODING_UTF8)); + OUString sUserData = m_xFileView->GetConfigString(); + aDlgOpt.SetUserItem( "UserData", + Any( sUserData ) ); + } + + m_xFileView->SetSelectHdl(Link<SvtFileView*,void>()); + + // Save bookmarked places + if (!m_xImpl->m_xPlaces->IsUpdated()) + return; + + const std::vector<PlacePtr> aPlaces = m_xImpl->m_xPlaces->GetPlaces(); + Sequence< OUString > placesUrlsList(m_xImpl->m_xPlaces->GetNbEditablePlaces()); + auto placesUrlsListRange = asNonConstRange(placesUrlsList); + Sequence< OUString > placesNamesList(m_xImpl->m_xPlaces->GetNbEditablePlaces()); + auto placesNamesListRange = asNonConstRange(placesNamesList); + int i(0); + for (auto const& place : aPlaces) + { + if(place->IsEditable()) { + placesUrlsListRange[i] = place->GetUrl(); + placesNamesListRange[i] = place->GetName(); + ++i; + } + } + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::FilePickerPlacesUrls::set(placesUrlsList, batch); + officecfg::Office::Common::Misc::FilePickerPlacesNames::set(placesNamesList, batch); + batch->commit(); +} + +IMPL_LINK_NOARG(SvtFileDialog, NewFolderHdl_Impl, weld::Button&, void) +{ + m_xFileView->EndInplaceEditing(); + + SmartContent aContent( m_xFileView->GetViewURL( ) ); + OUString aTitle; + aContent.getTitle( aTitle ); + QueryFolderNameDialog aDlg(m_xDialog.get(), aTitle, FpsResId(STR_SVT_NEW_FOLDER)); + bool bHandled = false; + + while ( !bHandled ) + { + if (aDlg.run() == RET_OK) + { + OUString aUrl = aContent.createFolder(aDlg.GetName()); + if ( !aUrl.isEmpty( ) ) + { + m_xFileView->CreatedFolder(aUrl, aDlg.GetName()); + bHandled = true; + } + } + else + bHandled = true; + } +} + +void SvtFileDialog::createNewUserFilter( const OUString& _rNewFilter ) +{ + // delete the old user filter and create a new one + m_xImpl->m_xUserFilter.reset( new SvtFileDialogFilter_Impl( _rNewFilter, _rNewFilter ) ); + + // remember the extension + bool bIsAllFiles = _rNewFilter == FILEDIALOG_FILTER_ALL; + if ( bIsAllFiles ) + EraseDefaultExt(); + else + SetDefaultExt( _rNewFilter.copy( 2 ) ); + // TODO: this is nonsense. In the whole file there are a lot of places where we assume that a user filter + // is always "*.<something>". But changing this would take some more time than I have now... + + // now, the default extension is set to the one of the user filter (or empty) + if ( m_xImpl->GetCurFilter( ) ) + SetDefaultExt( m_xImpl->GetCurFilter( )->GetExtension() ); + else + EraseDefaultExt(); +} + + +AdjustFilterFlags SvtFileDialog::adjustFilter( const OUString& rFilter ) +{ + AdjustFilterFlags nReturn = AdjustFilterFlags::NONE; + + const bool bNonEmpty = !rFilter.isEmpty(); + if ( bNonEmpty ) + { + nReturn |= AdjustFilterFlags::NonEmpty; + + bool bFilterChanged = true; + + // search for a corresponding filter + SvtFileDialogFilter_Impl* pFilter = FindFilter_Impl( rFilter, false, bFilterChanged ); + + // look for multi-ext filters if necessary + if ( !pFilter ) + pFilter = FindFilter_Impl( rFilter, true, bFilterChanged ); + + if ( bFilterChanged ) + nReturn |= AdjustFilterFlags::Changed; + + if ( !pFilter ) + { + nReturn |= AdjustFilterFlags::UserFilter; + // no filter found : use it as user defined filter + createNewUserFilter( rFilter ); + } + } + + return nReturn; +} + +IMPL_LINK_NOARG(SvtFileDialog, CancelHdl_Impl, weld::Button&, void) +{ + if ( m_pCurrentAsyncAction.is() ) + { + m_pCurrentAsyncAction->cancel(); + onAsyncOperationFinished(); + } + else + { + m_xDialog->response(RET_CANCEL); + } +} + +IMPL_LINK( SvtFileDialog, OpenClickHdl_Impl, weld::Button&, rVoid, void ) +{ + OpenHdl_Impl(&rVoid); +} + +IMPL_LINK( SvtFileDialog, OpenUrlHdl_Impl, weld::ComboBox&, rVoid, bool ) +{ + OpenHdl_Impl(&rVoid); + return true; +} + +void SvtFileDialog::OpenHdl_Impl(void const * pVoid) +{ + if ( m_xImpl->m_bMultiSelection && m_xFileView->GetSelectionCount() > 1 ) + { + // special open in case of multiselection + OpenMultiSelection_Impl(); + return; + } + + OUString aFileName; + OUString aOldPath(m_xFileView->GetViewURL()); + if ( m_xImpl->m_bDoubleClick || m_xFileView->has_focus() ) + { + // Selection done by doubleclicking in the view, get filename from the view + aFileName = m_xFileView->GetCurrentURL(); + } + + if ( aFileName.isEmpty() ) + { + // if an entry is selected in the view... + if ( m_xFileView->GetSelectionCount() ) + { // -> use this one. This will allow us to step down this folder + aFileName = m_xFileView->GetCurrentURL(); + } + } + + if ( aFileName.isEmpty() ) + { + // get the URL from the edit field ( if not empty ) + if ( !m_xImpl->m_xEdFileName->get_active_text().isEmpty() ) + { + OUString aText = m_xImpl->m_xEdFileName->get_active_text(); + + // did we reach the root? + if ( !INetURLObject( aOldPath ).getSegmentCount() ) + { + if ( ( aText.getLength() == 2 && aText == ".." ) || + ( aText.getLength() == 3 && ( aText == "..\\" || aText == "../" ) ) ) + // don't go higher than the root + return; + } + +#if defined( UNX ) + if ( ( 1 == aText.getLength() ) && ( '~' == aText[0] ) ) + { + // go to the home directory + if ( lcl_getHomeDirectory( m_xFileView->GetViewURL(), aFileName ) ) + // in case we got a home dir, reset the text of the edit + m_xImpl->m_xEdFileName->set_entry_text( OUString() ); + } + if ( aFileName.isEmpty() ) +#endif + { + // get url from autocomplete edit + aFileName = m_xImpl->m_xEdFileName->GetURL(); + } + } + else if ( pVoid == m_xImpl->m_xBtnFileOpen.get() ) + // OpenHdl was called for the "Open" Button; if edit field is empty, use selected element in the view + aFileName = m_xFileView->GetCurrentURL(); + } + + // MBA->PB: ?! + if ( aFileName.isEmpty() && pVoid == m_xImpl->m_xEdFileName.get() && m_xImpl->m_xUserFilter ) + { + m_xImpl->m_xUserFilter.reset(); + return; + } + + sal_Int32 nLen = aFileName.getLength(); + if ( !nLen ) + { + // if the dialog was opened to select a folder, the last selected folder should be selected + if( m_xImpl->m_eDlgType == FILEDLG_TYPE_PATHDLG ) + { + aFileName = m_xImpl->m_xEdCurrentPath->get_active_text(); + nLen = aFileName.getLength(); + } + else + // no file selected ! + return; + } + + // mark input as selected + m_xImpl->m_xEdFileName->select_entry_region(0, nLen); + + // if a path with wildcards is given, divide the string into path and wildcards + OUString aFilter; + if ( !SvtFileDialog::IsolateFilterFromPath_Impl( aFileName, aFilter ) ) + return; + + // if a filter was retrieved, there were wildcards ! + AdjustFilterFlags nNewFilterFlags = adjustFilter( aFilter ); + if ( nNewFilterFlags & AdjustFilterFlags::Changed ) + { + // cut off all text before wildcard in edit and select wildcard + m_xImpl->m_xEdFileName->set_entry_text( aFilter ); + m_xImpl->m_xEdFileName->select_entry_region(0, -1); + } + + { + INetURLObject aFileObject( aFileName ); + if ( ( aFileObject.GetProtocol() == INetProtocol::NotValid ) && !aFileName.isEmpty() ) + { + OUString sCompleted = SvtURLBox::ParseSmart( aFileName, m_xFileView->GetViewURL() ); + if ( !sCompleted.isEmpty() ) + aFileName = sCompleted; + } + } + + // check if it is a folder + bool bIsFolder = false; + + // first thing before doing anything with the content: Reset it. When the user presses "open" (or "save" or "export", + // for that matter), s/he wants the complete handling, including all possible error messages, even if s/he + // does the same thing for the same content twice, s/he wants both fails to be displayed. + // Without the reset, it could be that the content cached all relevant information, and will not display any + // error messages for the same content a second time... + m_aContent.bindTo( OUString( ) ); + + if ( !aFileName.isEmpty() ) + { + // Make sure we have own Interaction Handler in place. We do not need + // to intercept interactions here, but to record the fact that there + // was an interaction. + SmartContent::InteractionHandlerType eInterActionHandlerType + = m_aContent.queryCurrentInteractionHandler(); + if ( ( eInterActionHandlerType == SmartContent::IHT_NONE ) || + ( eInterActionHandlerType == SmartContent::IHT_DEFAULT ) ) + m_aContent.enableOwnInteractionHandler( + OFilePickerInteractionHandler::E_NOINTERCEPTION ); + + bIsFolder = m_aContent.isFolder( aFileName ); + + // access denied to the given resource - and interaction was already + // used => break following operations + OFilePickerInteractionHandler* pHandler + = m_aContent.getOwnInteractionHandler(); + + OSL_ENSURE( pHandler, "Got no Interaction Handler!!!" ); + + if ( pHandler->wasAccessDenied() ) + return; + + if ( m_aContent.isInvalid() && + ( m_xImpl->m_eMode == FILEDLG_MODE_OPEN ) ) + { + if ( !pHandler->wasUsed() ) + ErrorHandler::HandleError( ERRCODE_IO_NOTEXISTS ); + + return; + } + + // restore previous Interaction Handler + if ( eInterActionHandlerType == SmartContent::IHT_NONE ) + m_aContent.disableInteractionHandler(); + else if ( eInterActionHandlerType == SmartContent::IHT_DEFAULT ) + m_aContent.enableDefaultInteractionHandler(); + } + + if ( !bIsFolder // no existent folder + && m_xImpl->m_xCbAutoExtension // auto extension is enabled in general + && m_xImpl->m_xCbAutoExtension->get_active()// auto extension is really to be used + && !GetDefaultExt().isEmpty() // there is a default extension + && GetDefaultExt() != "*" // the default extension is not "all" + && !( FILEDLG_MODE_SAVE == m_xImpl->m_eMode // we're saving a file + && m_xFileView->GetSelectionCount() // there is a selected file in the file view -> it will later on + ) // (in SvtFileDialog::GetPathList) be taken as file to save to + + && FILEDLG_MODE_OPEN != m_xImpl->m_eMode // #i83408# don't append extension on open + ) + { + // check extension and append the default extension if necessary + appendDefaultExtension(aFileName, + GetDefaultExt(), + m_xImpl->GetCurFilter()->GetType()); + } + + bool bOpenFolder = ( FILEDLG_TYPE_PATHDLG == m_xImpl->m_eDlgType ) && + !m_xImpl->m_bDoubleClick && pVoid != m_xImpl->m_xEdFileName.get(); + if ( bIsFolder ) + { + if ( bOpenFolder ) + { + m_aPath = aFileName; + } + else + { + if ( aFileName != m_xFileView->GetViewURL() ) + { + OpenURL_Impl( aFileName ); + } + else + { + if ( nNewFilterFlags & AdjustFilterFlags::Changed ) + ExecuteFilter(); + } + + return; + } + } + else if ( !( nNewFilterFlags & AdjustFilterFlags::NonEmpty ) ) + { + // if applicable save URL + m_aPath = aFileName; + } + else + { + // if applicable filter again + if ( nNewFilterFlags & AdjustFilterFlags::Changed ) + ExecuteFilter(); + return; + } + + INetURLObject aFileObj( aFileName ); + if ( aFileObj.HasError() ) + { + ErrorHandler::HandleError( ERRCODE_IO_GENERAL ); + return; + } + + switch (m_xImpl->m_eMode) + { + case FILEDLG_MODE_SAVE: + { + if ( ::utl::UCBContentHelper::Exists( aFileObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + { + OUString aMsg = FpsResId(STR_SVT_ALREADYEXISTOVERWRITE); + aMsg = aMsg.replaceFirst( + "$filename$", + aFileObj.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset) + ); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, aMsg)); + if (xBox->run() != RET_YES) + return; + } + else + { + OUString aCurPath; + if (osl::FileBase::getSystemPathFromFileURL(aFileName, aCurPath) == osl::FileBase::E_None) + { + // if content does not exist: at least its path must exist + INetURLObject aPathObj = aFileObj; + aPathObj.removeSegment(); + bool bFolder = m_aContent.isFolder( aPathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if ( !bFolder ) + { + ErrorHandler::HandleError( ERRCODE_IO_NOTEXISTSPATH ); + return; + } + } + } + } + break; + + case FILEDLG_MODE_OPEN: + { + // do an existence check herein, again + + if ( INetProtocol::File == aFileObj.GetProtocol( ) ) + { + bool bExists = m_aContent.is( aFileObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if ( !bExists ) + { + OUString sError(FpsResId(RID_FILEOPEN_NOTEXISTENTFILE)); + + OUString sInvalidFile( aFileObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) ); + if ( INetProtocol::File == aFileObj.GetProtocol() ) + { // if it's a file URL, transform the URL into system notation + OUString sURL( sInvalidFile ); + OUString sSystem; + osl_getSystemPathFromFileURL( sURL.pData, &sSystem.pData ); + sInvalidFile = sSystem; + } + sError = sError.replaceFirst( "$name$", sInvalidFile ); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, sError)); + xBox->run(); + return; + } + } + } + break; + + default: + OSL_FAIL("SvtFileDialog, OpenHdl_Impl: invalid mode!"); + } + + m_xDialog->response(RET_OK); +} + +void SvtFileDialog::EnableAutocompletion(bool bEnable) +{ + m_xImpl->m_xEdFileName->EnableAutocomplete(bEnable); +} + +IMPL_LINK_NOARG( SvtFileDialog, FilterSelectHdl_Impl, weld::ComboBox&, void ) +{ + OUString sSelectedFilterDisplayName; + SvtFileDialogFilter_Impl* pSelectedFilter = m_xImpl->GetSelectedFilterEntry( sSelectedFilterDisplayName ); + if ( !pSelectedFilter ) + { // there is no current selection. This happens if for instance the user selects a group separator using + // the keyboard, and then presses enter: When the selection happens, we immediately deselect the entry, + // so in this situation there is no current selection. + restoreCurrentFilter( m_xImpl ); + } + else + { + if ( ( pSelectedFilter != m_xImpl->GetCurFilter() ) + || m_xImpl->m_xUserFilter + ) + { + // Store the old filter for the auto extension handling + OUString sLastFilterExt = m_xImpl->GetCurFilter()->GetExtension(); + m_xImpl->m_xUserFilter.reset(); + + // if applicable remove filter of the user + m_xImpl->SetCurFilter( pSelectedFilter, sSelectedFilterDisplayName ); + + // if applicable show extension + SetDefaultExt( pSelectedFilter->GetExtension() ); + sal_Int32 nSepPos = GetDefaultExt().indexOf( FILEDIALOG_DEF_EXTSEP ); + + if ( nSepPos != -1 ) + EraseDefaultExt( nSepPos ); + + // update the extension of the current file if necessary + lcl_autoUpdateFileExtension( this, sLastFilterExt ); + + // if the user is traveling fast through the filterbox + // do not filter instantly + // FilterSelectHdl_Impl should be started again at idle + m_xImpl->m_aFilterIdle.Start(); + } + } +} + +IMPL_LINK_NOARG(SvtFileDialog, FilterSelectTimerHdl_Impl, Timer*, void) +{ + // filter the view again + ExecuteFilter(); +} + +IMPL_LINK_NOARG( SvtFileDialog, FileNameGetFocusHdl_Impl, weld::Widget&, void ) +{ + m_xFileView->SetNoSelection(); +} + +IMPL_LINK( SvtFileDialog, FileNameModifiedHdl_Impl, weld::ComboBox&, rComboBox, void ) +{ + FileNameGetFocusHdl_Impl(rComboBox); +} + +IMPL_LINK_NOARG(SvtFileDialog, URLBoxModifiedHdl_Impl, weld::ComboBox&, bool) +{ + OUString aPath = m_xImpl->m_xEdCurrentPath->GetURL(); + OpenURL_Impl(aPath); + return true; +} + +IMPL_LINK_NOARG( SvtFileDialog, ConnectToServerPressed_Hdl, weld::Button&, void ) +{ + m_xFileView->EndInplaceEditing(); + + PlaceEditDialog aDlg(m_xDialog.get()); + short aRetCode = aDlg.run(); + + switch (aRetCode) { + case RET_OK : + { + PlacePtr newPlace = aDlg.GetPlace(); + m_xImpl->m_xPlaces->AppendPlace(newPlace); + + break; + } + case RET_CANCEL : + default : + // Do Nothing + break; + } +} + +IMPL_LINK_NOARG ( SvtFileDialog, AddPlacePressed_Hdl, weld::Button&, void ) +{ + // Maybe open the PlacesDialog would have been a better idea + // there is an ux choice to make we did not make... + INetURLObject aURLObj( m_xFileView->GetViewURL() ); + PlacePtr newPlace = + std::make_shared<Place>( aURLObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset), + m_xFileView->GetViewURL(), true); + m_xImpl->m_xPlaces->AppendPlace(newPlace); +} + +IMPL_LINK_NOARG ( SvtFileDialog, RemovePlacePressed_Hdl, weld::Button&, void ) +{ + m_xImpl->m_xPlaces->RemoveSelectedPlace(); +} + +SvtFileDialogFilter_Impl* SvtFileDialog::FindFilter_Impl +( + const OUString& rFilter, + bool bMultiExt,/* TRUE - regard filter with several extensions + FALSE - do not ... + */ + bool& rFilterChanged +) + +/* [Description] + + This method looks for the specified extension in the included filters. +*/ + +{ + SvtFileDialogFilter_Impl* pFoundFilter = nullptr; + SvtFileDialogFilterList_Impl& rList = m_xImpl->m_aFilter; + sal_uInt16 nFilter = rList.size(); + + while ( nFilter-- ) + { + SvtFileDialogFilter_Impl* pFilter = rList[ nFilter ].get(); + const OUString& rType = pFilter->GetType(); + + if ( bMultiExt ) + { + sal_Int32 nIdx = 0; + while ( !pFoundFilter && nIdx != -1 ) + { + const OUString aSingleType = rType.getToken( 0, FILEDIALOG_DEF_EXTSEP, nIdx ); +#ifdef UNX + if ( aSingleType == rFilter ) +#else + if ( aSingleType.equalsIgnoreAsciiCase( rFilter ) ) +#endif + pFoundFilter = pFilter; + } + } +#ifdef UNX + else if ( rType == rFilter ) +#else + else if ( rType.equalsIgnoreAsciiCase( rFilter ) ) +#endif + pFoundFilter = pFilter; + + if ( pFoundFilter ) + { + // activate filter + rFilterChanged = m_xImpl->m_xUserFilter || ( m_xImpl->GetCurFilter() != pFilter ); + + createNewUserFilter( rFilter ); + + break; + } + } + return pFoundFilter; +} + + +void SvtFileDialog::ExecuteFilter() +{ + executeAsync( AsyncPickerAction::eExecuteFilter, OUString(), getMostCurrentFilter(m_xImpl) ); +} + +/* [Description] + + OpenHandler for MultiSelection +*/ +void SvtFileDialog::OpenMultiSelection_Impl() +{ + SvtContentEntry* pEntry = m_xFileView->FirstSelected(); + + if (pEntry) + m_aPath = pEntry->maURL; + + m_xDialog->response(RET_OK); +} + +void SvtFileDialog::UpdateControls( const OUString& rURL ) +{ + m_xImpl->m_xEdFileName->SetBaseURL( rURL ); + + INetURLObject aObj( rURL ); + + { + OUString sText; + SAL_WARN_IF( INetProtocol::NotValid == aObj.GetProtocol(), "fpicker.office", "SvtFileDialog::UpdateControls: Invalid URL!" ); + + if ( aObj.getSegmentCount() ) + { + osl::FileBase::getSystemPathFromFileURL(rURL, sText); + if ( !sText.isEmpty() ) + { + // no Fsys path for server file system ( only UCB has mountpoints! ) + if ( INetProtocol::File != aObj.GetProtocol() ) + sText = rURL.copy( INetURLObject::GetScheme( aObj.GetProtocol() ).getLength() ); + } + + if ( sText.isEmpty() && aObj.getSegmentCount() ) + sText = rURL; + } + + // path mode ? + if ( FILEDLG_TYPE_PATHDLG == m_xImpl->m_eDlgType ) + // -> set new path in the edit field + m_xImpl->m_xEdFileName->set_entry_text( sText ); + + // in the "current path" field, truncate the trailing slash + if ( aObj.hasFinalSlash() ) + { + aObj.removeFinalSlash(); + OUString sURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if (osl::FileBase::getSystemPathFromFileURL(sURL, sText) != osl::FileBase::E_None) + sText = sURL; + } + + if ( sText.isEmpty() && !rURL.isEmpty() ) + // happens, for instance, for URLs which the INetURLObject does not know to belong to a hierarchical scheme + sText = rURL; + m_xImpl->m_xEdCurrentPath->set_entry_text(sText); + } + + m_aPath = rURL; + + m_xImpl->m_xBtnUp->FillURLMenu(); + + if (m_pFileNotifier) + m_pFileNotifier->notify( DIRECTORY_CHANGED, 0 ); +} + +IMPL_LINK( SvtFileDialog, SelectHdl_Impl, SvtFileView*, pBox, void ) +{ + SvtContentEntry* pUserData = pBox->FirstSelected(); + if (pUserData) + { + INetURLObject aObj( pUserData->maURL ); + if ( FILEDLG_TYPE_PATHDLG == m_xImpl->m_eDlgType ) + { + if ( aObj.GetProtocol() == INetProtocol::File ) + { + if ( !pUserData->mbIsFolder ) + aObj.removeSegment(); + OUString aName = aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos) ); + m_xImpl->m_xEdFileName->set_entry_text( aName ); + m_xImpl->m_xEdFileName->select_entry_region(0, -1); + m_aPath = pUserData->maURL; + } + else if ( !pUserData->mbIsFolder ) + { + m_xImpl->m_xEdFileName->set_entry_text( pUserData->maURL ); + m_xImpl->m_xEdFileName->select_entry_region(0, -1); + m_aPath = pUserData->maURL; + } + else + m_xImpl->m_xEdFileName->set_entry_text( OUString() ); + } + else + { + if ( !pUserData->mbIsFolder ) + { + OUString aName = pBox->get_selected_text(); + m_xImpl->m_xEdFileName->set_entry_text( aName ); + m_xImpl->m_xEdFileName->select_entry_region(0, -1); + m_aPath = pUserData->maURL; + } + } + } + + if ( m_xImpl->m_bMultiSelection && m_xFileView->GetSelectionCount() > 1 ) + { + // clear the file edit for multiselection + m_xImpl->m_xEdFileName->set_entry_text( OUString() ); + } + + FileSelect(); +} + +IMPL_LINK_NOARG(SvtFileDialog, DblClickHdl_Impl, SvtFileView*, bool) +{ + m_xImpl->m_bDoubleClick = true; + OpenHdl_Impl( nullptr ); + m_xImpl->m_bDoubleClick = false; + return true; +} + +IMPL_LINK_NOARG(SvtFileDialog, EntrySelectHdl_Impl, weld::ComboBox&, void) +{ + FileSelect(); +} + +IMPL_LINK( SvtFileDialog, OpenDoneHdl_Impl, SvtFileView*, pView, void ) +{ + const OUString& sCurrentFolder( pView->GetViewURL() ); + // check if we can create new folders + EnableControl( m_xImpl->m_xBtnNewFolder.get(), ContentCanMakeFolder( sCurrentFolder ) ); + + // check if we can travel one level up + bool bCanTravelUp = ContentHasParentFolder( pView->GetViewURL() ); + if ( bCanTravelUp ) + { + // additional check: the parent folder should not be prohibited + INetURLObject aCurrentFolder( sCurrentFolder ); + SAL_WARN_IF( INetProtocol::NotValid == aCurrentFolder.GetProtocol(), + "fpicker.office", "SvtFileDialog::OpenDoneHdl_Impl: invalid current URL!" ); + + aCurrentFolder.removeSegment(); + } + EnableControl( m_xImpl->m_xBtnUp->getWidget(), bCanTravelUp ); +} + +IMPL_LINK_NOARG(SvtFileDialog, AutoExtensionHdl_Impl, weld::Toggleable&, void) +{ + if (m_pFileNotifier) + m_pFileNotifier->notify(CTRL_STATE_CHANGED, CHECKBOX_AUTOEXTENSION); + + // update the extension of the current file if necessary + lcl_autoUpdateFileExtension( this, m_xImpl->GetCurFilter()->GetExtension() ); +} + +IMPL_LINK( SvtFileDialog, ClickHdl_Impl, weld::Toggleable&, rCheckBox, void ) +{ + if (!m_pFileNotifier) + return; + + sal_Int16 nId = -1; + + if ( &rCheckBox == m_xImpl->m_xCbOptions.get() ) + nId = CHECKBOX_FILTEROPTIONS; + else if ( &rCheckBox == m_xCbSelection.get() ) + nId = CHECKBOX_SELECTION; + else if ( &rCheckBox == m_xCbReadOnly.get() ) + nId = CHECKBOX_READONLY; + else if ( &rCheckBox == m_xImpl->m_xCbPassword.get() ) + nId = CHECKBOX_PASSWORD; + else if ( &rCheckBox == m_xImpl->m_xCbGPGEncrypt.get() ) + nId = CHECKBOX_GPGENCRYPTION; + else if ( &rCheckBox == m_xCbLinkBox.get() ) + nId = CHECKBOX_LINK; + else if ( &rCheckBox == m_xCbPreviewBox.get() ) + nId = CHECKBOX_PREVIEW; + + if ( nId != -1 ) + m_pFileNotifier->notify( CTRL_STATE_CHANGED, nId ); +} + +IMPL_LINK_NOARG(SvtFileDialog, PlayButtonHdl_Impl, weld::Button&, void) +{ + if (m_pFileNotifier) + m_pFileNotifier->notify(CTRL_STATE_CHANGED, PUSHBUTTON_PLAY); +} + +namespace +{ + +bool implIsInvalid( const OUString & rURL ) +{ + SmartContent aContent( rURL ); + aContent.enableOwnInteractionHandler( ::svt::OFilePickerInteractionHandler::E_DOESNOTEXIST ); + aContent.isFolder(); // do this _before_ asking isInvalid! Otherwise result might be wrong. + return aContent.isInvalid(); +} + +} + + +OUString SvtFileDialog::implGetInitialURL( const OUString& _rPath, std::u16string_view _rFallback ) +{ + // a URL parser for the fallback + INetURLObject aURLParser; + + // set the path + bool bWasAbsolute = false; + aURLParser = aURLParser.smartRel2Abs( _rPath, bWasAbsolute ); + + // is it a valid folder? + m_aContent.bindTo( aURLParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + bool bIsFolder = m_aContent.isFolder( ); // do this _before_ asking isInvalid! + bool bIsInvalid = m_aContent.isInvalid(); + + if ( bIsInvalid && m_bHasFilename && !aURLParser.hasFinalSlash() ) + { // check if the parent folder exists + INetURLObject aParent( aURLParser ); + aParent.removeSegment( ); + aParent.setFinalSlash( ); + bIsInvalid = implIsInvalid( aParent.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + + if ( bIsInvalid ) + { + INetURLObject aFallback( _rFallback ); + bIsInvalid = implIsInvalid( aFallback.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if ( !bIsInvalid ) + aURLParser = aFallback; + } + + if ( bIsInvalid ) + { + INetURLObject aParent( aURLParser ); + while ( bIsInvalid && aParent.removeSegment() ) + { + aParent.setFinalSlash( ); + bIsInvalid = implIsInvalid( aParent.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + + if ( !bIsInvalid ) + aURLParser = aParent; + } + + if ( !bIsInvalid && bIsFolder ) + { + aURLParser.setFinalSlash(); + } + return aURLParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ); +} + + +short SvtFileDialog::run() +{ + if ( !PrepareExecute() ) + return 0; + + // start the dialog + m_bIsInExecute = true; + short nResult = GenericDialogController::run(); + m_bIsInExecute = false; + + SAL_WARN_IF( m_pCurrentAsyncAction.is(), "fpicker.office", "SvtFilePicker::run: still running an async action!" ); + // the dialog should not be cancellable while an async action is running - first, the action + // needs to be cancelled + + // remember last directory + if ( RET_OK == nResult ) + { + INetURLObject aURL( m_aPath ); + if ( aURL.GetProtocol() == INetProtocol::File ) + { + // remember the selected directory only for file URLs not for virtual folders + sal_Int32 nLevel = aURL.getSegmentCount(); + bool bDir = m_aContent.isFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if ( nLevel > 1 && ( FILEDLG_TYPE_FILEDLG == m_xImpl->m_eDlgType || !bDir ) ) + aURL.removeSegment(); + } + } + + return nResult; +} + +void SvtFileDialog::onAsyncOperationStarted() +{ + EnableUI( false ); + // the cancel button must be always enabled + m_xImpl->m_xBtnCancel->set_sensitive(true); + m_xImpl->m_xBtnCancel->grab_focus(); +} + +void SvtFileDialog::onAsyncOperationFinished() +{ + EnableUI( true ); + m_pCurrentAsyncAction = nullptr; + if ( !m_bInExecuteAsync ) + m_xImpl->m_xEdFileName->grab_focus(); + // (if m_bInExecuteAsync is true, then the operation was finished within the minimum wait time, + // and to the user, the operation appears to be synchronous) +} + +void SvtFileDialog::RemovablePlaceSelected(bool enable) +{ + m_xImpl->m_xPlaces->SetDelEnabled( enable ); +} + +void SvtFileDialog::displayIOException( const OUString& _rURL, IOErrorCode _eCode ) +{ + try + { + // create make a human-readable string from the URL + OUString sDisplayPath; + if (osl::FileBase::getSystemPathFromFileURL(_rURL, sDisplayPath) + == osl::FileBase::E_None) + { + sDisplayPath = _rURL; + } + + // build an own exception which tells "access denied" + InteractiveAugmentedIOException aException; + aException.Arguments = + { css::uno::Any(sDisplayPath), + css::uno::Any(PropertyValue( + "Uri", + -1, aException.Arguments[ 0 ], PropertyState_DIRECT_VALUE + )) }; + // (formerly, it was sufficient to put the URL first parameter. Nowadays, + // the services expects the URL in a PropertyValue named "Uri" ...) + aException.Code = _eCode; + aException.Classification = InteractionClassification_ERROR; + + // let and interaction handler handle this exception + rtl::Reference<::comphelper::OInteractionRequest> pRequest = + new ::comphelper::OInteractionRequest( Any( aException ) ); + pRequest->addContinuation( new ::comphelper::OInteractionAbort( ) ); + + Reference< XInteractionHandler2 > xHandler( + InteractionHandler::createWithParent( ::comphelper::getProcessComponentContext(), nullptr ) ); + xHandler->handle( pRequest ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "fpicker", "iodlg::displayIOException" ); + } +} + +void SvtFileDialog::EnableUI(bool bEnable) +{ + m_xDialog->set_sensitive(bEnable); + + if (bEnable) + { + for (auto& rxControl : m_aDisabledControls) + { + rxControl->set_sensitive(false); + } + } +} + +void SvtFileDialog::EnableControl(weld::Widget* pControl, bool bEnable) +{ + if (!pControl) + { + SAL_WARN( "fpicker.office", "SvtFileDialog::EnableControl: invalid control!" ); + return; + } + + pControl->set_sensitive(bEnable); + + if (bEnable) + { + auto aPos = m_aDisabledControls.find( pControl ); + if ( m_aDisabledControls.end() != aPos ) + m_aDisabledControls.erase( aPos ); + } + else + m_aDisabledControls.insert( pControl ); +} + +bool SvtFileDialog::PrepareExecute() +{ + if (comphelper::LibreOfficeKit::isActive()) + return false; + + OUString aEnvValue; + if ( getEnvironmentValue( "WorkDirMustContainRemovableMedia", aEnvValue ) && aEnvValue == "1" ) + { + try + { + INetURLObject aStdDir( GetStandardDir() ); + ::ucbhelper::Content aCnt( aStdDir.GetMainURL( + INetURLObject::DecodeMechanism::NONE ), + Reference< XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + Sequence< OUString > aProps { "IsVolume", "IsRemoveable" }; + + Reference< XResultSet > xResultSet + = aCnt.createCursor( aProps, ::ucbhelper::INCLUDE_FOLDERS_ONLY ); + if ( xResultSet.is() && !xResultSet->next() ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + FpsResId(STR_SVT_NOREMOVABLEDEVICE))); + xBox->run(); + return false; + } + } + catch ( ContentCreationException const & ) + { + } + catch ( CommandAbortedException const & ) + { + } + } + + if ( ( m_xImpl->m_nStyle & PickerFlags::SaveAs ) && m_bHasFilename ) + // when doing a save-as, we do not want the handler to handle "this file does not exist" messages + // - finally we're going to save that file, aren't we? + m_aContent.enableOwnInteractionHandler(::svt::OFilePickerInteractionHandler::E_DOESNOTEXIST); + else + m_aContent.enableDefaultInteractionHandler(); + + // possibly just a filename without a path + OUString aFileNameOnly; + if( !m_aPath.isEmpty() && (m_xImpl->m_eMode == FILEDLG_MODE_SAVE) + && (m_aPath.indexOf(':') == -1) + && (m_aPath.indexOf('\\') == -1) + && (m_aPath.indexOf('/') == -1)) + { + aFileNameOnly = m_aPath; + m_aPath.clear(); + } + + // no starting path specified? + if ( m_aPath.isEmpty() ) + { + // then use the standard directory + m_aPath = lcl_ensureFinalSlash( m_xImpl->GetStandardDir() ); + + // attach given filename to path + if ( !aFileNameOnly.isEmpty() ) + m_aPath += aFileNameOnly; + } + + + m_aPath = implGetInitialURL( m_aPath, GetStandardDir() ); + + if ( m_xImpl->m_nStyle & PickerFlags::SaveAs && !m_bHasFilename ) + // when doing a save-as, we do not want the handler to handle "this file does not exist" messages + // - finally we're going to save that file, aren't we? + m_aContent.enableOwnInteractionHandler(::svt::OFilePickerInteractionHandler::E_DOESNOTEXIST); + + // if applicable show filter + m_xImpl->InitFilterList(); + + // set up initial filter + sal_uInt16 nFilterCount = GetFilterCount(); + OUString aAll = FpsResId( STR_FILTERNAME_ALL ); + bool bHasAll = m_xImpl->HasFilterListEntry( aAll ); + if ( m_xImpl->GetCurFilter() || nFilterCount == 1 || ( nFilterCount == 2 && bHasAll ) ) + { + // if applicable set the only filter or the only filter that + // does not refer to all files, as the current one + if ( !m_xImpl->GetCurFilter() ) + { + sal_uInt16 nPos = 0; + if ( 2 == nFilterCount && bHasAll ) + { + nPos = nFilterCount; + while ( nPos-- ) + { + if ( aAll != GetFilterName( nPos ) ) + break; + } + } + SvtFileDialogFilter_Impl* pNewCurFilter = m_xImpl->m_aFilter[ nPos ].get(); + assert( pNewCurFilter && "SvtFileDialog::run: invalid filter pos!" ); + m_xImpl->SetCurFilter( pNewCurFilter, pNewCurFilter->GetName() ); + } + + // adjust view + m_xImpl->SelectFilterListEntry( m_xImpl->GetCurFilter()->GetName() ); + SetDefaultExt( m_xImpl->GetCurFilter()->GetExtension() ); + sal_Int32 nSepPos = GetDefaultExt().indexOf( FILEDIALOG_DEF_EXTSEP ); + if ( nSepPos != -1 ) + EraseDefaultExt( nSepPos ); + } + else + { + // if applicable set respectively create filter for all files + if ( !bHasAll ) + { + SvtFileDialogFilter_Impl* pAllFilter = implAddFilter( aAll, FILEDIALOG_FILTER_ALL ); + m_xImpl->InsertFilterListEntry( pAllFilter ); + m_xImpl->SetCurFilter( pAllFilter, aAll ); + } + m_xImpl->SelectFilterListEntry( aAll ); + } + + // if applicable isolate filter + OUString aFilter; + + if ( !IsolateFilterFromPath_Impl( m_aPath, aFilter ) ) + return false; + + AdjustFilterFlags nNewFilterFlags = adjustFilter( aFilter ); + if ( nNewFilterFlags & ( AdjustFilterFlags::NonEmpty | AdjustFilterFlags::UserFilter ) ) + { + m_xImpl->m_xEdFileName->set_entry_text( aFilter ); + } + + // create and show instance for set path + INetURLObject aFolderURL( m_aPath ); + OUString aFileName( aFolderURL.getName( INetURLObject::LAST_SEGMENT, false ) ); + sal_Int32 nFileNameLen = aFileName.getLength(); + bool bFileToSelect = nFileNameLen != 0; + if ( bFileToSelect && aFileName[ nFileNameLen - 1 ] != '/' ) + { + OUString aDecodedName = aFolderURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + m_xImpl->m_xEdFileName->set_entry_text( aDecodedName ); + aFolderURL.removeSegment(); + } + + INetURLObject aObj = aFolderURL; + if ( aObj.GetProtocol() == INetProtocol::File ) + { + // set folder as current directory + aObj.setFinalSlash(); + } + + UpdateControls( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + // Somebody might want to enable some controls according to the current filter + FilterSelect(); + + OpenURL_Impl( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + // if applicable read and set size from ini + InitSize(); + + return true; +} + +void SvtFileDialog::executeAsync( ::svt::AsyncPickerAction::Action eAction, + const OUString& rURL, const OUString& rFilter ) +{ + SAL_WARN_IF( m_pCurrentAsyncAction.is(), "fpicker.office", "SvtFileDialog::executeAsync: previous async action not yet finished!" ); + + m_pCurrentAsyncAction = new AsyncPickerAction( this, m_xFileView.get(), eAction ); + + bool bReallyAsync = true; + m_aConfiguration.getNodeValue( OUString( "FillAsynchronously" ) ) >>= bReallyAsync; + + sal_Int32 nMinTimeout = 0; + m_aConfiguration.getNodeValue( OUString( "Timeout/Min" ) ) >>= nMinTimeout; + sal_Int32 nMaxTimeout = 0; + m_aConfiguration.getNodeValue( OUString( "Timeout/Max" ) ) >>= nMaxTimeout; + + m_bInExecuteAsync = true; + m_pCurrentAsyncAction->execute(rURL, rFilter, bReallyAsync ? nMinTimeout : -1, nMaxTimeout, GetDenyList()); + m_bInExecuteAsync = false; +} + + +void SvtFileDialog::FileSelect() +{ + if (m_pFileNotifier) + m_pFileNotifier->notify( FILE_SELECTION_CHANGED, 0 ); +} + + +void SvtFileDialog::FilterSelect() +{ + if (m_pFileNotifier) + m_pFileNotifier->notify( CTRL_STATE_CHANGED, + LISTBOX_FILTER ); +} + + +/* [Description] + + This method sets the path for the default button. +*/ +void SvtFileDialog::SetStandardDir( const OUString& rStdDir ) +{ + INetURLObject aObj( rStdDir ); + SAL_WARN_IF( aObj.GetProtocol() == INetProtocol::NotValid, "fpicker.office", "Invalid protocol!" ); + aObj.setFinalSlash(); + m_xImpl->SetStandardDir( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); +} + +void SvtFileDialog::SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) +{ + m_xImpl->SetDenyList( rDenyList ); +} + + +const css::uno::Sequence< OUString >& SvtFileDialog::GetDenyList() const +{ + return m_xImpl->GetDenyList(); +} + + +/* [Description] + + This method returns the standard path. +*/ +const OUString& SvtFileDialog::GetStandardDir() const +{ + return m_xImpl->GetStandardDir(); +} + + +void SvtFileDialog::PrevLevel_Impl() +{ + m_xFileView->EndInplaceEditing(); + + OUString sDummy; + executeAsync( AsyncPickerAction::ePrevLevel, sDummy, sDummy ); +} + +void SvtFileDialog::OpenURL_Impl( const OUString& _rURL ) +{ + m_xFileView->EndInplaceEditing(); + + executeAsync( AsyncPickerAction::eOpenURL, _rURL, getMostCurrentFilter( m_xImpl ) ); +} + +SvtFileDialogFilter_Impl* SvtFileDialog::implAddFilter( const OUString& rFilter, const OUString& _rType ) +{ + SvtFileDialogFilter_Impl* pNewFilter = new SvtFileDialogFilter_Impl( rFilter, _rType ); + m_xImpl->m_aFilter.push_front( std::unique_ptr<SvtFileDialogFilter_Impl>( pNewFilter ) ); + + if ( !m_xImpl->GetCurFilter() ) + m_xImpl->SetCurFilter( pNewFilter, rFilter ); + + return pNewFilter; +} + +void SvtFileDialog::AddFilter( const OUString& rFilter, const OUString& _rType ) +{ + SAL_WARN_IF( m_bIsInExecute, "fpicker.office", "SvtFileDialog::AddFilter: currently executing!" ); + implAddFilter ( rFilter, _rType ); +} + + +void SvtFileDialog::AddFilterGroup( const OUString& rFilter, const Sequence< StringPair >& rFilters ) +{ + SAL_WARN_IF( m_bIsInExecute, "fpicker.office", "SvtFileDialog::AddFilter: currently executing!" ); + + implAddFilter( rFilter, OUString() ); + const StringPair* pSubFilters = rFilters.getConstArray(); + const StringPair* pSubFiltersEnd = pSubFilters + rFilters.getLength(); + for ( ; pSubFilters != pSubFiltersEnd; ++pSubFilters ) + implAddFilter( pSubFilters->First, pSubFilters->Second ); +} + + +void SvtFileDialog::SetCurFilter( const OUString& rFilter ) +{ + SAL_WARN_IF( m_bIsInExecute, "fpicker.office", "SvtFileDialog::SetCurFilter: currently executing!" ); + + // look for corresponding filter + sal_uInt16 nPos = m_xImpl->m_aFilter.size(); + + while ( nPos-- ) + { + SvtFileDialogFilter_Impl* pFilter = m_xImpl->m_aFilter[ nPos ].get(); + if ( pFilter->GetName() == rFilter ) + { + m_xImpl->SetCurFilter( pFilter, rFilter ); + break; + } + } +} + +OUString SvtFileDialog::GetCurFilter() const +{ + OUString aFilter; + + const SvtFileDialogFilter_Impl* pCurrentFilter = m_xImpl->GetCurFilter(); + if ( pCurrentFilter ) + aFilter = pCurrentFilter->GetName(); + + return aFilter; +} + +OUString SvtFileDialog::getCurFilter( ) const +{ + return GetCurFilter(); +} + +sal_uInt16 SvtFileDialog::GetFilterCount() const +{ + return m_xImpl->m_aFilter.size(); +} + +const OUString& SvtFileDialog::GetFilterName( sal_uInt16 nPos ) const +{ + assert( nPos < GetFilterCount() && "invalid index" ); + return m_xImpl->m_aFilter[ nPos ]->GetName(); +} + +void SvtFileDialog::InitSize() +{ + if (m_xImpl->m_aIniKey.isEmpty()) + return; + + // initialize from config + SvtViewOptions aDlgOpt( EViewType::Dialog, m_xImpl->m_aIniKey ); + + if ( aDlgOpt.Exists() ) + { + m_xDialog->set_window_state(OUStringToOString(aDlgOpt.GetWindowState(), RTL_TEXTENCODING_UTF8)); + + Any aUserData = aDlgOpt.GetUserItem( "UserData"); + OUString sCfgStr; + if ( aUserData >>= sCfgStr ) + m_xFileView->SetConfigString( sCfgStr ); + } +} + +std::vector<OUString> SvtFileDialog::GetPathList() const +{ + std::vector<OUString> aList; + + m_xFileView->selected_foreach([this, &aList](weld::TreeIter& rCurEntry){ + aList.push_back(m_xFileView->GetURL(rCurEntry)); + return false; + }); + + if (aList.empty()) + { + if ( !m_xImpl->m_xEdFileName->get_active_text().isEmpty() && m_bIsInExecute ) + aList.push_back(m_xImpl->m_xEdFileName->GetURL()); + else + aList.push_back(m_aPath); + } + + return aList; +} + +bool SvtFileDialog::IsolateFilterFromPath_Impl( OUString& rPath, OUString& rFilter ) +{ + OUString aReversePath = comphelper::string::reverseString(rPath); + sal_Int32 nQuestionMarkPos = rPath.indexOf( '?' ); + sal_Int32 nWildCardPos = rPath.indexOf( FILEDIALOG_DEF_WILDCARD ); + + if ( nQuestionMarkPos != -1 ) + { + // use question mark as wildcard only for files + INetProtocol eProt = INetURLObject::CompareProtocolScheme( rPath ); + + if ( INetProtocol::NotValid != eProt && INetProtocol::File != eProt ) + nQuestionMarkPos = -1; + + nWildCardPos = std::min( nWildCardPos, nQuestionMarkPos ); + } + + rFilter.clear(); + + if ( nWildCardPos == -1 ) + return true; + + sal_Int32 nPathTokenPos = aReversePath.indexOf( '/' ); + + if ( nPathTokenPos == -1 ) + { + OUString aDelim( +#if defined(_WIN32) + '\\' +#else + '/' +#endif + ); + + nPathTokenPos = aReversePath.indexOf( aDelim ); +#if !defined( UNX ) + if ( nPathTokenPos == -1 ) + { + nPathTokenPos = aReversePath.indexOf( ':' ); + } +#endif + } + + // check syntax + if ( nPathTokenPos != -1 ) + { + if ( nPathTokenPos < (rPath.getLength() - nWildCardPos - 1) ) + { + ErrorHandler::HandleError( ERRCODE_SFX_INVALIDSYNTAX ); + return false; + } + + // cut off filter + rFilter = aReversePath.copy( 0, nPathTokenPos ); + rFilter = comphelper::string::reverseString(rFilter); + + // determine folder + rPath = aReversePath.copy( nPathTokenPos ); + rPath = comphelper::string::reverseString(rPath); + } + else + { + rFilter = rPath; + rPath.clear(); + } + + return true; +} + +IMPL_LINK_NOARG(SvtFileDialog, SizeAllocHdl, const Size&, void) +{ + if (m_pFileNotifier) + m_pFileNotifier->notify(DIALOG_SIZE_CHANGED, 0); +} + +weld::Widget* SvtFileDialog::getControl( sal_Int16 nControlId, bool bLabelControl ) const +{ + weld::Widget* pReturn = nullptr; + + switch ( nControlId ) + { + case CONTROL_FILEVIEW: + pReturn = bLabelControl ? nullptr : m_xFileView->identifier(); + break; + + case EDIT_FILEURL: + pReturn = bLabelControl + ? static_cast<weld::Widget*>(m_xImpl->m_xFtFileName.get()) + : static_cast<weld::Widget*>(m_xImpl->m_xEdFileName->getWidget()); + break; + + case EDIT_FILEURL_LABEL: + pReturn = m_xImpl->m_xFtFileName.get(); + break; + + case CHECKBOX_AUTOEXTENSION: + pReturn = m_xImpl->m_xCbAutoExtension.get(); + break; + + case CHECKBOX_PASSWORD: + pReturn = m_xImpl->m_xCbPassword.get(); + break; + + case CHECKBOX_GPGENCRYPTION: + pReturn = m_xImpl->m_xCbGPGEncrypt.get(); + break; + + case CHECKBOX_FILTEROPTIONS: + pReturn = m_xImpl->m_xCbOptions.get(); + break; + + case CHECKBOX_READONLY: + pReturn = m_xCbReadOnly.get(); + break; + + case CHECKBOX_LINK: + pReturn = m_xCbLinkBox.get(); + break; + + case CHECKBOX_PREVIEW: + pReturn = m_xCbPreviewBox.get(); + break; + + case CHECKBOX_SELECTION: + pReturn = m_xCbSelection.get(); + break; + + case LISTBOX_FILTER: + pReturn = bLabelControl ? m_xImpl->m_xFtFileType.get() : m_xImpl->GetFilterListControl(); + break; + + case LISTBOX_FILTER_LABEL: + pReturn = m_xImpl->m_xFtFileType.get(); + break; + + case FIXEDTEXT_CURRENTFOLDER: + pReturn = m_xImpl->m_xEdCurrentPath->getWidget(); + break; + + case LISTBOX_VERSION: + pReturn = bLabelControl + ? static_cast<weld::Widget*>(m_xImpl->m_xSharedLabel.get()) + : static_cast<weld::Widget*>(m_xImpl->m_xSharedListBox.get()); + break; + + case LISTBOX_TEMPLATE: + pReturn = bLabelControl + ? static_cast<weld::Widget*>(m_xImpl->m_xSharedLabel.get()) + : static_cast<weld::Widget*>(m_xImpl->m_xSharedListBox.get()); + break; + + case LISTBOX_IMAGE_TEMPLATE: + pReturn = bLabelControl + ? static_cast<weld::Widget*>(m_xImpl->m_xSharedLabel.get()) + : static_cast<weld::Widget*>(m_xImpl->m_xSharedListBox.get()); + break; + + case LISTBOX_IMAGE_ANCHOR: + pReturn = bLabelControl + ? static_cast<weld::Widget*>(m_xImpl->m_xSharedLabel.get()) + : static_cast<weld::Widget*>(m_xImpl->m_xSharedListBox.get()); + break; + + case LISTBOX_VERSION_LABEL: + pReturn = m_xImpl->m_xSharedLabel.get(); + break; + + case LISTBOX_TEMPLATE_LABEL: + pReturn = m_xImpl->m_xSharedLabel.get(); + break; + + case LISTBOX_IMAGE_TEMPLATE_LABEL: + pReturn = m_xImpl->m_xSharedLabel.get(); + break; + + case LISTBOX_IMAGE_ANCHOR_LABEL: + pReturn = m_xImpl->m_xSharedLabel.get(); + break; + + case PUSHBUTTON_OK: + pReturn = m_xImpl->m_xBtnFileOpen.get(); + break; + + case PUSHBUTTON_CANCEL: + pReturn = m_xImpl->m_xBtnCancel.get(); + break; + + case PUSHBUTTON_PLAY: + pReturn = m_xPbPlay.get(); + break; + + case PUSHBUTTON_HELP: + pReturn = m_xImpl->m_xBtnHelp.get(); + break; + + case TOOLBOXBUTTON_LEVEL_UP: + pReturn = m_xImpl->m_xBtnUp->getWidget(); + break; + + case TOOLBOXBUTTON_NEW_FOLDER: + pReturn = m_xImpl->m_xBtnNewFolder.get(); + break; + + case LISTBOX_FILTER_SELECTOR: + // only exists on SalGtkFilePicker + break; + + default: + SAL_WARN( "fpicker.office", "SvtFileDialog::getControl: invalid id!" ); + } + return pReturn; +} + +void SvtFileDialog::enableControl(sal_Int16 nControlId, bool bEnable) +{ + weld::Widget* pControl = getControl(nControlId); + if (pControl) + EnableControl(pControl, bEnable); + weld::Widget* pLabel = getControl(nControlId, true); + if (pLabel) + EnableControl(pLabel, bEnable); +} + +void SvtFileDialog::AddControls_Impl( ) +{ + // create the "insert as link" checkbox, if needed + if ( m_nPickerFlags & PickerFlags::InsertAsLink ) + { + m_xCbLinkBox->set_label( FpsResId( STR_SVT_FILEPICKER_INSERT_AS_LINK ) ); + m_xCbLinkBox->set_help_id( HID_FILEDLG_LINK_CB ); + m_xCbLinkBox->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xCbLinkBox->show(); + } + + // create the "show preview" checkbox ( and the preview window, too ), if needed + if ( m_nPickerFlags & PickerFlags::ShowPreview ) + { + m_xImpl->m_aIniKey = "ImportGraphicDialog"; + + // "preview" + m_xCbPreviewBox->set_label( FpsResId( STR_SVT_FILEPICKER_SHOW_PREVIEW ) ); + m_xCbPreviewBox->set_help_id( HID_FILEDLG_PREVIEW_CB ); + m_xCbPreviewBox->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xCbPreviewBox->show(); + + // generate preview window just here + m_aPreviewSize = Size(200, 300); + m_xPrevBmp->set_size_request(m_aPreviewSize.Width(), m_aPreviewSize.Height()); + m_xPrevBmp->connect_size_allocate(LINK(this, SvtFileDialog, PreviewSizeAllocHdl)); + m_xPreviewFrame->show(); + m_xPrevBmp->set_accessible_name(FpsResId(STR_PREVIEW)); + } + + if ( m_nPickerFlags & PickerFlags::AutoExtension ) + { + m_xImpl->m_xCbAutoExtension->set_label( FpsResId( STR_SVT_FILEPICKER_AUTO_EXTENSION ) ); + m_xImpl->m_xCbAutoExtension->set_active(true); + m_xImpl->m_xCbAutoExtension->connect_toggled( LINK( this, SvtFileDialog, AutoExtensionHdl_Impl ) ); + m_xImpl->m_xCbAutoExtension->show(); + } + + if ( m_nPickerFlags & PickerFlags::FilterOptions ) + { + m_xImpl->m_xCbOptions->set_label( FpsResId( STR_SVT_FILEPICKER_FILTER_OPTIONS ) ); + m_xImpl->m_xCbOptions->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xImpl->m_xCbOptions->show(); + } + + if ( m_nPickerFlags & PickerFlags::Selection ) + { + m_xCbSelection->set_label( FpsResId( STR_SVT_FILEPICKER_SELECTION ) ); + m_xCbSelection->connect_toggled( LINK( this, SvtFileDialog, ClickHdl_Impl ) ); + m_xCbSelection->show(); + } + + if ( m_nPickerFlags & PickerFlags::PlayButton ) + { + m_xPbPlay->set_label( FpsResId( STR_SVT_FILEPICKER_PLAY ) ); + m_xPbPlay->set_help_id( HID_FILESAVE_DOPLAY ); + m_xPbPlay->connect_clicked( LINK( this, SvtFileDialog, PlayButtonHdl_Impl ) ); + m_xPbPlay->show(); + } + + if ( m_nPickerFlags & PickerFlags::ShowVersions ) + { + m_xImpl->m_xSharedLabel->set_label( FpsResId( STR_SVT_FILEPICKER_VERSION ) ); + m_xImpl->m_xSharedLabel->show(); + + m_xImpl->m_xSharedListBox->set_help_id( HID_FILEOPEN_VERSION ); + m_xImpl->m_xSharedListBox->show(); + } + else if ( m_nPickerFlags & PickerFlags::Templates ) + { + m_xImpl->m_xSharedLabel->set_label( FpsResId( STR_SVT_FILEPICKER_TEMPLATES ) ); + m_xImpl->m_xSharedLabel->show(); + + m_xImpl->m_xSharedListBox->set_help_id( HID_FILEOPEN_VERSION ); + m_xImpl->m_xSharedListBox->show(); + // This is strange. During the re-factoring during 96930, I discovered that this help id + // is set in the "Templates mode". This was hidden in the previous implementation. + // Shouldn't this be a more meaningful help id. + } + else if ( m_nPickerFlags & PickerFlags::ImageTemplate ) + { + m_xImpl->m_xSharedLabel->set_label( FpsResId( STR_SVT_FILEPICKER_IMAGE_TEMPLATE ) ); + m_xImpl->m_xSharedLabel->show(); + + m_xImpl->m_xSharedListBox->set_help_id( HID_FILEOPEN_IMAGE_TEMPLATE ); + m_xImpl->m_xSharedListBox->show(); + } + else if ( m_nPickerFlags & PickerFlags::ImageAnchor ) + { + m_xImpl->m_xSharedLabel->set_label( FpsResId( STR_SVT_FILEPICKER_IMAGE_ANCHOR ) ); + m_xImpl->m_xSharedLabel->show(); + + m_xImpl->m_xSharedListBox->set_help_id( HID_FILEOPEN_IMAGE_ANCHOR ); + m_xImpl->m_xSharedListBox->show(); + } + + m_xImpl->m_xPlaces.reset(new PlacesListBox(m_xBuilder->weld_tree_view("places"), + m_xBuilder->weld_button("add"), + m_xBuilder->weld_button("del"), + this)); + m_xImpl->m_xPlaces->set_help_id("SVT_HID_FILESAVE_PLACES_LISTBOX"); + m_xImpl->m_xPlaces->SetAddHdl( LINK ( this, SvtFileDialog, AddPlacePressed_Hdl ) ); + m_xImpl->m_xPlaces->SetDelHdl( LINK ( this, SvtFileDialog, RemovePlacePressed_Hdl ) ); + + initDefaultPlaces(); +} + +IMPL_LINK(SvtFileDialog, PreviewSizeAllocHdl, const Size&, rSize, void) +{ + m_aPreviewSize = rSize; +} + +sal_Int32 SvtFileDialog::getAvailableWidth() +{ + if (m_xPrevBmp) + return m_aPreviewSize.Width(); + else + return 0; +} + +sal_Int32 SvtFileDialog::getAvailableHeight() +{ + if (m_xPrevBmp) + return m_aPreviewSize.Height(); + else + return 0; +} + +void SvtFileDialog::setImage(const Any& rImage) +{ + if (!m_xPrevBmp || !m_xPreviewFrame->get_visible()) + return; + + Sequence < sal_Int8 > aBmpSequence; + + if ( rImage >>= aBmpSequence ) + { + BitmapEx aBmp; + SvMemoryStream aData( aBmpSequence.getArray(), + aBmpSequence.getLength(), + StreamMode::READ ); + ReadDIBBitmapEx(aBmp, aData); + + m_xPrevBmp->set_image(Graphic(aBmp).GetXGraphic()); + } + else + { + m_xPrevBmp->set_image(nullptr); + } +} + +OUString SvtFileDialog::getCurrentFileText( ) const +{ + OUString sReturn; + if (m_xImpl && m_xImpl->m_xEdFileName) + sReturn = m_xImpl->m_xEdFileName->get_active_text(); + return sReturn; +} + +void SvtFileDialog::setCurrentFileText( const OUString& _rText, bool m_bSelectAll ) +{ + if (m_xImpl && m_xImpl->m_xEdFileName) + { + m_xImpl->m_xEdFileName->set_entry_text( _rText ); + if ( m_bSelectAll ) + m_xImpl->m_xEdFileName->select_entry_region(0, -1); + } +} + +bool SvtFileDialog::isAutoExtensionEnabled() const +{ + return m_xImpl->m_xCbAutoExtension && m_xImpl->m_xCbAutoExtension->get_active(); +} + +bool SvtFileDialog::getShowState() +{ + if (m_xPreviewFrame) + return m_xPreviewFrame->get_visible(); + else + return false; +} + +bool SvtFileDialog::ContentHasParentFolder( const OUString& rURL ) +{ + m_aContent.bindTo( rURL ); + + if ( m_aContent.isInvalid() ) + return false; + + return m_aContent.hasParentFolder( ) && m_aContent.isValid(); +} + +bool SvtFileDialog::ContentCanMakeFolder( const OUString& rURL ) +{ + m_aContent.bindTo( rURL ); + + if ( m_aContent.isInvalid() ) + return false; + + return m_aContent.canCreateFolder( ) && m_aContent.isValid(); +} + +bool SvtFileDialog::ContentGetTitle( const OUString& rURL, OUString& rTitle ) +{ + m_aContent.bindTo( rURL ); + + if ( m_aContent.isInvalid() ) + return false; + + OUString sTitle; + m_aContent.getTitle( sTitle ); + rTitle = sTitle; + + return m_aContent.isValid(); +} + +void SvtFileDialog::appendDefaultExtension(OUString& rFileName, + std::u16string_view rFilterDefaultExtension, + const OUString& rFilterExtensions) +{ + const OUString aType(rFilterExtensions.toAsciiLowerCase()); + + if ( aType == FILEDIALOG_FILTER_ALL ) + return; + + const OUString aTemp(rFileName.toAsciiLowerCase()); + sal_Int32 nPos = 0; + + do + { + if (nPos+1<aType.getLength() && aType[nPos]=='*') // take care of a leading * + ++nPos; + const std::u16string_view aExt(o3tl::getToken(aType, 0, FILEDIALOG_DEF_EXTSEP, nPos )); + if (aExt.empty()) + continue; + if (o3tl::ends_with(aTemp, aExt)) + return; + } + while (nPos>=0); + + rFileName += OUString::Concat(".") + rFilterDefaultExtension; +} + +void SvtFileDialog::initDefaultPlaces( ) +{ + PlacePtr pRootPlace = std::make_shared<Place>( FpsResId(STR_DEFAULT_DIRECTORY), GetStandardDir() ); + m_xImpl->m_xPlaces->AppendPlace( pRootPlace ); + + // Load from user settings + Sequence< OUString > placesUrlsList(officecfg::Office::Common::Misc::FilePickerPlacesUrls::get()); + Sequence< OUString > placesNamesList(officecfg::Office::Common::Misc::FilePickerPlacesNames::get()); + + for(sal_Int32 nPlace = 0; nPlace < placesUrlsList.getLength() && nPlace < placesNamesList.getLength(); ++nPlace) + { + PlacePtr pPlace = std::make_shared<Place>(placesNamesList[nPlace], placesUrlsList[nPlace], true); + m_xImpl->m_xPlaces->AppendPlace(pPlace); + } + + // Reset the placesList "updated" state + m_xImpl->m_xPlaces->IsUpdated(); +} + +QueryFolderNameDialog::QueryFolderNameDialog(weld::Window* _pParent, + const OUString& rTitle, const OUString& rDefaultText) + : GenericDialogController(_pParent, "fps/ui/foldernamedialog.ui", "FolderNameDialog") + , m_xNameEdit(m_xBuilder->weld_entry("entry")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) +{ + m_xDialog->set_title(rTitle); + m_xNameEdit->set_text(rDefaultText); + m_xNameEdit->select_region(0, -1); + m_xOKBtn->connect_clicked(LINK(this, QueryFolderNameDialog, OKHdl)); + m_xNameEdit->connect_changed(LINK(this, QueryFolderNameDialog, NameHdl)); +}; + +QueryFolderNameDialog::~QueryFolderNameDialog() +{ +} + +IMPL_LINK_NOARG(QueryFolderNameDialog, OKHdl, weld::Button&, void) +{ + // trim the strings + m_xNameEdit->set_text(comphelper::string::strip(m_xNameEdit->get_text(), ' ')); + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(QueryFolderNameDialog, NameHdl, weld::Entry&, void) +{ + // trim the strings + OUString aName = comphelper::string::strip(m_xNameEdit->get_text(), ' '); + m_xOKBtn->set_sensitive(!aName.isEmpty()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/iodlg.hxx b/fpicker/source/office/iodlg.hxx new file mode 100644 index 000000000..9de9261af --- /dev/null +++ b/fpicker/source/office/iodlg.hxx @@ -0,0 +1,302 @@ +/* -*- 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 <memory> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/ucb/IOErrorCode.hpp> +#include <unotools/confignode.hxx> +#include "asyncfilepicker.hxx" +#include "fpsmartcontent.hxx" +#include "fpdialogbase.hxx" +#include <o3tl/typed_flags_set.hxx> +#include <vcl/timer.hxx> + +#include <set> +#include <string_view> + +class SvtFileView; +class SvtFileDialogFilter_Impl; +class SvtExpFileDlg_Impl; +class SvtURLBox; + +enum class AdjustFilterFlags { + NONE = 0x0000, + NonEmpty = 0x0001, + Changed = 0x0002, + UserFilter = 0x0004, +}; +namespace o3tl { + template<> struct typed_flags<AdjustFilterFlags> : is_typed_flags<AdjustFilterFlags, 0x0007> {}; +} + + +class SvtFileDialog final : public SvtFileDialog_Base +{ +private: + std::unique_ptr<weld::CheckButton> m_xCbReadOnly; + std::unique_ptr<weld::CheckButton> m_xCbLinkBox; + std::unique_ptr<weld::CheckButton> m_xCbPreviewBox; + std::unique_ptr<weld::CheckButton> m_xCbSelection; + std::unique_ptr<weld::Button> m_xPbPlay; + std::unique_ptr<weld::Widget> m_xPreviewFrame; + std::unique_ptr<weld::Image> m_xPrevBmp; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<SvtFileView> m_xFileView; + ::svt::IFilePickerListener* m_pFileNotifier; + std::unique_ptr<SvtExpFileDlg_Impl> m_xImpl; + Size m_aPreviewSize; + PickerFlags m_nPickerFlags; + bool m_bIsInExecute : 1; + + ::svt::SmartContent m_aContent; + + ::std::set<weld::Widget*> m_aDisabledControls; + + ::utl::OConfigurationNode m_aConfiguration; + ::rtl::Reference< ::svt::AsyncPickerAction > + m_pCurrentAsyncAction; + bool m_bInExecuteAsync; + bool m_bHasFilename; + + DECL_LINK( FilterSelectHdl_Impl, weld::ComboBox&, void ); + DECL_LINK( FilterSelectTimerHdl_Impl, Timer*, void ); + DECL_LINK( NewFolderHdl_Impl, weld::Button&, void ); + DECL_LINK( OpenUrlHdl_Impl, weld::ComboBox&, bool ); + DECL_LINK( OpenClickHdl_Impl, weld::Button&, void ); + DECL_LINK( CancelHdl_Impl, weld::Button&, void ); + DECL_LINK( FileNameGetFocusHdl_Impl, weld::Widget&, void ); + DECL_LINK( FileNameModifiedHdl_Impl, weld::ComboBox&, void ); + + DECL_LINK( URLBoxModifiedHdl_Impl, weld::ComboBox&, bool ); + DECL_LINK( ConnectToServerPressed_Hdl, weld::Button&, void ); + + DECL_LINK( AddPlacePressed_Hdl, weld::Button&, void ); + DECL_LINK( RemovePlacePressed_Hdl, weld::Button&, void ); + DECL_LINK( PreviewSizeAllocHdl, const Size&, void); + + void OpenHdl_Impl(void const * pVoid); + + /** find a filter with the given wildcard + @param _rFilter + the wildcard pattern to look for in the filter list + @param _bMultiExt + allow for filters with more than one extension pattern + @param _rFilterChanged + set to <TRUE/> if the filter changed + @return + the filter which has been found + */ + SvtFileDialogFilter_Impl* FindFilter_Impl( const OUString& _rFilter, + bool _bMultiExt, + bool& _rFilterChanged + ); + void ExecuteFilter(); + void OpenMultiSelection_Impl(); + void AddControls_Impl( ); + + DECL_LINK(SelectHdl_Impl, SvtFileView*, void); + DECL_LINK(DblClickHdl_Impl, SvtFileView*, bool); + DECL_LINK(EntrySelectHdl_Impl, weld::ComboBox&, void); + DECL_LINK(OpenDoneHdl_Impl, SvtFileView*, void); + DECL_LINK(AutoExtensionHdl_Impl, weld::Toggleable&, void); + DECL_LINK(ClickHdl_Impl, weld::Toggleable&, void); + DECL_LINK(PlayButtonHdl_Impl, weld::Button&, void); + DECL_LINK(SizeAllocHdl, const Size&, void); + + // removes a filter with wildcards from the path and returns it + static bool IsolateFilterFromPath_Impl( OUString& rPath, OUString& rFilter ); + + OUString m_aPath; + OUString m_aDefExt; + + /** enables or disables the complete UI of the file picker, with only offering a + cancel button + + This method preserves the "enabled" state of its controls in the following sense: + If you disable a certain control, then disable the dialog UI, then enable the dialog + UI, the control will still be disabled. + This is under the assumption that you'll use EnableControl. Direct access to the control + (such as pControl->Enable()) will break this. + */ + void EnableUI( bool _bEnable ); + + /** enables or disables a control + + You are strongly encouraged to prefer this method over pControl->Enable( bEnable ). See + <member>EnableUI</member> for details. + */ + void EnableControl(weld::Widget* pControl, bool bEnable); + virtual bool PrepareExecute() override; + +public: + SvtFileDialog( weld::Window* pParent, PickerFlags nBits ); + virtual ~SvtFileDialog() override; + + virtual short run() override; + + void FileSelect(); + void FilterSelect() override; + + void SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) override; + const css::uno::Sequence< OUString >& GetDenyList() const override; + void SetStandardDir( const OUString& rStdDir ) override; + const OUString& GetStandardDir() const override; + std::vector<OUString> GetPathList() const override; // for MultiSelection + + void AddFilter( const OUString& rFilter, + const OUString& rType ) override; + + void AddFilterGroup( + const OUString& _rFilter, + const css::uno::Sequence< css::beans::StringPair >& rFilters ) override; + + void SetCurFilter( const OUString& rFilter ) override; + OUString GetCurFilter() const override; + sal_uInt16 GetFilterCount() const; + const OUString& GetFilterName( sal_uInt16 nPos ) const; + + void PrevLevel_Impl(); + void OpenURL_Impl( const OUString& rURL ); + + SvtFileView* GetView() override; + + void InitSize(); + void UpdateControls( const OUString& rURL ) override; + void EnableAutocompletion( bool _bEnable = true ) override; + + void SetFileCallback( ::svt::IFilePickerListener *pNotifier ) override { m_pFileNotifier = pNotifier; } + + sal_Int32 getAvailableWidth() override; + sal_Int32 getAvailableHeight() override; + void setImage( const css::uno::Any& rImage ) override; + bool getShowState() override; + bool isAutoExtensionEnabled() const; + + OUString getCurrentFileText( ) const override; + void setCurrentFileText( const OUString& _rText, bool _bSelectAll = false ) override; + + void onAsyncOperationStarted() override; + void onAsyncOperationFinished() override; + + void RemovablePlaceSelected(bool enable = true); + + static void displayIOException( const OUString& _rURL, css::ucb::IOErrorCode _eCode ); + + // inline + inline void SetPath( const OUString& rNewURL ) override; + inline void SetHasFilename( bool bHasFilename ) override; + inline const OUString& GetPath() override; + inline void SetDefaultExt( const OUString& rExt ); + inline void EraseDefaultExt( sal_Int32 _nIndex = 0 ); + inline const OUString& GetDefaultExt() const; + + bool ContentIsFolder( const OUString& rURL ) override { return m_aContent.isFolder( rURL ) && m_aContent.isValid(); } + bool ContentHasParentFolder( const OUString& rURL ); + bool ContentCanMakeFolder( const OUString& rURL ); + bool ContentGetTitle( const OUString& rURL, OUString& rTitle ); + +private: + SvtFileDialogFilter_Impl* implAddFilter( const OUString& _rFilter, const OUString& _rType ); + + /** updates m_xUserFilter with a new filter + <p>No checks for necessity are made.</p> + */ + void createNewUserFilter( const OUString& _rNewFilter ); + + AdjustFilterFlags adjustFilter( const OUString& _rFilter ); + + // IFilePickerController, needed by OControlAccess + virtual weld::Widget* getControl( sal_Int16 nControlId, bool bLabelControl = false ) const override; + virtual void enableControl( sal_Int16 _nControlId, bool _bEnable ) override; + virtual OUString getCurFilter( ) const override; + + OUString implGetInitialURL( const OUString& _rPath, std::u16string_view _rFallback ); + + /// executes a certain FileView action asynchronously + void executeAsync( + ::svt::AsyncPickerAction::Action _eAction, + const OUString& _rURL, + const OUString& _rFilter + ); + + /** helper function to check and append the default filter extension if + necessary. + The function checks if the specified filename already contains one of + the valid extensions of the specified filter. If not the filter default + extension is appended to the filename. + + @param _rFileName the filename which is checked and extended if necessary. + @param _rFilterDefaultExtension the default extension of the used filter. + @param _rFilterExtensions a list of one or more valid filter extensions + of the used filter. + + */ + static void appendDefaultExtension( + OUString& _rFileName, + std::u16string_view _rFilterDefaultExtension, + const OUString& _rFilterExtensions); + + void initDefaultPlaces( ); +}; + + +inline void SvtFileDialog::SetPath( const OUString& rNewURL ) +{ + m_aPath = rNewURL; +} + + +inline void SvtFileDialog::SetHasFilename( bool bHasFilename ) +{ + m_bHasFilename = bHasFilename; +} + + +inline const OUString& SvtFileDialog::GetPath() +{ + return m_aPath; +} + + +inline void SvtFileDialog::SetDefaultExt( const OUString& rExt ) +{ + m_aDefExt = rExt; +} + +inline void SvtFileDialog::EraseDefaultExt( sal_Int32 _nIndex ) +{ + m_aDefExt = m_aDefExt.copy( 0, _nIndex ); +} + +inline const OUString& SvtFileDialog::GetDefaultExt() const +{ + return m_aDefExt; +} + + +inline SvtFileView* SvtFileDialog::GetView() +{ + return m_xFileView.get(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/iodlgimp.cxx b/fpicker/source/office/iodlgimp.cxx new file mode 100644 index 000000000..e9e8e0049 --- /dev/null +++ b/fpicker/source/office/iodlgimp.cxx @@ -0,0 +1,190 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include "fileview.hxx" +#include "iodlgimp.hxx" +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <svtools/inettbc.hxx> +#include "iodlg.hxx" +#include <svtools/imagemgr.hxx> +#include <svl/svlresid.hxx> +#include <svl/svl.hrc> +#include <utility> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::utl; + +SvtFileDialogFilter_Impl::SvtFileDialogFilter_Impl( OUString aName, OUString aType ) + : m_aName(std::move( aName )) + , m_aType(std::move( aType )) +{ + m_aType = m_aType.toAsciiLowerCase(); +} + +SvtFileDialogFilter_Impl::~SvtFileDialogFilter_Impl() +{ +} + +//= SvtUpButton_Impl +SvtUpButton_Impl::SvtUpButton_Impl(std::unique_ptr<weld::Toolbar> xToolbar, + std::unique_ptr<weld::Menu> xMenu, + SvtFileDialog* pDlg) + : m_xToolbar(std::move(xToolbar)) + , m_xMenu(std::move(xMenu)) + , m_pDlg(pDlg) +{ + m_xToolbar->set_item_menu("up_btn", m_xMenu.get()); + m_xToolbar->connect_clicked(LINK(this, SvtUpButton_Impl, ClickHdl)); + m_xMenu->connect_activate(LINK(this, SvtUpButton_Impl, SelectHdl)); +} + +void SvtUpButton_Impl::FillURLMenu() +{ + SvtFileView* pBox = m_pDlg->GetView(); + + sal_uInt16 nItemId = 1; + + aURLs.clear(); + m_xMenu->clear(); + + // determine parent levels + INetURLObject aObject( pBox->GetViewURL() ); + sal_Int32 nCount = aObject.getSegmentCount(); + + ::svtools::VolumeInfo aVolInfo( true /* volume */, false /* remote */, + false /* removable */, false /* floppy */, + false /* compact disk */ ); + OUString aVolumeImage( SvFileInformationManager::GetFolderImageId( aVolInfo ) ); + + while ( nCount >= 1 ) + { + aObject.removeSegment(); + OUString aParentURL(aObject.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + + OUString aTitle; + + if (nCount == 1) // adjust the title of the top level entry (the workspace) + aTitle = SvlResId(STR_SVT_MIMETYPE_CNT_FSYSBOX); + else if (!m_pDlg->ContentGetTitle(aParentURL, aTitle) || aTitle.isEmpty()) + aTitle = aObject.getName(); + + OUString aImage = ( nCount > 1 ) // if nCount == 1 means workplace, which detects the wrong image + ? SvFileInformationManager::GetImageId( aObject ) : aVolumeImage; + + m_xMenu->append(OUString::number(nItemId), aTitle, aImage); + aURLs.push_back(aParentURL); + + ++nItemId; + --nCount; + } +} + +IMPL_LINK(SvtUpButton_Impl, SelectHdl, const OString&, rId, void) +{ + sal_uInt32 nId = rId.toUInt32(); + if (nId) + { + --nId; + assert( nId <= aURLs.size() && "SvtUpButton_Impl: wrong index" ); + + m_pDlg->OpenURL_Impl(aURLs[nId]); + } +} + +IMPL_LINK_NOARG(SvtUpButton_Impl, ClickHdl, const OString&, void) +{ + m_pDlg->PrevLevel_Impl(); +} + +// SvtExpFileDlg_Impl +SvtExpFileDlg_Impl::SvtExpFileDlg_Impl() + : m_pCurFilter( nullptr ) + , m_eMode( FILEDLG_MODE_OPEN ) + , m_eDlgType( FILEDLG_TYPE_FILEDLG ) + , m_nStyle( PickerFlags::NONE ) + , m_aFilterIdle("fpicker SvtExpFileDlg_Impl m_aFilterIdle") + , m_bDoubleClick( false ) + , m_bMultiSelection( false ) +{ +} + +SvtExpFileDlg_Impl::~SvtExpFileDlg_Impl() +{ +} + +void SvtExpFileDlg_Impl::SetStandardDir( const OUString& _rDir ) +{ + m_aStdDir = _rDir; + if (m_aStdDir.isEmpty()) + m_aStdDir = "file:///"; +} + +namespace { + OUString lcl_DecoratedFilter( std::u16string_view _rOriginalFilter ) + { + return "<" + OUString::Concat(_rOriginalFilter) + ">"; + } +} + +void SvtExpFileDlg_Impl::SetCurFilter( SvtFileDialogFilter_Impl const * pFilter, const OUString& rDisplayName ) +{ + DBG_ASSERT( pFilter, "SvtExpFileDlg_Impl::SetCurFilter: invalid filter!" ); + DBG_ASSERT( ( rDisplayName == pFilter->GetName() ) + || ( rDisplayName == lcl_DecoratedFilter( pFilter->GetName() ) ), + "SvtExpFileDlg_Impl::SetCurFilter: arguments are inconsistent!" ); + + m_pCurFilter = pFilter; + m_sCurrentFilterDisplayName = rDisplayName; +} + +void SvtExpFileDlg_Impl::InsertFilterListEntry(const SvtFileDialogFilter_Impl* pFilterDesc) +{ + // insert and set user data + OUString sId(weld::toId(pFilterDesc)); + OUString sName = pFilterDesc->GetName(); + if (pFilterDesc->isGroupSeparator()) + m_xLbFilter->append_separator(sId); + else + m_xLbFilter->append(sId, sName); +} + +void SvtExpFileDlg_Impl::InitFilterList( ) +{ + // clear the current list + m_xLbFilter->clear(); + + // reinit it + sal_uInt16 nPos = m_aFilter.size(); + + // search for the first entry which is no group separator + while ( nPos-- && m_aFilter[ nPos ]->isGroupSeparator() ) + ; + + // add all following entries + while ( static_cast<sal_Int16>(nPos) >= 0 ) + InsertFilterListEntry( m_aFilter[ nPos-- ].get() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/iodlgimp.hxx b/fpicker/source/office/iodlgimp.hxx new file mode 100644 index 000000000..caac91793 --- /dev/null +++ b/fpicker/source/office/iodlgimp.hxx @@ -0,0 +1,205 @@ +/* -*- 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 "PlacesListBox.hxx" + +#include <vcl/idle.hxx> + +#include <deque> +#include <memory> +#include <vector> + +class CheckBox; +class SvtFileDialog; + +#define FILEDIALOG_DEF_EXTSEP ';' +#define FILEDIALOG_DEF_WILDCARD '*' + + +// SvtFileDialogFilter_Impl + + +/* [Description] + + Instances of this class represent a filter. +*/ + +class SvtFileDialogFilter_Impl +{ +private: + OUString m_aName; // name of the entry + OUString m_aType; // filter wildcard - if empty, the entry marks a group + +public: + SvtFileDialogFilter_Impl( OUString aName, OUString aType ); + ~SvtFileDialogFilter_Impl(); + + const OUString& GetName() const { return m_aName; } + const OUString& GetType() const { return m_aType; } + OUString GetExtension() const { return m_aType.getLength() > 2 ? m_aType.copy( 2 ) : OUString(); } + + bool isGroupSeparator() const { return m_aType.isEmpty(); } +}; + +typedef std::deque<std::unique_ptr<SvtFileDialogFilter_Impl>> SvtFileDialogFilterList_Impl; + +enum SvtFileDlgMode +{ + FILEDLG_MODE_OPEN = 0, + FILEDLG_MODE_SAVE = 1 +}; + +enum SvtFileDlgType +{ + FILEDLG_TYPE_FILEDLG = 0, + FILEDLG_TYPE_PATHDLG +}; + +class SvtUpButton_Impl +{ +private: + std::unique_ptr<weld::Toolbar> m_xToolbar; + std::unique_ptr<weld::Menu> m_xMenu; + SvtFileDialog* m_pDlg; + + std::vector<OUString> aURLs; + +public: + SvtUpButton_Impl(std::unique_ptr<weld::Toolbar> xToolbar, + std::unique_ptr<weld::Menu> xMenu, + SvtFileDialog* pDlg); + + void set_help_id(const OString& rHelpId) { m_xToolbar->set_help_id(rHelpId); } + void show() { m_xToolbar->show(); } + + void FillURLMenu(); + + weld::Widget* getWidget() { return m_xToolbar.get(); } + +private: + + DECL_LINK(SelectHdl, const OString&, void); + DECL_LINK(ClickHdl, const OString&, void); +}; + +class SvtURLBox; +class SvtExpFileDlg_Impl +{ +private: + const SvtFileDialogFilter_Impl* m_pCurFilter; + OUString m_sCurrentFilterDisplayName; // may differ from m_pCurFilter->GetName in case it is a cached entry + + css::uno::Sequence< OUString > m_aDenyList; + +public: + SvtFileDialogFilterList_Impl m_aFilter; + std::unique_ptr<SvtFileDialogFilter_Impl> m_xUserFilter; + + std::unique_ptr<weld::Label> m_xFtFileName; + std::unique_ptr<SvtURLBox> m_xEdFileName; + + std::unique_ptr<weld::Label> m_xSharedLabel; + std::unique_ptr<weld::ComboBox> m_xSharedListBox; + + std::unique_ptr<weld::Label> m_xFtFileType; + std::unique_ptr<weld::ComboBox> m_xLbFilter; + std::unique_ptr<weld::Button> m_xBtnFileOpen; + std::unique_ptr<weld::Button> m_xBtnCancel; + std::unique_ptr<weld::Button> m_xBtnHelp; + std::unique_ptr<SvtUpButton_Impl> m_xBtnUp; + std::unique_ptr<weld::Button> m_xBtnNewFolder; + std::unique_ptr<weld::CheckButton> m_xCbPassword; + std::unique_ptr<weld::CheckButton> m_xCbGPGEncrypt; + std::unique_ptr<SvtURLBox> m_xEdCurrentPath; + std::unique_ptr<weld::CheckButton> m_xCbAutoExtension; + std::unique_ptr<weld::CheckButton> m_xCbOptions; + + std::unique_ptr<PlacesListBox> m_xPlaces; + std::unique_ptr<weld::Button> m_xBtnConnectToServer; + + SvtFileDlgMode m_eMode; + SvtFileDlgType m_eDlgType; + PickerFlags m_nStyle; + + OUString m_aStdDir; + + // delay filter when traveling the filterbox + Idle m_aFilterIdle; + + // shows OpenHdl_Imp() if the open was triggered by a double click + bool m_bDoubleClick; + + // MultiSelection? + bool m_bMultiSelection; + + // remember sizes + OUString m_aIniKey; + + explicit SvtExpFileDlg_Impl(); + ~SvtExpFileDlg_Impl(); + + void SetDenyList( const css::uno::Sequence< OUString >& rDenyList ) { m_aDenyList = rDenyList; } + const css::uno::Sequence< OUString >& GetDenyList() const { return m_aDenyList; } + void SetStandardDir( const OUString& rDir ); + const OUString& GetStandardDir() const { return m_aStdDir; } + + // access to the filter listbox only as weld::Widget* - we want to maintain the entries/userdata ourself + weld::Widget* GetFilterListControl() { return m_xLbFilter.get(); } + const weld::Widget* GetFilterListControl() const { return m_xLbFilter.get(); } + void SetFilterListSelectHdl(const Link<weld::ComboBox&, void>& rHandler) + { + m_xLbFilter->connect_changed(rHandler); + } + + // inits the listbox for the filters from the filter list (_pFilter) + void InitFilterList( ); + bool HasFilterListEntry( const OUString& rFilterName ) + { + return m_xLbFilter->find_text(rFilterName) != -1; + } + + void SelectFilterListEntry( const OUString& rFilterName ) + { + m_xLbFilter->set_active_text(rFilterName); + } + + void InsertFilterListEntry( const SvtFileDialogFilter_Impl* _pFilterDesc ); + // _pFilterDesc must already have been added to _pFilter + SvtFileDialogFilter_Impl* GetSelectedFilterEntry( OUString& rDisplayName ) const + { + rDisplayName = m_xLbFilter->get_active_text(); + return weld::fromId<SvtFileDialogFilter_Impl*>(m_xLbFilter->get_active_id()); + } + + // access to the current filter via methods only - need to care for consistency between m_pCurFilter and m_sCurrentFilterDisplayName + const SvtFileDialogFilter_Impl* GetCurFilter( ) const + { + return m_pCurFilter; + } + + const OUString& GetCurFilterDisplayName() const + { + return m_sCurrentFilterDisplayName; + } + + void SetCurFilter( SvtFileDialogFilter_Impl const * _pFilter, const OUString& rDisplayName ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/office/pickercallbacks.hxx b/fpicker/source/office/pickercallbacks.hxx new file mode 100644 index 000000000..dcc82c083 --- /dev/null +++ b/fpicker/source/office/pickercallbacks.hxx @@ -0,0 +1,51 @@ +/* -*- 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 <sal/types.h> +#include <rtl/ustring.hxx> + +namespace weld { class Widget; } + +namespace svt +{ + class IFilePickerController + { + public: + virtual weld::Widget* getControl( sal_Int16 nControlId, bool bLabelControl = false ) const = 0; + virtual void enableControl( sal_Int16 nControlId, bool bEnable ) = 0; + virtual OUString getCurFilter( ) const = 0; + + protected: + ~IFilePickerController() {} + }; + + class IFilePickerListener + { + public: + virtual void notify( sal_Int16 nEventId, sal_Int16 nControlId ) = 0; + + protected: + ~IFilePickerListener() {} + }; +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/FilterContainer.cxx b/fpicker/source/win32/FilterContainer.cxx new file mode 100644 index 000000000..67ccd6353 --- /dev/null +++ b/fpicker/source/win32/FilterContainer.cxx @@ -0,0 +1,275 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> +#include <memory> +#include <stdexcept> +#include <osl/diagnose.h> +#include "FilterContainer.hxx" + +#include <utility> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +CFilterContainer::CFilterContainer( sal_Int32 initSize ) : + m_vFilters( initSize ), + m_bIterInitialized( false ) +{ +} + +// add a name/filter pair + +bool CFilterContainer::addFilter( + const OUString& aName, const OUString& aFilter, bool bAllowDuplicates ) +{ + // check if the filter is already in the container + sal_Int32 pos = -1; + + if ( !bAllowDuplicates ) + { + pos = getFilterTagPos( aName ); + if ( pos < 0 ) // if not there, append + { + m_vFilters.push_back( std::make_pair( aName, aFilter ) ); + m_bIterInitialized = false; + } + } + else + { + m_vFilters.push_back( std::make_pair( aName, aFilter ) ); + m_bIterInitialized = false; + } + + return pos < 0; +} + +// delete a filter +// Precondition: the container is not empty +// there is a filter identified by the given name + +bool CFilterContainer::delFilter( const OUString& aName ) +{ + OSL_ASSERT( !m_vFilters.empty() ); + + sal_Int32 pos = getFilterTagPos( aName ); + if ( pos > -1 ) + { + m_vFilters.erase( m_vFilters.begin() + pos ); + m_bIterInitialized = false; + } + + return pos > -1; +} + +// return the number of filters currently in the container + +sal_Int32 CFilterContainer::numFilter( ) +{ + return m_vFilters.size( ); +} + +// clear all entries + +void CFilterContainer::empty() +{ + m_vFilters.clear( ); +} + +// get a filter by name +// Precondition: the container is not empty +// there is a filter identified by the name + +bool CFilterContainer::getFilterByName(const OUString& aName, OUString& theFilter) const +{ + OSL_PRECOND( !m_vFilters.empty() , "Empty filter container" ); + return getFilterByIndex(getFilterTagPos(aName), theFilter); +} + +bool CFilterContainer::getFilterByIndex(sal_Int32 aIndex, OUString& theFilter) const +{ + bool bRet = true; + + try + { + theFilter = m_vFilters.at(aIndex).second; + } + catch (std::out_of_range&) + { + OSL_FAIL("Filter index out of range"); + bRet = false; + } + + return bRet; +} + +bool CFilterContainer::getFilterNameByIndex(sal_Int32 aIndex, OUString& theName) const +{ + bool bRet = true; + + try + { + theName = m_vFilters.at(aIndex).first; + } + catch( std::out_of_range& ) + { + OSL_FAIL( "Filter index out of range" ); + bRet = false; + } + + return bRet; +} + +sal_Int32 CFilterContainer::getFilterPos( const OUString& aName ) const +{ + return getFilterTagPos( aName ); +} + +// returns the index of the filter identified by name + +sal_Int32 CFilterContainer::getFilterTagPos( const OUString& aName ) const +{ + if ( !m_vFilters.empty() ) + { + FILTER_VECTOR_T::const_iterator iter = std::find_if(m_vFilters.begin(), m_vFilters.end(), + [&aName](const FILTER_ENTRY_T& rFilter) { return rFilter.first.equalsIgnoreAsciiCase(aName); }); + if (iter != m_vFilters.end()) + return std::distance(m_vFilters.begin(), iter); + } + + return -1; +} + +// starts enumerating the filter in the container + +void CFilterContainer::beginEnumFilter( ) +{ + m_iter = m_vFilters.begin( ); + m_bIterInitialized = true; +} + +// returns true if another filter has been retrieved + +bool CFilterContainer::getNextFilter( FILTER_ENTRY_T& nextFilterEntry ) +{ + OSL_ASSERT( m_bIterInitialized ); + + bool bRet = ( m_iter != m_vFilters.end( ) ); + + if ( bRet ) + nextFilterEntry = *m_iter++; + else + m_bIterInitialized = false; + + return bRet; +} + +void CFilterContainer::setCurrentFilter( const OUString& aName ) +{ + m_sCurrentFilter = aName; +} + +OUString CFilterContainer::getCurrentFilter() const +{ + return m_sCurrentFilter; +} + +// calculates the length of a '\0' separated filter, that means +// length of the name + '\0' + length of the filter string + +// a trailing '\0' + +static sal_uInt32 getLengthFilter( CFilterContainer::FILTER_ENTRY_T aFilterEntry ) +{ + return ( + aFilterEntry.first.getLength( ) + 1 + + aFilterEntry.second.getLength( ) + 1 ); +} + +// calculates the length of all filters currently in the container + +static sal_uInt32 getTotalFilterLength( CFilterContainer& aFilterContainer ) +{ + CFilterContainer::FILTER_ENTRY_T nextFilter; + + aFilterContainer.beginEnumFilter( ); + + sal_uInt32 totalLength = 0; + while( aFilterContainer.getNextFilter( nextFilter ) ) + totalLength += getLengthFilter( nextFilter ); + + return ( totalLength > 0 ) ? totalLength + 1 : totalLength; +} + +static +void wcsmemcpy( sal_Unicode* pDest, const sal_Unicode* pSrc, sal_uInt32 nLength ) +{ + memcpy( pDest, pSrc, nLength * sizeof( sal_Unicode ) ); +} + +// a helper trivial helper function to create a filter buffer in the +// format the Win32 API requires, +// e.g. "Text\0*.txt\0Doc\0*.doc;*xls\0\0" + +OUString makeWinFilterBuffer( CFilterContainer& aFilterContainer ) +{ + // calculate the required buffer size + sal_uInt32 reqBuffSize = getTotalFilterLength( aFilterContainer ); + + // return if there are no filters + if ( !reqBuffSize ) + return OUString( ); + + auto pBuff = std::make_unique<sal_Unicode[]>(reqBuffSize); + + // initialize the buffer with 0 + ZeroMemory( pBuff.get(), sizeof( sal_Unicode ) * reqBuffSize ); + + OUString winFilterBuff; + CFilterContainer::FILTER_ENTRY_T nextFilter; + sal_uInt32 memPos = 0; + + aFilterContainer.beginEnumFilter( ); + + while( aFilterContainer.getNextFilter( nextFilter ) ) + { + wcsmemcpy( + pBuff.get() + memPos, + nextFilter.first.getStr( ), + nextFilter.first.getLength( ) ); + + memPos += nextFilter.first.getLength( ) + 1; + + wcsmemcpy( + pBuff.get() + memPos, + nextFilter.second.getStr( ), + nextFilter.second.getLength( ) ); + + memPos += nextFilter.second.getLength( ) + 1 ; + } + + winFilterBuff = OUString( pBuff.get(), reqBuffSize ); + + return winFilterBuff; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/FilterContainer.hxx b/fpicker/source/win32/FilterContainer.hxx new file mode 100644 index 000000000..a1e498cef --- /dev/null +++ b/fpicker/source/win32/FilterContainer.hxx @@ -0,0 +1,107 @@ +/* -*- 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 <sal/types.h> +#include <rtl/ustring.hxx> + +#include <vector> + + +// helper class, only usable by OFilterContainer + + +class CFilterContainer +{ +public: + // defines a filter entry which is made of a name and a filter value + // e.g. 'Text *.txt' + typedef std::pair< OUString, OUString > FILTER_ENTRY_T; + +public: + explicit CFilterContainer( sal_Int32 initSize = 0 ); + + // add a new filter + // returns true if the filter was successfully added + // returns false if duplicates are not allowed and + // the filter is already in the container + bool addFilter( + const OUString& aName, + const OUString& aFilter, + bool bAllowDuplicates = false ); + + // delete the specified filter returns true on + // success and false if the filter was not found + bool delFilter( const OUString& aName ); + + // the number of filter already added + sal_Int32 numFilter( ); + + // clear all entries + void empty( ); + + // retrieve a filter from the container. These methods + // return true on success and false if the specified + // filter was not found + bool getFilterByName(const OUString& aName, OUString& theFilter) const; + bool getFilterByIndex(sal_Int32 aIndex, OUString& theFilter) const; + bool getFilterNameByIndex(sal_Int32 aIndex, OUString& theName) const; + + // returns the position of the specified filter or -1 + // if the filter was not found + sal_Int32 getFilterPos( const OUString& aName ) const; + + // starts enumerating the filter in the container + void beginEnumFilter( ); + + // returns true if another filter has been retrieved + bool getNextFilter( FILTER_ENTRY_T& nextFilterEntry ); + + // cache current filter + void setCurrentFilter( const OUString& aName ); + + // returns cached current filter + OUString getCurrentFilter() const; + +protected: + typedef std::vector< FILTER_ENTRY_T > FILTER_VECTOR_T; + +private: + // prevent copy and assignment + CFilterContainer( const CFilterContainer& ); + CFilterContainer& SAL_CALL operator=( const CFilterContainer& ); + + sal_Int32 getFilterTagPos( const OUString& aName ) const; + +private: + FILTER_VECTOR_T m_vFilters; + FILTER_VECTOR_T::const_iterator m_iter; + bool m_bIterInitialized; + OUString m_sCurrentFilter; +}; + + +// a helper function to create a filter buffer in the format +// the Win32 API requires, e.g. "Text\0*.txt\0Doc\0*.doc;*xls\0\0" + + +OUString makeWinFilterBuffer( CFilterContainer& aFilterContainer ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/IVistaFilePickerInternalNotify.hxx b/fpicker/source/win32/IVistaFilePickerInternalNotify.hxx new file mode 100644 index 000000000..42f00c196 --- /dev/null +++ b/fpicker/source/win32/IVistaFilePickerInternalNotify.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 <systools/win32/uwinapi.h> + +namespace fpicker::win32::vista{ + + +// types, const etc. + + +/** todo document me + */ +class IVistaFilePickerInternalNotify +{ + public: + + virtual void onAutoExtensionChanged (bool bChecked) = 0; + + virtual bool onFileTypeChanged( UINT nTypeIndex ) = 0; + + virtual void onDirectoryChanged() = 0; + + protected: + ~IVistaFilePickerInternalNotify() {} +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePicker.cxx b/fpicker/source/win32/VistaFilePicker.cxx new file mode 100644 index 000000000..dbd503073 --- /dev/null +++ b/fpicker/source/win32/VistaFilePicker.cxx @@ -0,0 +1,541 @@ +/* -*- 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 <sal/config.h> + +#include "VistaFilePicker.hxx" + +#include "WinImplHelper.hxx" +#include "shared.hxx" + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerNotifier.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerListener.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> + +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/processfactory.hxx> +#include <osl/file.hxx> +#include <officecfg/Office/Common.hxx> + +#include <shlobj.h> + +namespace fpicker{ +namespace win32{ +namespace vista{ + +VistaFilePicker::VistaFilePicker(bool bFolderPicker) + : TVistaFilePickerBase (m_aMutex ) + , m_bInitialized (false ) + , m_bFolderPicker (bFolderPicker ) +{ +} + +VistaFilePicker::~VistaFilePicker() +{ +} + +void SAL_CALL VistaFilePicker::addFilePickerListener(const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_ADD_PICKER_LISTENER); + rRequest.setArgument(PROP_PICKER_LISTENER, xListener); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::removeFilePickerListener(const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_REMOVE_PICKER_LISTENER); + rRequest.setArgument(PROP_PICKER_LISTENER, xListener); + + m_rDialog.doRequest(rRequest); +} + +void VistaFilePicker::disposing(const css::lang::EventObject& /*aEvent*/) +{ +} + +void SAL_CALL VistaFilePicker::setMultiSelectionMode(sal_Bool bMode) +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_MULTISELECTION_MODE); + rRequest.setArgument(PROP_MULTISELECTION_MODE, bMode); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::setTitle(const OUString& sTitle) +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_TITLE); + rRequest.setArgument(PROP_TITLE, sTitle); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::appendFilter(const OUString& sTitle , + const OUString& sFilter) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_APPEND_FILTER); + rRequest.setArgument(PROP_FILTER_TITLE, sTitle ); + rRequest.setArgument(PROP_FILTER_VALUE, sFilter); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::setCurrentFilter(const OUString& sTitle) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_CURRENT_FILTER); + rRequest.setArgument(PROP_FILTER_TITLE, sTitle); + + m_rDialog.doRequest(rRequest); +} + +OUString SAL_CALL VistaFilePicker::getCurrentFilter() +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_GET_CURRENT_FILTER); + + m_rDialog.doRequest(rRequest); + + const OUString sTitle = rRequest.getArgumentOrDefault(PROP_FILTER_TITLE, OUString()); + return sTitle; +} + +void SAL_CALL VistaFilePicker::appendFilterGroup(const OUString& /*sGroupTitle*/, + const css::uno::Sequence< css::beans::StringPair >& rFilters ) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_APPEND_FILTERGROUP); + rRequest.setArgument(PROP_FILTER_GROUP, rFilters); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::setDefaultName(const OUString& sName ) +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_DEFAULT_NAME); + rRequest.setArgument(PROP_FILENAME, sName); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::setDisplayDirectory(const OUString& sDirectory) +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_DIRECTORY); + rRequest.setArgument(PROP_DIRECTORY, sDirectory); + + m_rDialog.doRequest(rRequest); +} + +OUString SAL_CALL VistaFilePicker::getDisplayDirectory() +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_GET_DIRECTORY); + m_rDialog.doRequest(rRequest); + const OUString sDirectory = rRequest.getArgumentOrDefault(PROP_DIRECTORY, OUString()); + + return sDirectory; +} + +// @deprecated can't be supported any longer ... see IDL description for further details +css::uno::Sequence< OUString > SAL_CALL VistaFilePicker::getFiles() +{ + css::uno::Sequence< OUString > lFiles = getSelectedFiles(); + // multiselection doesn't really work + // so just retrieve the first url + if (lFiles.getLength() > 1) + lFiles.realloc(1); + m_lLastFiles = lFiles; + return lFiles; +} + +css::uno::Sequence< OUString > SAL_CALL VistaFilePicker::getSelectedFiles() +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_GET_SELECTED_FILES); + + m_rDialog.doRequest(rRequest); + + const css::uno::Sequence< OUString > lFiles = rRequest.getArgumentOrDefault(PROP_SELECTED_FILES, css::uno::Sequence< OUString >()); + m_lLastFiles = lFiles; + return lFiles; +} + +void VistaFilePicker::ensureInit() +{ + if ( !m_bInitialized ) + { + if (m_bFolderPicker) + { + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_CREATE_FOLDER_PICKER); + m_rDialog.doRequest(rRequest); + m_bInitialized = true; + } + else + { + initialize( { css::uno::Any(css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE) }); + } + } +} + +::sal_Int16 SAL_CALL VistaFilePicker::execute() +{ + ensureInit(); + + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SHOW_DIALOG_MODAL); + + // show a modal window + m_rDialog.doRequest(rRequest); + + const bool bOK = rRequest.getArgumentOrDefault(PROP_DIALOG_SHOW_RESULT, false ); + m_lLastFiles = rRequest.getArgumentOrDefault(PROP_SELECTED_FILES , css::uno::Sequence< OUString >()); + + ::sal_Int16 nResult = css::ui::dialogs::ExecutableDialogResults::CANCEL; + if (bOK) + nResult = css::ui::dialogs::ExecutableDialogResults::OK; + return nResult; +} + +// XFilePicker + +void SAL_CALL VistaFilePicker::setValue( ::sal_Int16 nControlId , + ::sal_Int16 nControlAction, + const css::uno::Any& aValue ) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_CONTROL_VALUE); + rRequest.setArgument(PROP_CONTROL_ID , nControlId ); + rRequest.setArgument(PROP_CONTROL_ACTION, nControlAction); + rRequest.setArgument(PROP_CONTROL_VALUE , aValue ); + + m_rDialog.doRequest(rRequest); +} + +css::uno::Any SAL_CALL VistaFilePicker::getValue(::sal_Int16 nControlId , + ::sal_Int16 nControlAction) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_GET_CONTROL_VALUE); + rRequest.setArgument(PROP_CONTROL_ID , nControlId ); + rRequest.setArgument(PROP_CONTROL_ACTION, nControlAction); + + m_rDialog.doRequest(rRequest); + return rRequest.getValue(PROP_CONTROL_VALUE); +} + +void SAL_CALL VistaFilePicker::enableControl(::sal_Int16 nControlId, + sal_Bool bEnable ) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_ENABLE_CONTROL); + rRequest.setArgument(PROP_CONTROL_ID , nControlId); + rRequest.setArgument(PROP_CONTROL_ENABLE, bEnable ); + + m_rDialog.doRequest(rRequest); +} + +void SAL_CALL VistaFilePicker::setLabel( ::sal_Int16 nControlId, + const OUString& sLabel ) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_SET_CONTROL_LABEL); + rRequest.setArgument(PROP_CONTROL_ID , nControlId); + rRequest.setArgument(PROP_CONTROL_LABEL, sLabel ); + + m_rDialog.doRequest(rRequest); +} + +OUString SAL_CALL VistaFilePicker::getLabel(::sal_Int16 nControlId) +{ + Request rRequest; + rRequest.setRequest (VistaFilePickerImpl::E_GET_CONTROL_LABEL); + rRequest.setArgument(PROP_CONTROL_ID, nControlId); + + m_rDialog.doRequest(rRequest); + const OUString sLabel = rRequest.getArgumentOrDefault(PROP_CONTROL_LABEL, OUString()); + return sLabel; +} + +css::uno::Sequence< ::sal_Int16 > SAL_CALL VistaFilePicker::getSupportedImageFormats() +{ + return css::uno::Sequence< sal_Int16 >(); +} + +sal_Int32 SAL_CALL VistaFilePicker::getTargetColorDepth() +{ + return 0; +} + +sal_Int32 SAL_CALL VistaFilePicker::getAvailableWidth() +{ + return 0; +} + +sal_Int32 SAL_CALL VistaFilePicker::getAvailableHeight() +{ + return 0; +} + +void SAL_CALL VistaFilePicker::setImage( sal_Int16 /*nImageFormat*/, + const css::uno::Any& /*aImage */) +{ +} + +sal_Bool SAL_CALL VistaFilePicker::setShowState(sal_Bool /*bShowState*/) +{ + return false; +} + +sal_Bool SAL_CALL VistaFilePicker::getShowState() +{ + return false; +} + +void SAL_CALL VistaFilePicker::initialize(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + if (lArguments.getLength() < 1) + throw css::lang::IllegalArgumentException( + "XInitialization::initialize() called without arguments.", + static_cast< css::ui::dialogs::XFilePicker2* >( this ), + 1); + + sal_Int32 nTemplate = -1; + lArguments[0] >>= nTemplate; + + bool bFileOpenDialog = true; + ::sal_Int32 nFeatures = 0; + + switch(nTemplate) + { + case css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE : + { + bFileOpenDialog = true; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_SIMPLE : + { + bFileOpenDialog = false; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD : + { + bFileOpenDialog = false; + nFeatures |= FEATURE_AUTOEXTENSION; + nFeatures |= FEATURE_PASSWORD; + nFeatures |= FEATURE_GPGPASSWORD; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS : + { + bFileOpenDialog = false; + nFeatures |= FEATURE_AUTOEXTENSION; + nFeatures |= FEATURE_PASSWORD; + nFeatures |= FEATURE_FILTEROPTIONS; + nFeatures |= FEATURE_GPGPASSWORD; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_SELECTION : + { + bFileOpenDialog = false; + nFeatures |= FEATURE_AUTOEXTENSION; + nFeatures |= FEATURE_SELECTION; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_TEMPLATE : + { + bFileOpenDialog = false; + nFeatures |= FEATURE_AUTOEXTENSION; + nFeatures |= FEATURE_TEMPLATE; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_LINK; + nFeatures |= FEATURE_PREVIEW; + nFeatures |= FEATURE_IMAGETEMPLATE; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_LINK; + nFeatures |= FEATURE_PREVIEW; + nFeatures |= FEATURE_IMAGEANCHOR; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_PLAY : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_PLAY; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PLAY : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_LINK; + nFeatures |= FEATURE_PLAY; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_READONLY; + nFeatures |= FEATURE_VERSION; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_LINK; + nFeatures |= FEATURE_PREVIEW; + } + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION : + { + bFileOpenDialog = false; + nFeatures |= FEATURE_AUTOEXTENSION; + } + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_PREVIEW : + { + bFileOpenDialog = true; + nFeatures |= FEATURE_PREVIEW; + } + break; + } + css::uno::Reference<css::awt::XWindow> xParentWindow; + if(lArguments.getLength() > 1) + { + lArguments[1] >>= xParentWindow; + } + Request rRequest; + if (bFileOpenDialog) + { + if (!m_bFolderPicker) + rRequest.setRequest(VistaFilePickerImpl::E_CREATE_OPEN_DIALOG); + else + rRequest.setRequest(VistaFilePickerImpl::E_CREATE_FOLDER_PICKER); + } + else + rRequest.setRequest (VistaFilePickerImpl::E_CREATE_SAVE_DIALOG); + rRequest.setArgument(PROP_FEATURES, nFeatures); + rRequest.setArgument(PROP_TEMPLATE_DESCR, nTemplate); + if(xParentWindow.is()) + rRequest.setArgument(PROP_PARENT_WINDOW, xParentWindow); + m_rDialog.doRequest(rRequest); + + m_bInitialized = true; +} + +void SAL_CALL VistaFilePicker::cancel() +{ +} + +OUString SAL_CALL VistaFilePicker::getDirectory() +{ + ensureInit(); + css::uno::Sequence< OUString > aFileSeq = getSelectedFiles(); + assert(aFileSeq.getLength() <= 1); + return aFileSeq.getLength() ? aFileSeq[0] : OUString(); +} + +void SAL_CALL VistaFilePicker::setDescription( const OUString& aDescription ) +{ + setTitle(aDescription); +} + +// XServiceInfo + +OUString SAL_CALL VistaFilePicker::getImplementationName() +{ + if (m_bFolderPicker) + return "com.sun.star.ui.dialogs.Win32FolderPicker"; + else + return "com.sun.star.ui.dialogs.Win32FilePicker"; +} + +sal_Bool SAL_CALL VistaFilePicker::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL VistaFilePicker::getSupportedServiceNames() +{ + return { + "com.sun.star.ui.dialogs.FilePicker", + "com.sun.star.ui.dialogs.SystemFilePicker", + "com.sun.star.ui.dialogs.SystemFolderPicker" }; +} + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_win32_FilePicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ::fpicker::win32::vista::VistaFilePicker(false)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +fpicker_win32_FolderPicker_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ::fpicker::win32::vista::VistaFilePicker(true)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePicker.hxx b/fpicker/source/win32/VistaFilePicker.hxx new file mode 100644 index 000000000..db4235a1d --- /dev/null +++ b/fpicker/source/win32/VistaFilePicker.hxx @@ -0,0 +1,224 @@ +/* -*- 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 "requests.hxx" +#include "VistaFilePickerImpl.hxx" +#include "VistaFilePickerEventHandler.hxx" + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/ui/dialogs/XFilePreview.hpp> +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <rtl/ustring.hxx> + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +// types + + +typedef ::cppu::WeakComponentImplHelper< + css::ui::dialogs::XFilePicker3, + css::ui::dialogs::XFilePickerControlAccess, + css::ui::dialogs::XFilePreview, + css::ui::dialogs::XFolderPicker2, + css::lang::XInitialization, + css::lang::XServiceInfo > TVistaFilePickerBase; + + +/** Implements the XFilePicker & friends interface(s) + for Windows Vista and upcoming versions. + + Note: This will be a UNO wrapper for the real file picker + implementation only. The real implementation is done in class + VistaFilePickerImpl. + */ +class VistaFilePicker : public ::cppu::BaseMutex + , public TVistaFilePickerBase +{ +public: + + + // ctor/dtor + + + explicit VistaFilePicker( bool bFolderPicker ); + virtual ~VistaFilePicker() override; + + + // 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& sTitle ) 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& sName ) override; + + virtual void SAL_CALL setDisplayDirectory( const OUString& sDirectory ) override; + + virtual OUString SAL_CALL getDisplayDirectory( ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getFiles( ) override; + + // XFilePicker2 functions + virtual css::uno::Sequence< OUString > SAL_CALL getSelectedFiles( ) override; + + + // XFilterManager functions + + + virtual void SAL_CALL appendFilter( const OUString& sTitle , + const OUString& sFilter ) override; + + virtual void SAL_CALL setCurrentFilter( const OUString& sTitle ) override; + + virtual OUString SAL_CALL getCurrentFilter( ) override; + + + // XFilterGroupManager functions + + + virtual void SAL_CALL appendFilterGroup( const OUString& sGroupTitle, + const css::uno::Sequence< css::beans::StringPair >& lFilters ) 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 nControlId , + sal_Int16 nControlAction ) override; + + virtual void SAL_CALL enableControl( sal_Int16 nControlId, + sal_Bool bEnable ) override; + + virtual void SAL_CALL setLabel( sal_Int16 nControlId, + const OUString& sLabel ) override; + + virtual OUString SAL_CALL getLabel( sal_Int16 nControlId ) override; + + + // XFilePreview + + + virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats( ) override; + + virtual sal_Int32 SAL_CALL getTargetColorDepth( ) override; + + virtual sal_Int32 SAL_CALL getAvailableWidth( ) override; + + virtual sal_Int32 SAL_CALL getAvailableHeight( ) override; + + virtual void SAL_CALL setImage( sal_Int16 nImageFormat, + const css::uno::Any& aImage ) override; + + virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState ) override; + + virtual sal_Bool SAL_CALL getShowState( ) override; + + + // XInitialization + + + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& lArguments ) override; + + + // XCancellable + + + virtual void SAL_CALL cancel( ) override; + + + // XEventListener + + /// @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; + + + // XFolderPicker functions + + + virtual OUString SAL_CALL getDirectory( ) override; + + virtual void SAL_CALL setDescription( const OUString& aDescription ) override; + + + private: + + // prevent copy and assignment + VistaFilePicker( const VistaFilePicker& ); + VistaFilePicker& operator=( const VistaFilePicker& ); + + using WeakComponentImplHelperBase::disposing; + + void ensureInit(); + + private: + + css::uno::Sequence< OUString > m_lLastFiles; + + VistaFilePickerImpl m_rDialog; + + bool m_bInitialized; + const bool m_bFolderPicker; +}; + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePickerEventHandler.cxx b/fpicker/source/win32/VistaFilePickerEventHandler.cxx new file mode 100644 index 000000000..97c0c9fbd --- /dev/null +++ b/fpicker/source/win32/VistaFilePickerEventHandler.cxx @@ -0,0 +1,313 @@ +/* -*- 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 <sal/config.h> + +#include "VistaFilePickerEventHandler.hxx" + +#include "requests.hxx" + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/document/XDocumentRevisionListPersistence.hpp> +#include <com/sun/star/util/RevisionTag.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> + +#include <osl/file.hxx> + + +// namespace directives + + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +VistaFilePickerEventHandler::VistaFilePickerEventHandler(IVistaFilePickerInternalNotify* pInternalNotify) + : m_nRefCount (0 ) + , m_nListenerHandle (0 ) + , m_pDialog ( ) + , m_pInternalNotify (pInternalNotify) + , m_lListener (m_aMutex) +{ +} + + +VistaFilePickerEventHandler::~VistaFilePickerEventHandler() +{ +} + + +HRESULT STDMETHODCALLTYPE VistaFilePickerEventHandler::QueryInterface(REFIID rIID , + void** ppObject) +{ + *ppObject=nullptr; + + if ( rIID == IID_IUnknown ) + *ppObject = static_cast<IUnknown*>(static_cast<IFileDialogEvents*>(this)); + + if ( rIID == IID_IFileDialogEvents ) + *ppObject = static_cast<IFileDialogEvents*>(this); + + if ( rIID == IID_IFileDialogControlEvents ) + *ppObject = static_cast<IFileDialogControlEvents*>(this); + + if ( *ppObject != nullptr ) + { + static_cast<IUnknown*>(*ppObject)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + + +ULONG STDMETHODCALLTYPE VistaFilePickerEventHandler::AddRef() +{ + return osl_atomic_increment(&m_nRefCount); +} + + +ULONG STDMETHODCALLTYPE VistaFilePickerEventHandler::Release() +{ + ULONG nReturn = --m_nRefCount; + if ( m_nRefCount == 0 ) + delete this; + + return nReturn; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnFileOk(IFileDialog* /*pDialog*/) +{ + return E_NOTIMPL; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnFolderChanging(IFileDialog* /*pDialog*/, + IShellItem* /*pFolder*/) +{ + return E_NOTIMPL; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnFolderChange(IFileDialog* /*pDialog*/) +{ + impl_sendEvent(E_DIRECTORY_CHANGED, 0); + m_pInternalNotify->onDirectoryChanged(); + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnSelectionChange(IFileDialog* /*pDialog*/) +{ + impl_sendEvent(E_FILE_SELECTION_CHANGED, 0); + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnShareViolation(IFileDialog* /*pDialog*/ , + + IShellItem* /*pItem*/ , + + FDE_SHAREVIOLATION_RESPONSE* /*pResponse*/) +{ + impl_sendEvent(E_CONTROL_STATE_CHANGED, css::ui::dialogs::CommonFilePickerElementIds::LISTBOX_FILTER); + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnTypeChange(IFileDialog* pDialog) +{ + UINT nFileTypeIndex; + HRESULT hResult = pDialog->GetFileTypeIndex( &nFileTypeIndex ); + + if ( hResult == S_OK ) + { + if ( m_pInternalNotify->onFileTypeChanged( nFileTypeIndex )) + impl_sendEvent(E_CONTROL_STATE_CHANGED, css::ui::dialogs::CommonFilePickerElementIds::LISTBOX_FILTER); + } + + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnOverwrite(IFileDialog* /*pDialog*/ , + IShellItem* /*pItem*/ , + FDE_OVERWRITE_RESPONSE* /*pResponse*/) +{ + return E_NOTIMPL; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnItemSelected(IFileDialogCustomize* /*pCustomize*/, + + DWORD nIDCtl , + + DWORD /*nIDItem*/ ) +{ + + impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl )); + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnButtonClicked(IFileDialogCustomize* /*pCustomize*/, + DWORD nIDCtl ) +{ + + impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl)); + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnCheckButtonToggled(IFileDialogCustomize* /*pCustomize*/, + DWORD nIDCtl , + BOOL bChecked ) +{ + if (nIDCtl == css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION) + m_pInternalNotify->onAutoExtensionChanged(bChecked); + + impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl)); + + return S_OK; +} + + +STDMETHODIMP VistaFilePickerEventHandler::OnControlActivating(IFileDialogCustomize* /*pCustomize*/, + DWORD nIDCtl ) +{ + impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl)); + return S_OK; +} + + +void VistaFilePickerEventHandler::addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) +{ + m_lListener.addInterface(cppu::UnoType<css::ui::dialogs::XFilePickerListener>::get(), xListener); +} + + +void VistaFilePickerEventHandler::removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) +{ + m_lListener.removeInterface(cppu::UnoType<css::ui::dialogs::XFilePickerListener>::get(), xListener); +} + + +void VistaFilePickerEventHandler::startListening( const TFileDialog& pBroadcaster ) +{ + if (m_pDialog.is()) + return; + + m_pDialog = pBroadcaster; + m_pDialog->Advise(this, &m_nListenerHandle); +} + + +void VistaFilePickerEventHandler::stopListening() +{ + if (m_pDialog.is()) + { + m_pDialog->Unadvise(m_nListenerHandle); + m_pDialog.clear(); + } +} + +const OUStringLiteral PROP_CONTROL_ID = u"control_id"; +const OUStringLiteral PROP_PICKER_LISTENER = u"picker_listener"; + +namespace { + +void doRequest(Request& rRequest) +{ + const ::sal_Int32 nEventID = rRequest.getRequest(); + const ::sal_Int16 nControlID = rRequest.getArgumentOrDefault(PROP_CONTROL_ID, ::sal_Int16(0)); + const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest.getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >()); + + if ( ! xListener.is()) + return; + + css::ui::dialogs::FilePickerEvent aEvent; + aEvent.ElementId = nControlID; + + switch (nEventID) + { + case VistaFilePickerEventHandler::E_FILE_SELECTION_CHANGED : + xListener->fileSelectionChanged(aEvent); + break; + + case VistaFilePickerEventHandler::E_DIRECTORY_CHANGED : + xListener->directoryChanged(aEvent); + break; + + case VistaFilePickerEventHandler::E_HELP_REQUESTED : + xListener->helpRequested(aEvent); + break; + + case VistaFilePickerEventHandler::E_CONTROL_STATE_CHANGED : + xListener->controlStateChanged(aEvent); + break; + + case VistaFilePickerEventHandler::E_DIALOG_SIZE_CHANGED : + xListener->dialogSizeChanged(); + break; + + // no default here. Let compiler detect changes on enum set ! + } +} + +} + +void VistaFilePickerEventHandler::impl_sendEvent( EEventType eEventType, + ::sal_Int16 nControlID) +{ + comphelper::OInterfaceContainerHelper2* pContainer = m_lListener.getContainer( cppu::UnoType<css::ui::dialogs::XFilePickerListener>::get()); + if ( ! pContainer) + return; + + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener ( + static_cast< css::ui::dialogs::XFilePickerListener* >(pIterator.next())); + + Request rRequest; + rRequest.setRequest (eEventType); + rRequest.setArgument(PROP_PICKER_LISTENER, xListener); + if ( nControlID ) + rRequest.setArgument(PROP_CONTROL_ID, nControlID); + + doRequest(rRequest); + } + catch(const css::uno::RuntimeException&) + { + pIterator.remove(); + } + } +} + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePickerEventHandler.hxx b/fpicker/source/win32/VistaFilePickerEventHandler.hxx new file mode 100644 index 000000000..b27ef7326 --- /dev/null +++ b/fpicker/source/win32/VistaFilePickerEventHandler.hxx @@ -0,0 +1,192 @@ +/* -*- 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 <shobjidl.h> + +#include "vistatypes.h" +#include "IVistaFilePickerInternalNotify.hxx" + +#include <com/sun/star/ui/dialogs/XFilePickerListener.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <cppuhelper/basemutex.hxx> +#include <comphelper/multicontainer2.hxx> +#include <osl/interlck.h> + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +// types, const etcpp. + + +/** todo document me + */ +class VistaFilePickerEventHandler : public ::cppu::BaseMutex + , public IFileDialogEvents + , public IFileDialogControlEvents +{ + public: + + + // ctor/dtor + + + explicit VistaFilePickerEventHandler(IVistaFilePickerInternalNotify* pInternalNotify); + virtual ~VistaFilePickerEventHandler(); + + + // IUnknown + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID rIID , + void** ppObject) override; + virtual ULONG STDMETHODCALLTYPE AddRef() override; + virtual ULONG STDMETHODCALLTYPE Release() override; + + + // IFileDialogEvents + + + STDMETHODIMP OnFileOk(IFileDialog* pDialog) override; + + STDMETHODIMP OnFolderChanging(IFileDialog* pDialog, + IShellItem* pFolder) override; + + STDMETHODIMP OnFolderChange(IFileDialog* pDialog) override; + + STDMETHODIMP OnSelectionChange(IFileDialog* pDialog) override; + + STDMETHODIMP OnShareViolation(IFileDialog* pDialog , + IShellItem* pItem , + FDE_SHAREVIOLATION_RESPONSE* pResponse) override; + + STDMETHODIMP OnTypeChange(IFileDialog* pDialog) override; + + STDMETHODIMP OnOverwrite(IFileDialog* pDialog , + IShellItem* pItem , + FDE_OVERWRITE_RESPONSE* pResponse) override; + + + // IFileDialogControlEvents + + + STDMETHODIMP OnItemSelected(IFileDialogCustomize* pCustomize, + DWORD nIDCtl , + DWORD nIDItem ) override; + + STDMETHODIMP OnButtonClicked(IFileDialogCustomize* pCustomize, + DWORD nIDCtl ) override; + + STDMETHODIMP OnCheckButtonToggled(IFileDialogCustomize* pCustomize, + DWORD nIDCtl , + BOOL bChecked ) override; + + STDMETHODIMP OnControlActivating(IFileDialogCustomize* pCustomize, + DWORD nIDCtl ) override; + + + // XFilePickerNotifier + + /// @throws css::uno::RuntimeException + virtual void addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ); + + /// @throws css::uno::RuntimeException + virtual void removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ); + + + // native interface + + + /** start listening for file picker events on the given file open dialog COM object. + * + * The broadcaster will be cached internally so deregistration will be easy. + * Further all needed information is capsulated within this class (e.g. the listener handler). + * Nobody outside must know such information. + * + * Nothing will happen if an inconsistent state will be detected + * (means: double registration will be ignored). + * + * @param pBroadcaster + * reference to the dialog, where we should start listening. + */ + void startListening( const TFileDialog& pBroadcaster ); + + + /** stop listening for file picker events on the internally cached dialog COM object. + * + * The COM dialog provided on the startListening() call was cached internally. + * And now it's used to deregister this listener. Doing so the also internally cached + * listener handle is used. If listener was not already registered - nothing will happen. + */ + void stopListening(); + + public: + + enum EEventType + { + E_FILE_SELECTION_CHANGED, + E_DIRECTORY_CHANGED, + E_HELP_REQUESTED, + E_CONTROL_STATE_CHANGED, + E_DIALOG_SIZE_CHANGED + }; + + private: + + + /// @todo document me + void impl_sendEvent( EEventType eEventType, + ::sal_Int16 nControlID); + + private: + + + /// ref count for AddRef/Release() + oslInterlockedCount m_nRefCount; + + + /// unique handle for this listener provided by the broadcaster on registration time + DWORD m_nListenerHandle; + + + /// cached file dialog instance (there we listen for events) + TFileDialog m_pDialog; + + + IVistaFilePickerInternalNotify* m_pInternalNotify; + + + /** used to inform file picker listener asynchronously. + * Those listener must be called asynchronously .. because + * every request will block the caller thread. Mostly that will be + * the main thread of the office. Further the global SolarMutex will + * be locked during this time. If we call our listener back now synchronously .. + * we will block on SolarMutex.acquire() forever .-)) + */ + comphelper::OMultiTypeInterfaceContainerHelper2 m_lListener; +}; + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePickerImpl.cxx b/fpicker/source/win32/VistaFilePickerImpl.cxx new file mode 100644 index 000000000..741fdadb6 --- /dev/null +++ b/fpicker/source/win32/VistaFilePickerImpl.cxx @@ -0,0 +1,1249 @@ +/* -*- 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 <sal/config.h> + +#include <memory> + +#include "VistaFilePickerImpl.hxx" + +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ControlActions.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp> +#include <com/sun/star/lang/SystemDependent.hpp> +#include <comphelper/sequence.hxx> +#include <fpicker/strings.hrc> +#include <fpicker/fpsofficeResMgr.hxx> +#include <osl/file.hxx> +#include <rtl/process.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/string_view.hxx> +#include <vcl/svapp.hxx> +#include "WinImplHelper.hxx" + +#include <shlguid.h> +#include <shlobj.h> + +static bool is_current_process_window(HWND hwnd) +{ + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + return (pid == GetCurrentProcessId()); +} + +static HWND choose_parent_window() +{ + HWND hwnd_parent = GetForegroundWindow(); + if (!is_current_process_window(hwnd_parent)) + hwnd_parent = GetDesktopWindow(); + return hwnd_parent; +} + +namespace { + +bool createFolderItem(OUString const& url, sal::systools::COMReference<IShellItem>& folder) +{ + OUString path; + if (osl::FileBase::getSystemPathFromFileURL(url, path) + != osl::FileBase::E_None) + { + return false; + } + HRESULT res = SHCreateItemFromParsingName( + o3tl::toW(path.getStr()), nullptr, + IID_PPV_ARGS(&folder)); + return SUCCEEDED(res); +} + +} + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +// types, const etcpp. + + +const ::sal_Int16 INVALID_CONTROL_ID = -1; +const ::sal_Int16 INVALID_CONTROL_ACTION = -1; + +// Guids used for IFileDialog::SetClientGuid +const GUID CLIENTID_FILEDIALOG_SIMPLE = {0xB8628FD3, 0xA3F5, 0x4845, 0x9B, 0x62, 0xD5, 0x1E, 0xDF, 0x97, 0xC4, 0x83}; +const GUID CLIENTID_FILEDIALOG_OPTIONS = {0x93ED486F, 0x0D04, 0x4807, 0x8C, 0x44, 0xAC, 0x26, 0xCB, 0x6C, 0x5D, 0x36}; +const GUID CLIENTID_FILESAVE_PASSWORD = {0xC12D4F4C, 0x4D41, 0x4D4F, 0x97, 0xEF, 0x87, 0xF9, 0x8D, 0xB6, 0x1E, 0xA6}; +const GUID CLIENTID_FILESAVE_SELECTION = {0x5B2482B3, 0x0358, 0x4E09, 0xAA, 0x64, 0x2B, 0x76, 0xB2, 0xA0, 0xDD, 0xFE}; +const GUID CLIENTID_FILESAVE_TEMPLATE = {0x9996D877, 0x20D5, 0x424B, 0x9C, 0x2E, 0xD3, 0xB6, 0x31, 0xEC, 0xF7, 0xCE}; +const GUID CLIENTID_FILEOPEN_LINK_TEMPLATE = {0x32237796, 0x1509, 0x49D1, 0xBB, 0x7E, 0x63, 0xAD, 0x36, 0xAE, 0x86, 0x8C}; +const GUID CLIENTID_FILEOPEN_LINK_ANCHOR = {0xBE3188CB, 0x399A, 0x45AE, 0x8F, 0x78, 0x75, 0x17, 0xAF, 0x26, 0x81, 0xEA}; +const GUID CLIENTID_FILEOPEN_PLAY = {0x32CFB147, 0xF5AE, 0x4F90, 0xA1, 0xF1, 0x81, 0x20, 0x72, 0xBB, 0x2F, 0xC5}; +const GUID CLIENTID_FILEOPEN_LINK = {0x39AC4BAE, 0x7D2D, 0x46BC, 0xBE, 0x2E, 0xF8, 0x8C, 0xB5, 0x65, 0x5E, 0x6A}; + + +class TDialogImplBase +{ +public: + TDialogImplBase(IFileDialog* iDialog) + : m_iDialog(iDialog) + { + } + + virtual ~TDialogImplBase() = default; + + TFileDialog getComPtr() { return m_iDialog; } + virtual sal::systools::COMReference<IShellItemArray> getResult(bool bInExecute) + { + sal::systools::COMReference<IShellItem> iItem; + if (m_iDialog.is()) + { + if (bInExecute) + m_iDialog->GetCurrentSelection(&iItem); + else + m_iDialog->GetResult(&iItem); + } + void* iItems = nullptr; + if (iItem.is()) + SHCreateShellItemArrayFromShellItem(iItem.get(), IID_IShellItemArray, &iItems); + return static_cast<IShellItemArray*>(iItems); + } + +private: + TFileDialog m_iDialog; +}; + +namespace { + +template <class ComPtrDialog, REFCLSID CLSID> class TDialogImpl : public TDialogImplBase +{ +public: + TDialogImpl() + : TDialogImplBase(ComPtrDialog(CLSID).get()) + { + } +}; + +class TOpenDialogImpl : public TDialogImpl<TFileOpenDialog, CLSID_FileOpenDialog> +{ +public: + sal::systools::COMReference<IShellItemArray> getResult(bool bInExecute) override + { + sal::systools::COMReference<IShellItemArray> iItems; + TFileOpenDialog iDialog(getComPtr(), sal::systools::COM_QUERY_THROW); + bool bGetResult = false; + if (!iDialog.is()) + bGetResult = true; + else if (FAILED(bInExecute ? iDialog->GetSelectedItems(&iItems) : iDialog->GetResults(&iItems))) + bGetResult = true; + + if (bGetResult) + iItems = TDialogImplBase::getResult(bInExecute); + + return iItems; + } +}; + +} + +using TSaveDialogImpl = TDialogImpl<TFileSaveDialog, CLSID_FileSaveDialog>; +using TFolderPickerDialogImpl = TDialogImpl<TFileOpenDialog, CLSID_FileOpenDialog>; + + +static OUString lcl_getURLFromShellItem (IShellItem* pItem) +{ + LPWSTR pStr = nullptr; + OUString sURL; + HRESULT hr; + + hr = pItem->GetDisplayName ( SIGDN_FILESYSPATH, &pStr ); + if (SUCCEEDED(hr)) + { + ::osl::FileBase::getFileURLFromSystemPath( OUString(o3tl::toU(pStr)), sURL ); + goto cleanup; + } + + hr = pItem->GetDisplayName ( SIGDN_URL, &pStr ); + if (SUCCEEDED(hr)) + { + sURL = o3tl::toU(pStr); + goto cleanup; + } + + hr = pItem->GetDisplayName ( SIGDN_PARENTRELATIVEPARSING, &pStr ); + if (SUCCEEDED(hr)) + { + GUID known_folder_id; + std::wstring aStr = pStr; + CoTaskMemFree (pStr); + + if (0 == aStr.compare(0, 3, L"::{")) + aStr = aStr.substr(2); + hr = IIDFromString(aStr.c_str(), &known_folder_id); + if (SUCCEEDED(hr)) + { + hr = SHGetKnownFolderPath(known_folder_id, 0, nullptr, &pStr); + if (SUCCEEDED(hr)) + { + ::osl::FileBase::getFileURLFromSystemPath(OUString(o3tl::toU(pStr)), sURL); + goto cleanup; + } + } + } + + // Default fallback + hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &pStr); + if (SUCCEEDED(hr)) + ::osl::FileBase::getFileURLFromSystemPath(OUString(o3tl::toU(pStr)), sURL); + else // shouldn't happen... + goto bailout; + +cleanup: + CoTaskMemFree (pStr); +bailout: + return sURL; +} + +// Vista file picker shows the filter mask next to filter name in the list; so we need to remove the +// mask from the filter name to avoid duplicating masks +static OUString lcl_AdjustFilterName(const OUString& sName) +{ + const sal_Int32 idx = sName.indexOf("(."); + return (idx > 0) ? OUString(o3tl::trim(sName.subView(0, idx))) : sName; +} + +// rvStrings holds the OUStrings, pointers to which data are stored in returned COMDLG_FILTERSPEC +static ::std::vector<COMDLG_FILTERSPEC> lcl_buildFilterList(CFilterContainer& rContainer, + std::vector<OUString>& rvStrings) +{ + ::std::vector< COMDLG_FILTERSPEC > lList ; + CFilterContainer::FILTER_ENTRY_T aFilter; + + rContainer.beginEnumFilter( ); + while( rContainer.getNextFilter(aFilter) ) + { + COMDLG_FILTERSPEC aSpec; + + rvStrings.push_back(lcl_AdjustFilterName(aFilter.first)); // to avoid dangling pointer + aSpec.pszName = o3tl::toW(rvStrings.back().getStr()); + aSpec.pszSpec = o3tl::toW(aFilter.second.getStr()); + + lList.push_back(aSpec); + } + + return lList; +} + + +VistaFilePickerImpl::VistaFilePickerImpl() + : m_lFilters () + , m_iEventHandler(new VistaFilePickerEventHandler(this)) + , m_bInExecute (false) + , m_bWasExecuted (false) + , m_hParentWindow(nullptr) + , m_sDirectory () + , m_sFilename () +{ +} + + +VistaFilePickerImpl::~VistaFilePickerImpl() +{ +} + + +void VistaFilePickerImpl::doRequest(Request& rRequest) +{ + try + { + switch(rRequest.getRequest()) + { + case E_ADD_PICKER_LISTENER : + impl_sta_addFilePickerListener(rRequest); + break; + + case E_REMOVE_PICKER_LISTENER : + impl_sta_removeFilePickerListener(rRequest); + break; + + case E_APPEND_FILTER : + impl_sta_appendFilter(rRequest); + break; + + case E_APPEND_FILTERGROUP : + impl_sta_appendFilterGroup(rRequest); + break; + + case E_SET_CURRENT_FILTER : + impl_sta_setCurrentFilter(rRequest); + break; + + case E_GET_CURRENT_FILTER : + impl_sta_getCurrentFilter(rRequest); + break; + + case E_CREATE_OPEN_DIALOG : + impl_sta_CreateOpenDialog(rRequest); + break; + + case E_CREATE_SAVE_DIALOG : + impl_sta_CreateSaveDialog(rRequest); + break; + + case E_CREATE_FOLDER_PICKER: + impl_sta_CreateFolderPicker(rRequest); + break; + + case E_SET_MULTISELECTION_MODE : + impl_sta_SetMultiSelectionMode(rRequest); + break; + + case E_SET_TITLE : + impl_sta_SetTitle(rRequest); + break; + + case E_SET_FILENAME: + impl_sta_SetFileName(rRequest); + break; + + case E_SET_DIRECTORY : + impl_sta_SetDirectory(rRequest); + break; + + case E_GET_DIRECTORY : + impl_sta_GetDirectory(rRequest); + break; + + case E_SET_DEFAULT_NAME : + impl_sta_SetDefaultName(rRequest); + break; + + case E_GET_SELECTED_FILES : + impl_sta_getSelectedFiles(rRequest); + break; + + case E_SHOW_DIALOG_MODAL : + impl_sta_ShowDialogModal(rRequest); + break; + + case E_SET_CONTROL_VALUE : + impl_sta_SetControlValue(rRequest); + break; + + case E_GET_CONTROL_VALUE : + impl_sta_GetControlValue(rRequest); + break; + + case E_SET_CONTROL_LABEL : + impl_sta_SetControlLabel(rRequest); + break; + + case E_GET_CONTROL_LABEL : + impl_sta_GetControlLabel(rRequest); + break; + + case E_ENABLE_CONTROL : + impl_sta_EnableControl(rRequest); + break; + + // no default: let the compiler detect changes on enum ERequest ! + } + } + catch(...) + {} +} + + +void VistaFilePickerImpl::impl_sta_addFilePickerListener(Request& rRequest) +{ + const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest.getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >()); + if ( ! xListener.is()) + return; + + if (m_iEventHandler.is()) + { + auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get()); + pHandlerImpl->addFilePickerListener(xListener); + } +} + + +void VistaFilePickerImpl::impl_sta_removeFilePickerListener(Request& rRequest) +{ + const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest.getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >()); + if ( ! xListener.is()) + return; + + if (m_iEventHandler.is()) + { + auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get()); + pHandlerImpl->removeFilePickerListener(xListener); + } +} + + +void VistaFilePickerImpl::impl_sta_appendFilter(Request& rRequest) +{ + const OUString sTitle = rRequest.getArgumentOrDefault(PROP_FILTER_TITLE, OUString()); + const OUString sFilter = rRequest.getArgumentOrDefault(PROP_FILTER_VALUE, OUString()); + + m_lFilters.addFilter(sTitle, sFilter); +} + + +void VistaFilePickerImpl::impl_sta_appendFilterGroup(Request& rRequest) +{ + const css::uno::Sequence< css::beans::StringPair > aFilterGroup = + rRequest.getArgumentOrDefault(PROP_FILTER_GROUP, css::uno::Sequence< css::beans::StringPair >()); + + if ( m_lFilters.numFilter() > 0 && aFilterGroup.getLength() > 0 ) + m_lFilters.addFilter( STRING_SEPARATOR, "", true ); + + ::sal_Int32 c = aFilterGroup.getLength(); + ::sal_Int32 i = 0; + for (i=0; i<c; ++i) + { + const css::beans::StringPair& rFilter = aFilterGroup[i]; + m_lFilters.addFilter(rFilter.First, rFilter.Second); + } +} + + +void VistaFilePickerImpl::impl_sta_setCurrentFilter(Request& rRequest) +{ + const OUString sTitle = rRequest.getArgumentOrDefault(PROP_FILTER_TITLE, OUString()); + + m_lFilters.setCurrentFilter(sTitle); +} + + +void VistaFilePickerImpl::impl_sta_getCurrentFilter(Request& rRequest) +{ + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + UINT nIndex = UINT_MAX; + HRESULT hResult = iDialog->GetFileTypeIndex(&nIndex); + if ( + ( FAILED(hResult) ) || + ( nIndex == UINT_MAX ) // COM dialog sometimes return S_OK for empty filter lists .-( + ) + return; + + OUString sTitle; + ::sal_Int32 nRealIndex = nIndex-1; // COM dialog base on 1 ... filter container on 0 .-) + if ( + (nRealIndex >= 0 ) && + (m_lFilters.getFilterNameByIndex(nRealIndex, sTitle)) + ) + rRequest.setArgument(PROP_FILTER_TITLE, sTitle); + else if ( nRealIndex == -1 ) // Dialog not visible yet + { + sTitle = m_lFilters.getCurrentFilter(); + rRequest.setArgument(PROP_FILTER_TITLE, sTitle); + } +} + + +template <class TDialogImplClass> void VistaFilePickerImpl::impl_sta_CreateDialog() +{ + m_pDialog = std::make_shared<TDialogImplClass>(); +} + + +void VistaFilePickerImpl::impl_sta_InitDialog(Request& rRequest, DWORD nOrFlags) +{ + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + DWORD nFlags = 0; + iDialog->GetOptions ( &nFlags ); + + nFlags &= ~FOS_FORCESHOWHIDDEN; + nFlags |= FOS_PATHMUSTEXIST; + nFlags |= FOS_DONTADDTORECENT; + nFlags |= nOrFlags; + + iDialog->SetOptions ( nFlags ); + + css::uno::Reference<css::awt::XWindow> xWindow = rRequest.getArgumentOrDefault(PROP_PARENT_WINDOW, css::uno::Reference<css::awt::XWindow>()); + if(xWindow.is()) + { + css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysDepWin(xWindow,css::uno::UNO_QUERY); + if(xSysDepWin.is()) { + css::uno::Sequence<sal_Int8> aProcessIdent(16); + rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray())); + css::uno::Any aAny = xSysDepWin->getWindowHandle(aProcessIdent,css::lang::SystemDependent::SYSTEM_WIN32); + sal_Int64 tmp = 0; + aAny >>= tmp; + if(tmp != 0) + { + m_hParentWindow = reinterpret_cast<HWND>(tmp); + } + } + } + + ::sal_Int32 nFeatures = rRequest.getArgumentOrDefault(PROP_FEATURES, ::sal_Int32(0)); + ::sal_Int32 nTemplate = rRequest.getArgumentOrDefault(PROP_TEMPLATE_DESCR, ::sal_Int32(0)); + impl_sta_enableFeatures(nFeatures, nTemplate); + + if (m_iEventHandler.is()) + { + auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get()); + pHandlerImpl->startListening(iDialog); + } +} + + +void VistaFilePickerImpl::impl_sta_CreateOpenDialog(Request& rRequest) +{ + impl_sta_CreateDialog<TOpenDialogImpl>(); + impl_sta_InitDialog(rRequest, FOS_FILEMUSTEXIST | FOS_OVERWRITEPROMPT); +} + + +void VistaFilePickerImpl::impl_sta_CreateSaveDialog(Request& rRequest) +{ + impl_sta_CreateDialog<TSaveDialogImpl>(); + impl_sta_InitDialog(rRequest, FOS_FILEMUSTEXIST | FOS_OVERWRITEPROMPT); +} + + +void VistaFilePickerImpl::impl_sta_CreateFolderPicker(Request& rRequest) +{ + impl_sta_CreateDialog<TFolderPickerDialogImpl>(); + impl_sta_InitDialog(rRequest, FOS_PICKFOLDERS); +} + + +const ::sal_Int32 GROUP_VERSION = 1; +const ::sal_Int32 GROUP_TEMPLATE = 2; +const ::sal_Int32 GROUP_IMAGETEMPLATE = 3; +const ::sal_Int32 GROUP_CHECKBOXES = 4; +const ::sal_Int32 GROUP_IMAGEANCHOR = 5; + + +static void setLabelToControl(TFileDialogCustomize iCustom, sal_uInt16 nControlId) +{ + OUString aLabel = CResourceProvider::getResString(nControlId); + aLabel = SOfficeToWindowsLabel(aLabel); + iCustom->SetControlLabel(nControlId, o3tl::toW(aLabel.getStr()) ); +} + + +void VistaFilePickerImpl::impl_sta_enableFeatures(::sal_Int32 nFeatures, ::sal_Int32 nTemplate) +{ + GUID aGUID = {}; + switch (nTemplate) + { + case css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE : + case css::ui::dialogs::TemplateDescription::FILEOPEN_PREVIEW : + case css::ui::dialogs::TemplateDescription::FILESAVE_SIMPLE : + aGUID = CLIENTID_FILEDIALOG_SIMPLE; + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION : + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS : + aGUID = CLIENTID_FILEDIALOG_OPTIONS; + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD : + aGUID = CLIENTID_FILESAVE_PASSWORD; + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION : + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_SELECTION : + aGUID = CLIENTID_FILESAVE_SELECTION; + break; + + case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_TEMPLATE : + aGUID = CLIENTID_FILESAVE_TEMPLATE; + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE : + aGUID = CLIENTID_FILEOPEN_LINK_TEMPLATE; + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR : + aGUID = CLIENTID_FILEOPEN_LINK_ANCHOR; + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_PLAY : + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PLAY : + aGUID = CLIENTID_FILEOPEN_PLAY; + break; + + case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW : + aGUID = CLIENTID_FILEOPEN_LINK; + break; + } + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (iDialog.is()) + iDialog->SetClientGuid ( aGUID ); + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if (!iCustom.is()) + return; + + if ((nFeatures & FEATURE_VERSION) == FEATURE_VERSION) + { + iCustom->StartVisualGroup (GROUP_VERSION, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_VERSION).replaceFirst("~","").getStr())); + iCustom->AddComboBox (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION); + iCustom->EndVisualGroup (); + iCustom->MakeProminent (GROUP_VERSION); + } + + if ((nFeatures & FEATURE_TEMPLATE) == FEATURE_TEMPLATE) + { + iCustom->StartVisualGroup (GROUP_TEMPLATE, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_TEMPLATES).replaceFirst("~","").getStr())); + iCustom->AddComboBox (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE); + iCustom->EndVisualGroup (); + iCustom->MakeProminent (GROUP_TEMPLATE); + } + + if ((nFeatures & FEATURE_IMAGETEMPLATE) == FEATURE_IMAGETEMPLATE) + { + iCustom->StartVisualGroup (GROUP_IMAGETEMPLATE, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_IMAGE_TEMPLATE).replaceFirst("~","").getStr())); + iCustom->AddComboBox (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE); + iCustom->EndVisualGroup (); + iCustom->MakeProminent (GROUP_IMAGETEMPLATE); + } + + if ((nFeatures & FEATURE_IMAGEANCHOR) == FEATURE_IMAGEANCHOR) + { + iCustom->StartVisualGroup (GROUP_IMAGEANCHOR, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_IMAGE_ANCHOR).replaceFirst("~","").getStr())); + iCustom->AddComboBox (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR); + iCustom->EndVisualGroup (); + iCustom->MakeProminent (GROUP_IMAGEANCHOR); + } + + iCustom->StartVisualGroup (GROUP_CHECKBOXES, L""); + + sal_uInt16 nControlId(0); + if ((nFeatures & FEATURE_AUTOEXTENSION) == FEATURE_AUTOEXTENSION) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_AUTO_EXTENSION).replaceFirst("~","").getStr()), true); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_PASSWORD) == FEATURE_PASSWORD) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_PASSWORD).replaceFirst("~","").getStr()), false); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_GPGPASSWORD) == FEATURE_GPGPASSWORD) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGENCRYPTION; + iCustom->AddCheckButton (nControlId, L"GpgPassword", false); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_READONLY) == FEATURE_READONLY) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_READONLY).replaceFirst("~","").getStr()), false); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_FILTEROPTIONS) == FEATURE_FILTEROPTIONS) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_FILTER_OPTIONS).replaceFirst("~","").getStr()), false); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_LINK) == FEATURE_LINK) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_INSERT_AS_LINK).replaceFirst("~","").getStr()), false); + setLabelToControl(iCustom, nControlId); + } + + if ((nFeatures & FEATURE_SELECTION) == FEATURE_SELECTION) + { + nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION; + iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_SELECTION).replaceFirst("~","").getStr()), false); + setLabelToControl(iCustom, nControlId); + } + + /* can be ignored ... new COM dialog supports preview native now ! + if ((nFeatures & FEATURE_PREVIEW) == FEATURE_PREVIEW) + iCustom->AddCheckButton (css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, L"Preview", false); + */ + + iCustom->EndVisualGroup(); + + if ((nFeatures & FEATURE_PLAY) == FEATURE_PLAY) + iCustom->AddPushButton (css::ui::dialogs::ExtendedFilePickerElementIds::PUSHBUTTON_PLAY, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_PLAY).replaceFirst("~","").getStr())); + +} + + +void VistaFilePickerImpl::impl_sta_SetMultiSelectionMode(Request& rRequest) +{ + const bool bMultiSelection = rRequest.getArgumentOrDefault(PROP_MULTISELECTION_MODE, true); + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + DWORD nFlags = 0; + iDialog->GetOptions(&nFlags); + + if (bMultiSelection) + nFlags |= FOS_ALLOWMULTISELECT; + else + nFlags &= ~FOS_ALLOWMULTISELECT; + + iDialog->SetOptions ( nFlags ); +} + + +void VistaFilePickerImpl::impl_sta_SetTitle(Request& rRequest) +{ + OUString sTitle = rRequest.getArgumentOrDefault(PROP_TITLE, OUString()); + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + iDialog->SetTitle(o3tl::toW(sTitle.getStr())); +} + + +void VistaFilePickerImpl::impl_sta_SetFileName(Request& rRequest) +{ + OUString sFileName = rRequest.getArgumentOrDefault(PROP_FILENAME, OUString()); + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + iDialog->SetFileName(o3tl::toW(sFileName.getStr())); +} + + +void VistaFilePickerImpl::impl_sta_SetDirectory(Request& rRequest) +{ + OUString sDirectory = rRequest.getArgumentOrDefault(PROP_DIRECTORY, OUString()); + + if( !m_bInExecute) + { + // Vista stores last used folders for file dialogs + // so we don't want the application to change the folder + // in most cases. + // Store the requested folder in the meantime and decide later + // what to do + m_sDirectory = sDirectory; + } + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + sal::systools::COMReference<IShellItem> pFolder; + if ( !createFolderItem(sDirectory, pFolder) ) + return; + + iDialog->SetFolder(pFolder.get()); +} + +OUString VistaFilePickerImpl::GetDirectory() +{ + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return OUString(); + sal::systools::COMReference<IShellItem> pFolder; + HRESULT hResult = iDialog->GetFolder( &pFolder ); + if ( FAILED(hResult) ) + return OUString(); + return lcl_getURLFromShellItem(pFolder.get()); +} + +void VistaFilePickerImpl::impl_sta_GetDirectory(Request& rRequest) +{ + const OUString sFolder = m_sDirectory.isEmpty() ? GetDirectory() : m_sDirectory; + if (!sFolder.isEmpty()) + rRequest.setArgument(PROP_DIRECTORY, sFolder); +} + +void VistaFilePickerImpl::impl_sta_SetDefaultName(Request& rRequest) +{ + OUString sFilename = rRequest.getArgumentOrDefault(PROP_FILENAME, OUString()); + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if ( ! iCustom.is()) + return; + + // if we have the autoextension check box set, remove (or change ???) the extension of the filename + // so that the autoextension mechanism can do its job + BOOL bValue = FALSE; + HRESULT hResult = iCustom->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue); + if ( FAILED(hResult) ) + return; + if ( bValue ) + { + sal_Int32 nSepPos = sFilename.lastIndexOf( '.' ); + if ( -1 != nSepPos ) + sFilename = sFilename.copy(0, nSepPos); + } + + iDialog->SetFileName (o3tl::toW(sFilename.getStr())); + m_sFilename = sFilename; +} + + +void VistaFilePickerImpl::impl_sta_setFiltersOnDialog() +{ + std::vector<OUString> vStrings; // to hold the adjusted filter names, pointers to which will be + // stored in lFilters + ::std::vector< COMDLG_FILTERSPEC > lFilters = lcl_buildFilterList(m_lFilters, vStrings); + OUString sCurrentFilter = m_lFilters.getCurrentFilter(); + sal_Int32 nCurrentFilter = m_lFilters.getFilterPos(sCurrentFilter); + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + TFileDialogCustomize iCustomize = impl_getCustomizeInterface(); + if (!iCustomize.is()) + return; + + if (lFilters.empty()) + return; + + COMDLG_FILTERSPEC *pFilt = lFilters.data(); + iDialog->SetFileTypes(lFilters.size(), pFilt/*&lFilters[0]*/); + iDialog->SetFileTypeIndex(nCurrentFilter + 1); + + BOOL bValue = FALSE; + HRESULT hResult = iCustomize->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue); + if ( FAILED(hResult) ) + return; + + if ( bValue ) + { + PCWSTR lpFilterExt = lFilters[0].pszSpec; + + lpFilterExt = wcsrchr( lpFilterExt, '.' ); + if ( lpFilterExt ) + lpFilterExt++; + iDialog->SetDefaultExtension( lpFilterExt ); + } + +} + + +void VistaFilePickerImpl::impl_sta_getSelectedFiles(Request& rRequest) +{ + if (m_pDialog == nullptr) + return; + + // ask dialog for results + // we must react different if dialog is in execute or not .-( + sal::systools::COMReference<IShellItemArray> iItems = m_pDialog->getResult(m_bInExecute); + if (!iItems.is()) + return; + + // convert and pack results + std::vector< OUString > lFiles; + if (DWORD nCount; SUCCEEDED(iItems->GetCount(&nCount))) + { + for (DWORD i = 0; i < nCount; ++i) + { + if (sal::systools::COMReference<IShellItem> iItem; + SUCCEEDED(iItems->GetItemAt(i, &iItem))) + { + if (const OUString sURL = lcl_getURLFromShellItem(iItem.get()); !sURL.isEmpty()) + lFiles.push_back(sURL); + } + } + } + + rRequest.setArgument(PROP_SELECTED_FILES, comphelper::containerToSequence(lFiles)); +} + + +void VistaFilePickerImpl::impl_sta_ShowDialogModal(Request& rRequest) +{ + impl_sta_setFiltersOnDialog(); + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + // it's important to know if we are showing the dialog. + // Some dialog interface methods can't be called then or some + // tasks must be done differently .-) (e.g. see impl_sta_getSelectedFiles()) + m_bInExecute = true; + + m_bWasExecuted = true; + + // we set the directory only if we have a save dialog and a filename + // for the other cases, the file dialog remembers its last location + // according to its client guid. + if( m_sDirectory.getLength()) + { + sal::systools::COMReference<IShellItem> pFolder; + if ( createFolderItem(m_sDirectory, pFolder) ) + { + if (m_sFilename.getLength()) + { + OUString aFileURL(m_sDirectory); + sal_Int32 nIndex = aFileURL.lastIndexOf('/'); + if (nIndex != aFileURL.getLength()-1) + aFileURL += "/"; + aFileURL += m_sFilename; + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if (!iCustom.is()) + return; + + BOOL bValue = FALSE; + HRESULT hResult = iCustom->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue); + if ( bValue ) + { + UINT nFileType; + hResult = iDialog->GetFileTypeIndex(&nFileType); + if ( SUCCEEDED(hResult) && nFileType > 0 ) + { + // COM dialog base on 1 ... filter container on 0 .-) + ::size_t nRealIndex = nFileType-1; + OUString sFilter; + if (m_lFilters.getFilterByIndex(nRealIndex, sFilter)) + { + const sal_Int32 idx = sFilter.indexOf('.'); + if (idx >= 0) + aFileURL += sFilter.subView(idx); + } + } + } + + // Check existence of file. Set folder only for this special case + OUString aSystemPath; + osl_getSystemPathFromFileURL( aFileURL.pData, &aSystemPath.pData ); + + WIN32_FIND_DATAW aFindFileData; + HANDLE hFind = FindFirstFileW( o3tl::toW(aSystemPath.getStr()), &aFindFileData ); + if (hFind != INVALID_HANDLE_VALUE) + iDialog->SetFolder(pFolder.get()); + else + hResult = iDialog->AddPlace(pFolder.get(), FDAP_TOP); + + FindClose( hFind ); + } + else + iDialog->AddPlace(pFolder.get(), FDAP_TOP); + } + } + + HRESULT hResult = E_FAIL; + try + { + // tdf#146007: Make sure we don't hold solar mutex: COM may need to forward + // the execution to the main thread, and holding solar mutex could deadlock + SolarMutexGuard g; // First acquire, to avoid releaser failure + SolarMutexReleaser r; + // show dialog and wait for user decision + hResult = iDialog->Show(m_hParentWindow ? m_hParentWindow + : choose_parent_window()); // parent window needed + } + catch(...) + {} + + m_bInExecute = false; + + if (m_iEventHandler.is()) + { + auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get()); + pHandlerImpl->stopListening(); + } + + if ( FAILED(hResult) ) + return; + + impl_sta_getSelectedFiles(rRequest); + rRequest.setArgument(PROP_DIALOG_SHOW_RESULT, true); +} + + +TFileDialog VistaFilePickerImpl::impl_getBaseDialogInterface() +{ + TFileDialog iDialog; + + if (m_pDialog != nullptr) + iDialog = m_pDialog->getComPtr(); + + return iDialog; +} + + +TFileDialogCustomize VistaFilePickerImpl::impl_getCustomizeInterface() +{ + if (m_pDialog != nullptr) + return { m_pDialog->getComPtr(), sal::systools::COM_QUERY_THROW }; + + return {}; +} + + +static void lcl_removeControlItemsWorkaround(const TFileDialogCustomize& iCustom , + ::sal_Int16 nControlId) +{ + (void)iCustom->SetSelectedControlItem(nControlId, 1000); // Don't care if this fails (useless?) + DWORD i = 0; + HRESULT hResult = S_OK; + while ( SUCCEEDED(hResult) ) + hResult = iCustom->RemoveControlItem(nControlId, i++); +} + + +void VistaFilePickerImpl::impl_sta_SetControlValue(Request& rRequest) +{ + ::sal_Int16 nId = rRequest.getArgumentOrDefault(PROP_CONTROL_ID , INVALID_CONTROL_ID ); + ::sal_Int16 nAction = rRequest.getArgumentOrDefault(PROP_CONTROL_ACTION, INVALID_CONTROL_ACTION); + css::uno::Any aValue = rRequest.getValue(PROP_CONTROL_VALUE); + + // don't check for right values here ... + // most parameters are optional ! + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if ( ! iCustom.is()) + return; + + switch (nId) + { + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK : + //case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW : // can be ignored ... preview is supported native now ! + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION : + { + bool bValue = false; + aValue >>= bValue; + iCustom->SetCheckButtonState(nId, bValue); + } + break; + + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION : + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE : + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE : + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR : + { + HRESULT hResult; + switch (nAction) + { + case css::ui::dialogs::ControlActions::DELETE_ITEMS : + { + hResult = iCustom->RemoveAllControlItems(nId); + if ( FAILED(hResult) ) + lcl_removeControlItemsWorkaround(iCustom, nId); + } + break; + + case css::ui::dialogs::ControlActions::ADD_ITEMS : + { + aValue >>= m_lItems; + for (::sal_Int32 i=0; i<m_lItems.getLength(); ++i) + { + const OUString& sItem = m_lItems[i]; + hResult = iCustom->AddControlItem(nId, i, o3tl::toW(sItem.getStr())); + } + } + break; + + case css::ui::dialogs::ControlActions::SET_SELECT_ITEM : + { + ::sal_Int32 nItem = 0; + aValue >>= nItem; + hResult = iCustom->SetSelectedControlItem(nId, nItem); + } + break; + } + } + break; + + case css::ui::dialogs::ExtendedFilePickerElementIds::PUSHBUTTON_PLAY : + { + } + break; + } +} + + +void VistaFilePickerImpl::impl_sta_GetControlValue(Request& rRequest) +{ + ::sal_Int16 nId = rRequest.getArgumentOrDefault(PROP_CONTROL_ID , INVALID_CONTROL_ID ); + + // don't check for right values here ... + // most parameters are optional ! + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if ( ! iCustom.is()) + return; + + css::uno::Any aValue; + if( m_bWasExecuted ) + switch (nId) + { + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGENCRYPTION : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS : + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK : + //case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW : // can be ignored ... preview is supported native now ! + case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION : + { + BOOL bValue = FALSE; + HRESULT hResult = iCustom->GetCheckButtonState(nId, &bValue); + if ( SUCCEEDED(hResult) ) + aValue <<= bool(bValue); + } + break; + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION: + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE: + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE: + case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR: + { + DWORD bValue = 0; + HRESULT hResult = iCustom->GetSelectedControlItem(nId, &bValue); + if ( SUCCEEDED(hResult) ) + { + const OUString& sItem = m_lItems[bValue]; + aValue <<= OUString(sItem.getStr()); + } + } + break; + } + + if (aValue.hasValue()) + rRequest.setArgument(PROP_CONTROL_VALUE, aValue); +} + + +void VistaFilePickerImpl::impl_sta_SetControlLabel(Request& rRequest) +{ + ::sal_Int16 nId = rRequest.getArgumentOrDefault(PROP_CONTROL_ID , INVALID_CONTROL_ID ); + OUString sLabel = rRequest.getArgumentOrDefault(PROP_CONTROL_LABEL, OUString() ); + + // don't check for right values here ... + // most parameters are optional ! + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if ( ! iCustom.is()) + return; + iCustom->SetControlLabel (nId, o3tl::toW(sLabel.getStr())); +} + + +void VistaFilePickerImpl::impl_sta_GetControlLabel(Request& /*rRequest*/) +{ +} + + +void VistaFilePickerImpl::impl_sta_EnableControl(Request& rRequest) +{ + ::sal_Int16 nId = rRequest.getArgumentOrDefault(PROP_CONTROL_ID , INVALID_CONTROL_ID ); + bool bEnabled = rRequest.getArgumentOrDefault(PROP_CONTROL_ENABLE, true); + + // don't check for right values here ... + // most parameters are optional ! + + TFileDialogCustomize iCustom = impl_getCustomizeInterface(); + if ( ! iCustom.is()) + return; + + CDCONTROLSTATEF eState = CDCS_VISIBLE; + if (bEnabled) + eState |= CDCS_ENABLED; + else + eState |= CDCS_INACTIVE; + + iCustom->SetControlState(nId, eState); +} + +void VistaFilePickerImpl::impl_SetDefaultExtension( const OUString& currentFilter ) +{ + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + if (currentFilter.getLength()) + { + OUString FilterExt; + m_lFilters.getFilterByName(currentFilter, FilterExt); + + sal_Int32 posOfPoint = FilterExt.indexOf(L'.'); + const sal_Unicode* pFirstExtStart = FilterExt.getStr() + posOfPoint + 1; + + sal_Int32 posOfSemiColon = FilterExt.indexOf(L';') - 1; + if (posOfSemiColon < 0) + posOfSemiColon = FilterExt.getLength() - 1; + + FilterExt = OUString(pFirstExtStart, posOfSemiColon - posOfPoint); + iDialog->SetDefaultExtension ( o3tl::toW(FilterExt.getStr()) ); + } +} + +void VistaFilePickerImpl::onAutoExtensionChanged (bool bChecked) +{ + const OUString sFilter = m_lFilters.getCurrentFilter (); + OUString sExt ; + if (!m_lFilters.getFilterByName(sFilter, sExt)) + return; + + TFileDialog iDialog = impl_getBaseDialogInterface(); + if (!iDialog.is()) + return; + + PCWSTR pExt = nullptr; + if ( bChecked ) + { + pExt = o3tl::toW(sExt.getStr()); + pExt = wcsrchr( pExt, '.' ); + if ( pExt ) + pExt++; + } + iDialog->SetDefaultExtension( pExt ); +} + +bool VistaFilePickerImpl::onFileTypeChanged( UINT /*nTypeIndex*/ ) +{ + return true; +} + +void VistaFilePickerImpl::onDirectoryChanged() +{ + m_sDirectory = GetDirectory(); +} + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/VistaFilePickerImpl.hxx b/fpicker/source/win32/VistaFilePickerImpl.hxx new file mode 100644 index 000000000..8e757c9c6 --- /dev/null +++ b/fpicker/source/win32/VistaFilePickerImpl.hxx @@ -0,0 +1,310 @@ +/* -*- 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 "platform_vista.h" + +#include <shobjidl.h> + +#include "requests.hxx" +#include "vistatypes.h" +#include "FilterContainer.hxx" +#include "VistaFilePickerEventHandler.hxx" +#include "IVistaFilePickerInternalNotify.hxx" +#include "resourceprovider.hxx" + +#include <cppuhelper/interfacecontainer.h> +#include <osl/thread.hxx> +#include <osl/conditn.hxx> +#include <rtl/ustring.hxx> + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +// types, const etcpp + + +const ::sal_Int32 FEATURE_AUTOEXTENSION = 1; +const ::sal_Int32 FEATURE_PASSWORD = 2; +const ::sal_Int32 FEATURE_FILTEROPTIONS = 4; +const ::sal_Int32 FEATURE_SELECTION = 8; +const ::sal_Int32 FEATURE_TEMPLATE = 16; +const ::sal_Int32 FEATURE_LINK = 32; +const ::sal_Int32 FEATURE_PREVIEW = 64; +const ::sal_Int32 FEATURE_IMAGETEMPLATE = 128; +const ::sal_Int32 FEATURE_PLAY = 256; +const ::sal_Int32 FEATURE_READONLY = 512; +const ::sal_Int32 FEATURE_VERSION = 1024; +const ::sal_Int32 FEATURE_GPGPASSWORD = 2048; +const ::sal_Int32 FEATURE_IMAGEANCHOR = 4096; + +inline constexpr OUStringLiteral PROP_PICKER_LISTENER(u"picker_listener" ); // [XFilePickerListenert] +inline constexpr OUStringLiteral PROP_DIALOG_SHOW_RESULT(u"dialog_show_result" ); // [sal_Bool] true=OK, false=CANCEL +inline constexpr OUStringLiteral PROP_SELECTED_FILES(u"selected_files" ); // [seq< OUString >] contains all user selected files (can be empty!) +inline constexpr OUStringLiteral PROP_MULTISELECTION_MODE(u"multiselection_mode"); // [sal_Bool] true=ON, false=OFF +inline constexpr OUStringLiteral PROP_TITLE(u"title" ); // [OUString] +inline constexpr OUStringLiteral PROP_FILENAME(u"filename" ); // [OUString] +inline constexpr OUStringLiteral PROP_DIRECTORY(u"directory" ); // [OUString] +inline constexpr OUStringLiteral PROP_FEATURES(u"features" ); // [sal_Int32] +inline constexpr OUStringLiteral PROP_TEMPLATE_DESCR(u"templatedescription"); // [sal_Int32] +inline constexpr OUStringLiteral PROP_FILTER_TITLE(u"filter_title" ); // [OUString] +inline constexpr OUStringLiteral PROP_FILTER_VALUE(u"filter_value" ); // [OUString] +inline constexpr OUStringLiteral PROP_FILTER_GROUP(u"filter-group" ); // [seq< css:beans::StringPair >] contains a group of filters + +inline constexpr OUStringLiteral PROP_CONTROL_ID(u"control_id" ); // [sal_Int16] +inline constexpr OUStringLiteral PROP_CONTROL_ACTION(u"control_action" ); // [sal_Int16] +inline constexpr OUStringLiteral PROP_CONTROL_VALUE(u"control_value" ); // [Any] +inline constexpr OUStringLiteral PROP_CONTROL_LABEL(u"control_label" ); // [OUString] +inline constexpr OUStringLiteral PROP_CONTROL_ENABLE(u"control_enable" ); // [sal_Bool] true=ON, false=OFF +inline constexpr OUStringLiteral PROP_PARENT_WINDOW(u"ParentWindow"); //[css::awt::XWindow] preferred parent window +inline constexpr OUStringLiteral STRING_SEPARATOR(u"------------------------------------------" ); + +class TDialogImplBase; + +/** native implementation of the file picker on Vista and upcoming windows versions. + * This dialog uses COM internally. Further it marshall every request so it will + * be executed on the main thread which is an STA thread ! + */ + +class VistaFilePickerImpl : public IVistaFilePickerInternalNotify +{ + public: + + // Workaround made to get input in Template Listbox + css::uno::Sequence< OUString > m_lItems; + /** used for marshalling requests. + * Will be used to map requests to the right implementations. + */ + enum ERequest + { + E_NO_REQUEST, + E_ADD_PICKER_LISTENER, + E_REMOVE_PICKER_LISTENER, + E_APPEND_FILTER, + E_SET_CURRENT_FILTER, + E_GET_CURRENT_FILTER, + E_CREATE_OPEN_DIALOG, + E_CREATE_SAVE_DIALOG, + E_CREATE_FOLDER_PICKER, + E_SET_MULTISELECTION_MODE, + E_SET_TITLE, + E_SET_FILENAME, + E_GET_DIRECTORY, + E_SET_DIRECTORY, + E_SET_DEFAULT_NAME, + E_GET_SELECTED_FILES, + E_SHOW_DIALOG_MODAL, + E_SET_CONTROL_VALUE, + E_GET_CONTROL_VALUE, + E_SET_CONTROL_LABEL, + E_GET_CONTROL_LABEL, + E_ENABLE_CONTROL, + E_APPEND_FILTERGROUP + }; + + public: + + + // ctor/dtor - nothing special + + VistaFilePickerImpl(); + virtual ~VistaFilePickerImpl(); + + + // RequestHandler + + void doRequest(Request& rRequest); + + + // IVistaFilePickerInternalNotify + + virtual void onAutoExtensionChanged (bool bChecked) override; + virtual bool onFileTypeChanged( UINT nTypeIndex ) override; + virtual void onDirectoryChanged() override; + + private: + OUString GetDirectory(); + + /// implementation of request E_ADD_FILEPICKER_LISTENER + void impl_sta_addFilePickerListener(Request& rRequest); + + + /// implementation of request E_REMOVE_FILEPICKER_LISTENER + void impl_sta_removeFilePickerListener(Request& rRequest); + + + /// implementation of request E_APPEND_FILTER + void impl_sta_appendFilter(Request& rRequest); + + + /// implementation of request E_APPEND_FILTERGROUP + void impl_sta_appendFilterGroup(Request& rRequest); + + + /// implementation of request E_SET_CURRENT_FILTER + void impl_sta_setCurrentFilter(Request& rRequest); + + + /// implementation of request E_GET_CURRENT_FILTER + void impl_sta_getCurrentFilter(Request& rRequest); + + + /// implementation of request E_CREATE_OPEN_DIALOG + void impl_sta_CreateOpenDialog(Request& rRequest); + + + /// implementation of request E_CREATE_SAVE_DIALOG + void impl_sta_CreateSaveDialog(Request& rRequest); + + + /// implementation of request E_CREATE_FOLDER_PICKER + void impl_sta_CreateFolderPicker(Request& rRequest); + + + /// implementation of request E_SET_MULTISELECTION_MODE + void impl_sta_SetMultiSelectionMode(Request& rRequest); + + + /// implementation of request E_SET_TITLE + void impl_sta_SetTitle(Request& rRequest); + + + /// implementation of request E_SET_FILENAME + void impl_sta_SetFileName(Request& rRequest); + + + /// implementation of request E_SET_DIRECTORY + void impl_sta_SetDirectory(Request& rRequest); + + + /// implementation of request E_GET_DIRECTORY + void impl_sta_GetDirectory(Request& rRequest); + + + /// implementation of request E_SET_DEFAULT_NAME + void impl_sta_SetDefaultName(Request& rRequest); + + + /// implementation of request E_GET_SELECTED_FILES + void impl_sta_getSelectedFiles(Request& rRequest); + + + /// implementation of request E_SHOW_DIALOG_MODAL + void impl_sta_ShowDialogModal(Request& rRequest); + + + /// implementation of request E_SET_CONTROL_VALUE + void impl_sta_SetControlValue(Request& rRequest); + + + /// implementation of request E_GET_CONTROL_VALUE + void impl_sta_GetControlValue(Request& rRequest); + + + /// implementation of request E_SET_CONTROL_LABEL + void impl_sta_SetControlLabel(Request& rRequest); + + + /// implementation of request E_GET_CONTROL_LABEL + static void impl_sta_GetControlLabel(Request& rRequest); + + + /// implementation of request E_ENABLE_CONTROL + void impl_sta_EnableControl(Request& rRequest); + + /** create all needed (optional!) UI controls addressed by the field nFeatures. + * The given number nFeatures is used as a flag field. Use const values FEATURE_XXX + * to address it. + * + * Internal new controls will be added to the dialog. Every control can be accessed + * by its own control id. Those control ID must be one of the const set + * css::ui::dialogs::ExtendedFilePickerElementIds. + * + * @see setControlValue() + * @see getControlValue() + * @see setControlLabel() + * @see getControlLabel() + * @see enableControl() + * + * @param nFeatures + * flag field(!) knows all features which must be enabled. + */ + void impl_sta_enableFeatures(::sal_Int32 nFeatures, ::sal_Int32 nTemplate); + + + /** returns an interface, which can be used to customize the internally used + * COM dialog. + * + * Because we use two member (open/save dialog) internally, this method + * ask the current active one for its customization interface. + * + * @return the customization interface for the current used dialog. + * Must not be null. + */ + TFileDialogCustomize impl_getCustomizeInterface(); + TFileDialog impl_getBaseDialogInterface(); + + + /// fill filter list of internal used dialog. + void impl_sta_setFiltersOnDialog(); + + void impl_SetDefaultExtension( const OUString& currentFilter ); + + private: + template <class TDialogImplClass> void impl_sta_CreateDialog(); + void impl_sta_InitDialog(Request& rRequest, DWORD nOrFlags); + + + /// object representing a file dialog + std::shared_ptr<TDialogImplBase> m_pDialog; + + + /// @todo document me + CFilterContainer m_lFilters; + + + /** help us to handle dialog events and provide them to interested office + * listener. + */ + TFileDialogEvents m_iEventHandler; + + + /// @todo document me + bool m_bInExecute; + + bool m_bWasExecuted; + + // handle to parent window + HWND m_hParentWindow; + + + OUString m_sDirectory; + + + OUString m_sFilename; +}; + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/WinImplHelper.cxx b/fpicker/source/win32/WinImplHelper.cxx new file mode 100644 index 000000000..5d6e20d92 --- /dev/null +++ b/fpicker/source/win32/WinImplHelper.cxx @@ -0,0 +1,161 @@ +/* -*- 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 "WinImplHelper.hxx" + +#include <vector> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using ::com::sun::star::lang::IllegalArgumentException; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Sequence; + +const sal_Unicode TILDE_SIGN = L'~'; +const sal_Unicode AMPERSAND_SIGN = L'&'; + +// OS NAME Platform Major Minor + +// Windows NT 3.51 VER_PLATFORM_WIN32_NT 3 51 +// Windows NT 4.0 VER_PLATFORM_WIN32_NT 4 0 +// Windows 2000 VER_PLATFORM_WIN32_NT 5 0 +// Windows XP VER_PLATFORM_WIN32_NT 5 1 +// Windows Vista VER_PLATFORM_WIN32_NT 6 0 +// Windows 7 VER_PLATFORM_WIN32_NT 6 1 +// Windows 95 VER_PLATFORM_WIN32_WINDOWS 4 0 +// Windows 98 VER_PLATFORM_WIN32_WINDOWS 4 10 +// Windows ME VER_PLATFORM_WIN32_WINDOWS 4 90 + + +static void Replace( const OUString& aLabel, sal_Unicode OldChar, sal_Unicode NewChar, OUStringBuffer& aBuffer ) +{ + OSL_ASSERT( aLabel.getLength( ) ); + OSL_ASSERT( aBuffer.getCapacity( ) >= (aLabel.getLength( )) ); + + sal_Int32 i = 0; + const sal_Unicode* pCurrent = aLabel.getStr( ); + const sal_Unicode* pNext = aLabel.getStr( ) + 1; + const sal_Unicode* pEnd = aLabel.getStr( ) + aLabel.getLength( ); + + while( pCurrent < pEnd ) + { + OSL_ASSERT( pNext <= pEnd ); + OSL_ASSERT( (i >= 0) && (i < aBuffer.getCapacity( )) ); + + if ( OldChar == *pCurrent ) + { + if ( OldChar == *pNext ) + { + // two OldChars in line will + // be replaced by one + // e.g. ~~ -> ~ + aBuffer.insert( i, *pCurrent ); + + // skip the next one + pCurrent++; + pNext++; + } + else + { + // one OldChar will be replace + // by NexChar + aBuffer.insert( i, NewChar ); + } + } + else if ( *pCurrent == NewChar ) + { + // a NewChar will be replaced by + // two NewChars + // e.g. & -> && + aBuffer.insert( i++, *pCurrent ); + aBuffer.insert( i, *pCurrent ); + } + else + { + aBuffer.insert( i, *pCurrent ); + } + + pCurrent++; + pNext++; + i++; + } +} + +// converts a soffice label to a windows label +// the following rules for character replacements +// will be done: +// '~' -> '&' +// '~~' -> '~' +// '&' -> '&&' + +OUString SOfficeToWindowsLabel( const OUString& aSOLabel ) +{ + OUString aWinLabel = aSOLabel; + + if ( (aWinLabel.indexOf( TILDE_SIGN ) > -1) || (aWinLabel.indexOf( AMPERSAND_SIGN ) > -1) ) + { + sal_Int32 nStrLen = aWinLabel.getLength( ); + + // in the worst case the new string is + // doubled in length, maybe some waste + // of memory but how long is a label + // normally(?) + OUStringBuffer aBuffer( nStrLen * 2 ); + + Replace( aWinLabel, TILDE_SIGN, AMPERSAND_SIGN, aBuffer ); + + aWinLabel = aBuffer.makeStringAndClear( ); + } + + return aWinLabel; +} + +// converts a windows label to a soffice label +// the following rules for character replacements +// will be done: +// '&' -> '~' +// '&&' -> '&' +// '~' -> '~~' + +OUString WindowsToSOfficeLabel( const OUString& aWinLabel ) +{ + OUString aSOLabel = aWinLabel; + + if ( (aSOLabel.indexOf( TILDE_SIGN ) > -1) || (aSOLabel.indexOf( AMPERSAND_SIGN ) > -1) ) + { + sal_Int32 nStrLen = aSOLabel.getLength( ); + + // in the worst case the new string is + // doubled in length, maybe some waste + // of memory but how long is a label + // normally(?) + OUStringBuffer aBuffer( nStrLen * 2 ); + + Replace( aSOLabel, AMPERSAND_SIGN, TILDE_SIGN, aBuffer ); + + aSOLabel = aBuffer.makeStringAndClear( ); + } + + return aSOLabel; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/WinImplHelper.hxx b/fpicker/source/win32/WinImplHelper.hxx new file mode 100644 index 000000000..9f7e16e94 --- /dev/null +++ b/fpicker/source/win32/WinImplHelper.hxx @@ -0,0 +1,49 @@ +/* -*- 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 <sal/types.h> +#include <rtl/ustring.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +// converts a soffice label to a windows label +// the following rules for character replacements +// will be done: +// '~' -> '&' +// '~~' -> '~' +// '&' -> '&&' +OUString SOfficeToWindowsLabel(const OUString& aSOLabel); + +// converts a windows label to a soffice label +// the following rules for character replacements +// will be done: +// '&' -> '~' +// '&&' -> '&' +// '~' -> '~~' +OUString WindowsToSOfficeLabel(const OUString& aWinLabel); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/fps.component b/fpicker/source/win32/fps.component new file mode 100644 index 000000000..3cc9c8029 --- /dev/null +++ b/fpicker/source/win32/fps.component @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.ui.dialogs.Win32FilePicker" + constructor="fpicker_win32_FilePicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.SystemFilePicker"/> + </implementation> + <implementation name="com.sun.star.ui.dialogs.Win32FolderPicker" + constructor="fpicker_win32_FolderPicker_get_implementation"> + <service name="com.sun.star.ui.dialogs.SystemFolderPicker"/> + </implementation> +</component> diff --git a/fpicker/source/win32/platform_vista.h b/fpicker/source/win32/platform_vista.h new file mode 100644 index 000000000..ab3f7c52c --- /dev/null +++ b/fpicker/source/win32/platform_vista.h @@ -0,0 +1,43 @@ +/* -*- 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 + +#undef _WTL_NO_CSTRING + +#define _WTL_NO_CSTRING + +#if defined _MSC_VER +#include <comip.h> +#undef RGB +#endif + +#ifdef _MSC_VER +#if defined _M_IX86 + #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_IA64 + #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_X64 + #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#else + #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/requests.hxx b/fpicker/source/win32/requests.hxx new file mode 100644 index 000000000..a12f61ad8 --- /dev/null +++ b/fpicker/source/win32/requests.hxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <comphelper/sequenceashashmap.hxx> + +namespace fpicker +{ +namespace win32 +{ +namespace vista +{ +/** @todo document me + */ +class Request +{ + // interface + +public: + explicit Request() + : m_nRequest(-1) + , m_lArguments() + { + } + + virtual ~Request(){}; + + void setRequest(::sal_Int32 nRequest) { m_nRequest = nRequest; } + + ::sal_Int32 getRequest() { return m_nRequest; } + + void clearArguments() { m_lArguments.clear(); } + + template <class TArgumentType> + void setArgument(const OUString& sName, const TArgumentType& aValue) + { + m_lArguments[sName] = css::uno::toAny(aValue); + } + + template <class TArgumentType> + TArgumentType getArgumentOrDefault(const OUString& sName, const TArgumentType& aDefault) + { + return m_lArguments.getUnpackedValueOrDefault(sName, aDefault); + } + + css::uno::Any getValue(OUString const& key) const { return m_lArguments.getValue(key); } + + // member + +private: + ::sal_Int32 m_nRequest; + ::comphelper::SequenceAsHashMap m_lArguments; +}; + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/fpicker/source/win32/resourceprovider.cxx b/fpicker/source/win32/resourceprovider.cxx new file mode 100644 index 000000000..3515e9432 --- /dev/null +++ b/fpicker/source/win32/resourceprovider.cxx @@ -0,0 +1,103 @@ +/* -*- 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 <sal/config.h> + +#include <memory> + +#include <rtl/ustrbuf.hxx> +#include "resourceprovider.hxx" +#include <osl/mutex.hxx> +#include <fpicker/strings.hrc> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <unotools/resmgr.hxx> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <fpicker/fpsofficeResMgr.hxx> + +using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds; + +#define FOLDERPICKER_TITLE 500 +#define FOLDER_PICKER_DEF_DESCRIPTION 501 + +// 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 }, + { CHECKBOX_GPGENCRYPTION, STR_SVT_FILEPICKER_GPGENCRYPT } +}; + +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 +{ + OUString getResString( sal_Int16 aId ) + { + OUString aResOUString; + // translate the control id to a resource id + TranslateId pResId = CtrlIdToResId(aId); + if (pResId) + aResOUString = FpsResId(pResId); + return aResOUString; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/resourceprovider.hxx b/fpicker/source/win32/resourceprovider.hxx new file mode 100644 index 000000000..09ae8339b --- /dev/null +++ b/fpicker/source/win32/resourceprovider.hxx @@ -0,0 +1,35 @@ +/* -*- 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 <sal/config.h> + +#include <memory> + +#include <sal/types.h> + +#include <rtl/ustring.hxx> + +namespace CResourceProvider +{ +OUString getResString(sal_Int16 aId); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/shared.hxx b/fpicker/source/win32/shared.hxx new file mode 100644 index 000000000..c47f6411e --- /dev/null +++ b/fpicker/source/win32/shared.hxx @@ -0,0 +1,29 @@ +/* -*- 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 <rtl/ustring.hxx> + +const OUStringLiteral BACKSLASH(u"\\"); +const OUStringLiteral FILTER_SEPARATOR(u"------------------------------------------"); +const OUStringLiteral ALL_FILES_WILDCARD(u"*.*"); +const bool ALLOW_DUPLICATES = true; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/vistatypes.h b/fpicker/source/win32/vistatypes.h new file mode 100644 index 000000000..a6a08faa5 --- /dev/null +++ b/fpicker/source/win32/vistatypes.h @@ -0,0 +1,43 @@ +/* -*- 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 <shobjidl.h> +#include <systools/win32/comtools.hxx> + +namespace fpicker{ +namespace win32{ +namespace vista{ + + +// types, const etcpp. + + +typedef sal::systools::COMReference<IFileDialog> TFileDialog; +typedef sal::systools::COMReference<IFileOpenDialog> TFileOpenDialog; +typedef sal::systools::COMReference<IFileSaveDialog> TFileSaveDialog; +typedef sal::systools::COMReference<IFileDialogEvents> TFileDialogEvents; +typedef sal::systools::COMReference<IFileDialogCustomize> TFileDialogCustomize; + +} // namespace vista +} // namespace win32 +} // namespace fpicker + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/workbench/Test_fps.cxx b/fpicker/source/win32/workbench/Test_fps.cxx new file mode 100644 index 000000000..899f208f1 --- /dev/null +++ b/fpicker/source/win32/workbench/Test_fps.cxx @@ -0,0 +1,356 @@ +/* -*- 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 <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/registry/XSimpleRegistry.hpp> +#include <osl/file.hxx> + +#include <cppuhelper/servicefactory.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <osl/diagnose.h> +#include <com/sun/star/ui/dialogs/XFilePicker.hpp> +#include <com/sun/star/ui/dialogs/XFilterManager.hpp> + +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/ui/dialogs/XFilePickerListener.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerNotifier.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ListboxControlActions.hpp> +#include <com/sun/star/ui/dialogs/XFilePreview.hpp> + +#include <osl/thread.h> + +#include <stdio.h> +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include "..\FPServiceInfo.hxx" + + +// namespaces + + +using namespace ::cppu ; +using namespace ::com::sun::star::uno ; +using namespace ::com::sun::star::lang ; +using namespace ::com::sun::star::ui::dialogs ; +using namespace ::com::sun::star::ui::dialogs::TemplateDescription; + +using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds; +using namespace ::com::sun::star::ui::dialogs::ListboxControlActions; + +using namespace std ; + +// forward + +void TestFilterManager( Reference< XFilePicker > xFilePicker ); + + +#define RDB_SYSPATH "D:\\Projects\\gsl\\sysui\\wntmsci7\\bin\\applicat.rdb" + + +// global variables + + +Reference< XMultiServiceFactory > g_xFactory; + +static const OUStringLiteral BMP_EXTENSION( u"bmp" ); + + +// a test client + + +class FilePickerListener : public WeakImplHelper< XFilePickerListener > +{ +public: + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) + throw(css::uno::RuntimeException); + + // XFilePickerListener + virtual void SAL_CALL fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException); + + virtual void SAL_CALL directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException); + + virtual OUString SAL_CALL helpRequested( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException); + + virtual void SAL_CALL controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException); + + virtual void SAL_CALL dialogSizeChanged( ) + throw (css::uno::RuntimeException); +}; + +void SAL_CALL FilePickerListener::disposing( const css::lang::EventObject& Source ) + throw(css::uno::RuntimeException) +{ +} + +void SAL_CALL FilePickerListener::fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException) +{ + try + { + Reference< XFilePicker > rXFilePicker( aEvent.Source, UNO_QUERY ); + Reference< XFilePreview > rXFilePreview( rXFilePicker, UNO_QUERY ); + + if ( !rXFilePreview.is( ) ) + return; + + Sequence< OUString > aFileList = rXFilePicker->getFiles( ); + if ( 1 == aFileList.getLength( ) ) + { + OUString FilePath = aFileList[0]; + + // detect file extension + sal_Int32 nIndex = FilePath.lastIndexOf( BMP_EXTENSION ); + if ( (FilePath.getLength( ) - 3) == nIndex ) + { + OUString FileSysPath; + ::osl::FileBase::getSystemPathFromFileURL( + FilePath, FileSysPath ); + + HANDLE hFile = CreateFileW( + FileSysPath.getStr( ), + GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ; + + if (hFile == INVALID_HANDLE_VALUE) + return; + + DWORD dwHighSize; + DWORD dwFileSize = GetFileSize (hFile, &dwHighSize) ; + + if (dwHighSize) + { + CloseHandle (hFile) ; + return; + } + + Sequence< sal_Int8 > aDIB( dwFileSize ); + + DWORD dwBytesRead; + sal_Bool bSuccess = ReadFile (hFile, aDIB.getArray( ), dwFileSize, &dwBytesRead, NULL) ; + CloseHandle (hFile); + + BITMAPFILEHEADER* pbmfh = (BITMAPFILEHEADER*)aDIB.getConstArray( ); + if (!bSuccess || (dwBytesRead != dwFileSize) + || (pbmfh->bfType != * (WORD *) "BM") + || (pbmfh->bfSize != dwFileSize)) + { + return; + } + + Any aAny; + + aAny <<= aDIB; + rXFilePreview->setImage( 1, aAny ); + } + } + } + catch( IllegalArgumentException& ) + { + } +} + +void SAL_CALL FilePickerListener::directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException) +{ + Reference< XFilePickerControlAccess > rFilePickerCtrlAccess( aEvent.Source, UNO_QUERY ); +} + +OUString SAL_CALL FilePickerListener::helpRequested( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException) +{ + return OUString( ); +} + +void SAL_CALL FilePickerListener::controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) + throw(css::uno::RuntimeException) +{ + try + { + Reference< XFilePickerControlAccess > rFPCtrlAccess( aEvent.Source, UNO_QUERY ); + + Any aValue; + + OUString lbString( L"Ein Eintrag 1" ); + aValue <<= lbString; + rFPCtrlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aValue ); + + lbString = L"Ein Eintrag 2"; + aValue <<= lbString; + rFPCtrlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aValue ); + + lbString = L"Ein Eintrag 3"; + aValue <<= lbString; + rFPCtrlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aValue ); + + sal_Int16 nSel = 1; + aValue <<= nSel; + rFPCtrlAccess->setValue( LISTBOX_VERSION, SET_SELECT_ITEM, aValue ); + + sal_Int32 nDel = 0; + aValue <<= nDel; + rFPCtrlAccess->setValue( LISTBOX_VERSION, DELETE_ITEM, aValue ); + } + catch( ... ) + { + } +} + +void SAL_CALL FilePickerListener::dialogSizeChanged( ) + throw(css::uno::RuntimeException) +{ +} + + +// main + + +int SAL_CALL main(int nArgc, char* Argv[], char* Env[] ) +{ + printf("Starting test of FPS-Service\n"); + + + // get the global service-manager + + + // Get global factory for uno services. + Reference< XMultiServiceFactory > g_xFactory( createRegistryServiceFactory( RDB_SYSPATH ) ); + + // Print a message if an error occurred. + if ( g_xFactory.is() == sal_False ) + { + OSL_FAIL("Can't create RegistryServiceFactory"); + return(-1); + } + + + // try to get an Interface to a XFilePicker Service + + + Sequence< Any > arguments(1); + arguments[0] = makeAny( FILEOPEN_READONLY_VERSION ); + + Reference< XFilePicker > xFilePicker( + g_xFactory->createInstanceWithArguments( + "com.sun.star.ui.dialogs.SystemFilePicker", arguments ), UNO_QUERY ); + + // install a FilePicker notifier + Reference< XFilePickerListener > xFPListener( + static_cast< XFilePickerListener* >( new FilePickerListener()), UNO_QUERY ); + + Reference< XFilePickerNotifier > xFPNotifier( xFilePicker, UNO_QUERY ); + if ( xFPNotifier.is( ) ) + xFPNotifier->addFilePickerListener( xFPListener ); + + xFilePicker->setTitle( OUString("FileOpen Simple...")); + xFilePicker->setMultiSelectionMode( sal_True ); + xFilePicker->setDefaultName( OUString("d:\\test2.sxw")); + + OUString aDirURL; + OUString aSysPath = OStringToOUString( "d:\\ueaeoe", osl_getThreadTextEncoding( ) ); + ::osl::FileBase::getFileURLFromSystemPath( aSysPath, aDirURL ); + xFilePicker->setDisplayDirectory( aDirURL ); + + Reference< XFilterManager > xFilterMgr( xFilePicker, UNO_QUERY ); + if ( xFilterMgr.is( ) ) + { + xFilterMgr->appendFilter( L"Alle", L"*.*" ); + xFilterMgr->appendFilter( L"BMP", L"*.bmp" ); + xFilterMgr->appendFilter( L"SDW", L"*.sdw;*.sdc;*.sdi" ); + xFilterMgr->appendFilter( L"SXW", L"*.sxw;*.sxi" ); + } + + Reference< XFilePickerControlAccess > xFPControlAccess( xFilePicker, UNO_QUERY ); + + Any aAny; + sal_Bool bChkState = sal_False; + + aAny.setValue( &bChkState, cppu::UnoType<sal_Bool>::get()); + xFPControlAccess->setValue( CHECKBOX_AUTOEXTENSION, 0, aAny ); + + OUString aVersion( L"Version 1" ); + aAny <<= aVersion; + xFPControlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aAny ); + xFPControlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aAny ); + xFPControlAccess->setValue( LISTBOX_VERSION, ADD_ITEM, aAny ); + + xFilePicker->execute( ); + + sal_Bool bCheckState; + aAny = xFPControlAccess->getValue( CHECKBOX_AUTOEXTENSION, 0 ); + if ( aAny.hasValue( ) ) + bCheckState = *reinterpret_cast< const sal_Bool* >( aAny.getValue( ) ); + + aAny = xFPControlAccess->getValue( CHECKBOX_READONLY, 0 ); + if ( aAny.hasValue( ) ) + bCheckState = *reinterpret_cast< const sal_Bool* >( aAny.getValue( ) ); + + aAny = xFPControlAccess->getValue( LISTBOX_VERSION, GET_SELECTED_ITEM ); + sal_Int32 nSel; + if ( aAny.hasValue( ) ) + aAny >>= nSel; + + aDirURL = xFilePicker->getDisplayDirectory( ); + Sequence< OUString > aFileList = xFilePicker->getFiles( ); + for ( int i = 0; i < aFileList.getLength( ); i++ ) + { + OUString nextPath = aFileList[i]; + } + + if ( xFPNotifier.is( ) ) + xFPNotifier->removeFilePickerListener( xFPListener ); + + + // shutdown + + + // Cast factory to XComponent + Reference< XComponent > xComponent( g_xFactory, UNO_QUERY ); + + // Print a message if an error occurred. + if ( xComponent.is() == sal_False ) + { + OSL_FAIL("Error shutting down"); + } + + // Dispose and clear factory + xComponent->dispose(); + g_xFactory.clear(); + + printf("Test successful\n"); + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/fpicker/source/win32/workbench/makefile.mk b/fpicker/source/win32/workbench/makefile.mk new file mode 100644 index 000000000..5398300c1 --- /dev/null +++ b/fpicker/source/win32/workbench/makefile.mk @@ -0,0 +1,55 @@ +# +# 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 . +# + +PRJ=..$/..$/..$/.. + +PRJNAME=sysui +TARGET=testfps +LIBTARGET=NO +TARGETTYPE=CUI + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +.IF "$(COM)" == "MSC" +CFLAGS+=-GR -GX +.ENDIF + +# --- Files -------------------------------------------------------- + + +OBJFILES=$(OBJ)$/test_fps.obj + +APP1TARGET=$(TARGET) +APP1OBJS=$(OBJFILES) + +APP1STDLIBS+=\ + $(CPPULIB)\ + $(CPPUHELPERLIB)\ + $(SALLIB)\ + $(USER32LIB)\ + $(OLE32LIB) + +APP1DEF=$(MISC)$/$(APP1TARGET).def + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk + + diff --git a/fpicker/uiconfig/ui/breadcrumb.ui b/fpicker/uiconfig/ui/breadcrumb.ui new file mode 100644 index 000000000..5e58b5a76 --- /dev/null +++ b/fpicker/uiconfig/ui/breadcrumb.ui @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="fps"> + <requires lib="gtk+" version="3.20"/> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="container"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLinkButton" id="link"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> +</interface> diff --git a/fpicker/uiconfig/ui/explorerfiledialog.ui b/fpicker/uiconfig/ui/explorerfiledialog.ui new file mode 100644 index 000000000..852ae4f27 --- /dev/null +++ b/fpicker/uiconfig/ui/explorerfiledialog.ui @@ -0,0 +1,896 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface domain="fps"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-name">list-add</property> + </object> + <object class="GtkImage" id="image2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-name">list-remove</property> + </object> + <object class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">fpicker/res/fp014.png</property> + </object> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkTreeStore" id="liststore2"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkTreeStore" id="liststore3"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name text2 --> + <column type="gchararray"/> + <!-- column-name text3 --> + <column type="gchararray"/> + <!-- column-name text1 --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkDialog" id="ExplorerFileDialog"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="border_width">6</property> + <property name="modal">True</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="layout_style">start</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="box3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkBox" id="box4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkComboBoxText" id="current_path"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="has_entry">True</property> + <child internal-child="entry"> + <object class="GtkEntry"> + <property name="truncate-multiline">True</property> + <property name="can_focus">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="connect_to_server"> + <property name="label" translatable="yes" context="explorerfiledialog|connect_to_server">Servers...</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="explorerfiledialog|connect_to_server|tooltip_text">Connect To Server</property> + <property name="valign">center</property> + <child internal-child="accessible"> + <object class="AtkObject" id="connect_to_server-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes" context="explorerfiledialog|connect_to_server-atkobject">Connect To Server</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <property name="hscrollbar_policy">never</property> + <property name="vscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <property name="propagate_natural_width">True</property> + <property name="propagate_natural_height">True</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkToolbar" id="up_bar"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkMenuToolButton" id="up_btn"> + <property name="visible">True</property> + <property name="use_underline">True</property> + <property name="icon_name">res/fp010.png</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="new_folder"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="focus_on_click">False</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="explorerfiledialog|new_folder|tooltip_text">Create New Folder</property> + <property name="valign">center</property> + <property name="image">image4</property> + <property name="always_show_image">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="new_folder-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes" context="explorerfiledialog|new_folder-atkobject">Create New Folder</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="container"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkPaned"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="places"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore1</property> + <property name="headers_clickable">False</property> + <property name="search_column">1</property> + <property name="show_expanders">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn5"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="explorerfiledialog|places">Places</property> + <child> + <object class="GtkCellRendererPixbuf" id="cellrenderertext6"/> + <attributes> + <attribute name="pixbuf">0</attribute> + </attributes> + </child> + <child> + <object class="GtkCellRendererText" id="cellrenderer11"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="add"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="explorerfiledialog|add">Add current folder to Places</property> + <property name="image">image1</property> + <property name="always_show_image">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="del"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="explorerfiledialog|add">Remove selected folder from Places</property> + <property name="image">image2</property> + <property name="always_show_image">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">False</property> + <property name="shrink">True</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="fileview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore3</property> + <property name="search_column">1</property> + <property name="show_expanders">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn3"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="explorerfiledialog|name">Name</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererPixbuf" id="cellrenderertext1"/> + <attributes> + <attribute name="pixbuf">0</attribute> + </attributes> + </child> + <child> + <object class="GtkCellRendererText" id="cellrenderer1"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn4"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="explorerfiledialog|type">Type</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer5"/> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn1"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="explorerfiledialog|size">Size</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer2"/> + <attributes> + <attribute name="text">3</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn2"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="explorerfiledialog|date">Date modified</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer3"/> + <attributes> + <attribute name="text">4</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="can_focus">True</property> + <property name="no_show_all">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkIconView" id="iconview"> + <property name="can_focus">True</property> + <property name="no_show_all">True</property> + <property name="margin">6</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore2</property> + <property name="pixbuf-column">0</property> + <property name="text-column">1</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">True</property> + <property name="shrink">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkButtonBox" id="buttonbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="open"> + <property name="label" translatable="yes" context="explorerfiledialog|open">_Open</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="play"> + <property name="label" translatable="yes" context="explorerfiledialog|play">_Play</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">12</property> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="file_name_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes" context="explorerfiledialog|file_name_label">File _name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">file_name</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="shared_label"> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">shared</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="file_type_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes" context="explorerfiledialog|file_type_label">File _type:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">file_type</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="shared"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="file_type"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="file_name"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="has_entry">True</property> + <child internal-child="entry"> + <object class="GtkEntry"> + <property name="can_focus">True</property> + <property name="truncate-multiline">True</property> + <property name="activates_default">True</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkCheckButton" id="readonly"> + <property name="label" translatable="yes" context="explorerfiledialog|readonly">_Read-only</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="halign">start</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="checkboxes"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkCheckButton" id="password"> + <property name="label" translatable="yes" context="explorerfiledialog|password">Save with password</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="extension"> + <property name="label" translatable="yes" context="explorerfiledialog|extension">_Automatic file name extension</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="options"> + <property name="label" translatable="yes" context="explorerfiledialog|options">Edit _filter settings</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="selection"> + <property name="label"> +</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="gpgencrypt"> + <property name="label" translatable="yes" context="explorerfiledialog|gpgencrypt">Encrypt with GPG key</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkCheckButton" id="link"> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="cb_preview"> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkScrolledWindow" id="previewframe"> + <property name="can_focus">True</property> + <property name="no_show_all">True</property> + <property name="valign">start</property> + <property name="hscrollbar_policy">never</property> + <property name="vscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <property name="propagate_natural_width">True</property> + <property name="propagate_natural_height">True</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkImage" id="preview"> + <property name="width_request">200</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="vexpand">True</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="titlebar"> + <placeholder/> + </child> + <action-widgets> + <action-widget response="-11">help</action-widget> + </action-widgets> + </object> + <object class="GtkMenu" id="up_menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> +</interface> diff --git a/fpicker/uiconfig/ui/foldernamedialog.ui b/fpicker/uiconfig/ui/foldernamedialog.ui new file mode 100644 index 000000000..5cc933a70 --- /dev/null +++ b/fpicker/uiconfig/ui/foldernamedialog.ui @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="fps"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="FolderNameDialog"> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="foldernamedialog|FolderNameDialog">Folder Name</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="stock">_OK</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="secondary">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="spacing">12</property> + <property name="margin-start">12</property> + <property name="margin-top">6</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="foldernamedialog|label2">Na_me:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="truncate-multiline">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="foldernamedialog|label1">Create New Folder</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">ok</action-widget> + <action-widget response="-6">cancel</action-widget> + </action-widgets> + </object> +</interface> diff --git a/fpicker/uiconfig/ui/remotefilesdialog.ui b/fpicker/uiconfig/ui/remotefilesdialog.ui new file mode 100644 index 000000000..c486bf7eb --- /dev/null +++ b/fpicker/uiconfig/ui/remotefilesdialog.ui @@ -0,0 +1,562 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="fps"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">svx/res/galicon.png</property> + </object> + <object class="GtkImage" id="image2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">svx/res/listview.png</property> + </object> + <object class="GtkImage" id="image3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">fpicker/res/fp014.png</property> + </object> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkTreeStore" id="liststore2"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkTreeStore" id="liststore3"> + <columns> + <!-- column-name expander --> + <column type="GdkPixbuf"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name text2 --> + <column type="gchararray"/> + <!-- column-name text3 --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkMenu" id="service_edit_menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuItem" id="add_service"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|edit_service">_Add service</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="edit_service"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|edit_service">_Edit service</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="delete_service"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|delete_service">_Delete service</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="change_password"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|change_password">_Change password</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + <object class="GtkDialog" id="RemoteFilesDialog"> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="remotefilesdialog|RemoteFilesDialog">Remote Files</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="remotefilesdialog|open">_Open</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|label1">Service:</property> + <accessibility> + <relation type="label-for" target="services_lb"/> + </accessibility> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="services_lb"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <accessibility> + <relation type="labelled-by" target="label1"/> + </accessibility> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="add_service_btn"> + <property name="label" translatable="yes" context="remotefilesdialog|add_service_btn">_Manage services</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <property name="popup">service_edit_menu</property> + <property name="use_popover">False</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="breadcrumb_container"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="list_view"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="remotefilesdialog|list_view|tooltip_text">List view</property> + <property name="image">image2</property> + <property name="active">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="icon_view"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="remotefilesdialog|icon_view|tooltip_text">Icon view</property> + <property name="image">image1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="new_folder"> + <property name="width_request">25</property> + <property name="height_request">25</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes" context="remotefilesdialog|new_folder|tooltip_text">Create New Folder</property> + <property name="margin-start">6</property> + <property name="image">image3</property> + <property name="always_show_image">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="container"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkPaned"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="foldertree"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore1</property> + <property name="headers_visible">False</property> + <property name="headers_clickable">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="Macro Library List-selection1"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn5"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCellRendererPixbuf" id="cellrenderertext6"/> + <attributes> + <attribute name="pixbuf">0</attribute> + </attributes> + </child> + <child> + <object class="GtkCellRendererText" id="cellrenderer11"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="resize">False</property> + <property name="shrink">True</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="fileview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore3</property> + <property name="show_expanders">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="Macro Library List-selection2"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn3"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="remotefilesdialog|name">Name</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererPixbuf" id="cellrenderertext1"/> + <attributes> + <attribute name="pixbuf">0</attribute> + </attributes> + </child> + <child> + <object class="GtkCellRendererText" id="cellrenderer1"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn4"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="remotefilesdialog|size">Size</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer2"/> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn1"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="title" translatable="yes" context="remotefilesdialog|date">Date modified</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer3"/> + <attributes> + <attribute name="text">3</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="can_focus">True</property> + <property name="no_show_all">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkIconView" id="iconview"> + <property name="can_focus">True</property> + <property name="no_show_all">True</property> + <property name="margin">6</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore2</property> + <property name="pixbuf_column">0</property> + <property name="text_column">1</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">True</property> + <property name="shrink">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="filterLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|filterLabel">Filter</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">filter_lb</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="nameLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="remotefilesdialog|nameLabel">File name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">filename</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="filter_lb"> + <property name="width_request">200</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="filename"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="truncate-multiline">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help</action-widget> + <action-widget response="-6">cancel</action-widget> + <action-widget response="-5">ok</action-widget> + </action-widgets> + </object> +</interface> |