summaryrefslogtreecommitdiffstats
path: root/vcl/unx/gtk3/fpicker
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/unx/gtk3/fpicker')
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx2091
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx239
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx205
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx66
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.cxx295
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.hxx144
-rw-r--r--vcl/unx/gtk3/fpicker/eventnotification.hxx41
-rw-r--r--vcl/unx/gtk3/fpicker/resourceprovider.cxx80
8 files changed, 3161 insertions, 0 deletions
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
new file mode 100644
index 0000000000..4b93d03617
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,2091 @@
+/* -*- 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 <config_gio.h>
+
+#include <com/sun/star/awt/SystemDependentXWindow.hpp>
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/SystemDependent.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 <osl/diagnose.h>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbhelper.hxx>
+
+#include <algorithm>
+#include <set>
+#include <string.h>
+#include <string_view>
+
+#include "SalGtkFilePicker.hxx"
+
+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;
+
+struct FilterEntry
+{
+protected:
+ OUString m_sTitle;
+ OUString m_sFilter;
+
+ css::uno::Sequence< css::beans::StringPair > m_aSubFilters;
+
+public:
+ FilterEntry( OUString _aTitle, OUString _aFilter )
+ :m_sTitle(std::move( _aTitle ))
+ ,m_sFilter(std::move( _aFilter ))
+ {
+ }
+
+ 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( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList );
+
+ // helpers for iterating the sub filters
+ const css::beans::StringPair* beginSubFilters() const { return m_aSubFilters.begin(); }
+ const css::beans::StringPair* endSubFilters() const { return m_aSubFilters.end(); }
+};
+
+bool FilterEntry::hasSubFilters() const
+{
+ return m_aSubFilters.hasElements();
+}
+
+void FilterEntry::getSubFilters( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList )
+{
+ _rSubFilterList = m_aSubFilters;
+}
+
+void SalGtkFilePicker::dialog_mapped_cb(GtkWidget *, SalGtkFilePicker *pobjFP)
+{
+ pobjFP->InitialMapping();
+}
+
+void SalGtkFilePicker::InitialMapping()
+{
+ if (!mbPreviewState )
+ {
+ gtk_widget_hide( m_pPreview );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false);
+#endif
+ }
+ gtk_widget_set_size_request (m_pPreview, -1, -1);
+}
+
+SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext ),
+ SalGtkFilePicker_Base( m_rbHelperMtx ),
+ m_pVBox ( nullptr ),
+ mnHID_FolderChange( 0 ),
+ mnHID_SelectionChange( 0 ),
+ bVersionWidthUnset( false ),
+ mbPreviewState( false ),
+ mbInitialized(false),
+ mHID_Preview( 0 ),
+ m_pPreview( nullptr ),
+ m_pPseudoFilter( nullptr )
+{
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = nullptr;
+ mbToggleVisibility[i] = false;
+ }
+
+ for( i = 0; i < BUTTON_LAST; i++ )
+ {
+ m_pButtons[i] = nullptr;
+ mbButtonVisibility[i] = false;
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = nullptr;
+ m_pLists[i] = nullptr;
+ m_pListLabels[i] = nullptr;
+ mbListVisibility[i] = false;
+ }
+
+ OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN );
+
+ m_pDialog = GTK_WIDGET(g_object_new(GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "title", OUStringToOString(aFilePickerTitle, RTL_TEXTENCODING_UTF8).getStr(),
+ "action", GTK_FILE_CHOOSER_ACTION_OPEN,
+ nullptr));
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+
+ m_pVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+ // We don't want clickable items to have a huge hit-area
+ GtkWidget *pHBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ GtkWidget *pThinVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0);
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), pHBox);
+ gtk_box_prepend(GTK_BOX(m_pVBox), pThinVBox);
+#endif
+ gtk_widget_show( pHBox );
+ gtk_widget_show( pThinVBox );
+
+ OUString aLabel;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = gtk_check_button_new();
+
+#define LABEL_TOGGLE( elem ) \
+ case elem : \
+ aLabel = getResString( CHECKBOX_##elem ); \
+ setLabel( CHECKBOX_##elem, aLabel ); \
+ break
+
+ switch( i ) {
+ LABEL_TOGGLE( AUTOEXTENSION );
+ LABEL_TOGGLE( PASSWORD );
+ LABEL_TOGGLE( GPGENCRYPTION );
+ LABEL_TOGGLE( FILTEROPTIONS );
+ LABEL_TOGGLE( READONLY );
+ LABEL_TOGGLE( LINK );
+ LABEL_TOGGLE( PREVIEW );
+ LABEL_TOGGLE( SELECTION );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(pThinVBox), m_pToggles[i]);
+#endif
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+ GtkListStore *pListStores[ LIST_LAST ];
+ pListStores[i] = gtk_list_store_new (1, G_TYPE_STRING);
+ m_pLists[i] = gtk_combo_box_new_with_model(GTK_TREE_MODEL(pListStores[i]));
+ g_object_unref (pListStores[i]); // owned by the widget.
+ GtkCellRenderer *pCell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start(
+ GTK_CELL_LAYOUT(m_pLists[i]), pCell, true);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT (m_pLists[i]), pCell, "text", 0, nullptr);
+
+ m_pListLabels[i] = gtk_label_new( "" );
+
+#define LABEL_LIST( elem ) \
+ case elem : \
+ aLabel = getResString( LISTBOX_##elem##_LABEL ); \
+ setLabel( LISTBOX_##elem##_LABEL, aLabel ); \
+ break
+
+ switch( i )
+ {
+ LABEL_LIST( VERSION );
+ LABEL_LIST( TEMPLATE );
+ LABEL_LIST( IMAGE_TEMPLATE );
+ LABEL_LIST( IMAGE_ANCHOR );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pLists[i], false, false, 0 );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pLists[i]);
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pListLabels[i]);
+#endif
+ gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] );
+ gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pHBoxs[i]);
+#endif
+ }
+
+ aLabel = getResString( FILE_PICKER_FILE_TYPE );
+ m_pFilterExpander = gtk_expander_new_with_mnemonic(
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pFilterExpander);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+#else
+ GtkWidget *scrolled_window = gtk_scrolled_window_new();
+ gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(scrolled_window), true);
+#endif
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window);
+#else
+ gtk_expander_set_child(GTK_EXPANDER(m_pFilterExpander), scrolled_window);
+#endif
+ gtk_widget_show (scrolled_window);
+
+ m_pFilterStore = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING);
+ m_pFilterView = gtk_tree_view_new_with_model (GTK_TREE_MODEL(m_pFilterStore));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(m_pFilterView), false);
+
+ GtkCellRenderer *cell = nullptr;
+
+ for (i = 0; i < 2; ++i)
+ {
+ GtkTreeViewColumn *column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_set_expand (column, true);
+ gtk_tree_view_column_pack_start (column, cell, false);
+ gtk_tree_view_column_set_attributes (column, cell, "text", i, nullptr);
+ gtk_tree_view_append_column (GTK_TREE_VIEW(m_pFilterView), column);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), m_pFilterView);
+#endif
+ gtk_widget_show (m_pFilterView);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox );
+#endif
+
+ m_pPreview = gtk_image_new();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview );
+#endif
+
+ g_signal_connect( G_OBJECT( m_pToggles[PREVIEW] ), "toggled",
+ G_CALLBACK( preview_toggled_cb ), this );
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW(m_pFilterView)), "changed",
+ G_CALLBACK ( type_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pDialog ), "notify::filter",
+ G_CALLBACK( filter_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pFilterExpander ), "activate",
+ G_CALLBACK( expander_changed_cb ), this);
+ g_signal_connect (G_OBJECT( m_pDialog ), "map",
+ G_CALLBACK (dialog_mapped_cb), this);
+
+ gtk_widget_show( m_pVBox );
+
+ PangoLayout *layout = gtk_widget_create_pango_layout (m_pFilterView, nullptr);
+ guint ypad;
+ PangoRectangle row_height;
+ pango_layout_set_markup (layout, "All Files", -1);
+ pango_layout_get_pixel_extents (layout, nullptr, &row_height);
+ g_object_unref (layout);
+
+ g_object_get (cell, "ypad", &ypad, nullptr);
+ guint height = (row_height.height + 2*ypad) * 5;
+ gtk_widget_set_size_request (m_pFilterView, -1, height);
+ gtk_widget_set_size_request (m_pPreview, 1, height);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true);
+#endif
+}
+
+// XFilePickerNotifier
+
+void SAL_CALL SalGtkFilePicker::addFilePickerListener( const uno::Reference<XFilePickerListener>& xListener )
+{
+ SolarMutexGuard g;
+
+ OSL_ENSURE(!m_xListener.is(),
+ "SalGtkFilePicker only talks with one listener at a time...");
+ m_xListener = xListener;
+}
+
+void SAL_CALL SalGtkFilePicker::removeFilePickerListener( const uno::Reference<XFilePickerListener>& )
+{
+ SolarMutexGuard g;
+
+ m_xListener.clear();
+}
+
+// FilePicker Event functions
+
+void SalGtkFilePicker::impl_fileSelectionChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->fileSelectionChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_directoryChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->directoryChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_controlStateChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->controlStateChanged( aEvent );
+}
+
+static bool
+isFilterString( std::u16string_view rFilterString, const char *pMatch )
+{
+ sal_Int32 nIndex = 0;
+ bool bIsFilter = true;
+
+ OUString aMatch(OUString::createFromAscii(pMatch));
+
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(rFilterString, 0, ';', nIndex );
+ if( !o3tl::starts_with(aToken, aMatch) )
+ {
+ bIsFilter = false;
+ break;
+ }
+ }
+ while( nIndex >= 0 );
+
+ return bIsFilter;
+}
+
+static OUString
+shrinkFilterName( const OUString &rFilterName, bool bAllowNoStar = false )
+{
+ int i;
+ int nBracketLen = -1;
+ int nBracketEnd = -1;
+ const sal_Unicode *pStr = rFilterName.getStr();
+ OUString aRealName = rFilterName;
+
+ for( i = aRealName.getLength() - 1; i > 0; i-- )
+ {
+ if( pStr[i] == ')' )
+ nBracketEnd = i;
+ else if( pStr[i] == '(' )
+ {
+ nBracketLen = nBracketEnd - i;
+ if( nBracketEnd <= 0 )
+ continue;
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), "*." ) )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ else if (bAllowNoStar)
+ {
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), ".") )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ }
+ }
+ }
+
+ return aRealName;
+}
+
+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)
+ || (shrinkFilterName(_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 css::beans::StringPair& _rEntry )
+ {
+ OUString aShrunkName = shrinkFilterName( _rEntry.First );
+ return aShrunkName == rTitle;
+ }
+ };
+}
+
+bool SalGtkFilePicker::FilterNameExists( const OUString& rTitle )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ bRet =
+ ::std::any_of(
+ m_pFilterVector->begin(),
+ m_pFilterVector->end(),
+ FilterTitleMatch( rTitle )
+ );
+
+ return bRet;
+}
+
+bool SalGtkFilePicker::FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ {
+ bRet = std::any_of(_rGroupedFilters.begin(), _rGroupedFilters.end(),
+ [&](const css::beans::StringPair& rFilter) {
+ return ::std::any_of( m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch( rFilter.First ) ); });
+ }
+
+ return bRet;
+}
+
+void SalGtkFilePicker::ensureFilterVector( const OUString& _rInitialCurrentFilter )
+{
+ if( !m_pFilterVector )
+ {
+ m_pFilterVector.reset( new std::vector<FilterEntry> );
+
+ // set the first filter to the current filter
+ if ( m_aCurrentFilter.isEmpty() )
+ m_aCurrentFilter = _rInitialCurrentFilter;
+ }
+}
+
+void SAL_CALL SalGtkFilePicker::appendFilter( const OUString& aTitle, const OUString& aFilter )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( FilterNameExists( aTitle ) )
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ ensureFilterVector( aTitle );
+
+ // append the filter
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( aTitle, aFilter ) );
+}
+
+void SAL_CALL SalGtkFilePicker::setCurrentFilter( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( aTitle != m_aCurrentFilter )
+ {
+ m_aCurrentFilter = aTitle;
+ SetCurFilter( m_aCurrentFilter );
+ }
+
+ // TODO m_pImpl->setCurrentFilter( aTitle );
+}
+
+void SalGtkFilePicker::updateCurrentFilterFromName(const gchar* filtername)
+{
+ OUString aFilterName(filtername, strlen(filtername), RTL_TEXTENCODING_UTF8);
+ if (m_pFilterVector)
+ {
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if (aFilterName == shrinkFilterName(filter.getTitle()))
+ {
+ m_aCurrentFilter = filter.getTitle();
+ break;
+ }
+ }
+ }
+}
+
+void SalGtkFilePicker::UpdateFilterfromUI()
+{
+ // Update the filtername from the users selection if they have had a chance to do so.
+ // If the user explicitly sets a type then use that, if not then take the implicit type
+ // from the filter of the files glob on which he is currently searching
+ if (!mnHID_FolderChange || !mnHID_SelectionChange)
+ return;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ updateCurrentFilterFromName(title);
+ g_free (title);
+ }
+ else if( GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog)))
+ {
+ if (m_pPseudoFilter != filter)
+ updateCurrentFilterFromName(gtk_file_filter_get_name( filter ));
+ else
+ updateCurrentFilterFromName(OUStringToOString( m_aInitialFilter, RTL_TEXTENCODING_UTF8 ).getStr());
+ }
+}
+
+OUString SAL_CALL SalGtkFilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ UpdateFilterfromUI();
+
+ return m_aCurrentFilter;
+}
+
+// XFilterGroupManager functions
+
+void SAL_CALL SalGtkFilePicker::appendFilterGroup( const OUString& /*sGroupTitle*/, const uno::Sequence<beans::StringPair>& aFilters )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->appendFilterGroup( sGroupTitle, aFilters );
+ // check the names
+ if( FilterNameExists( aFilters ) )
+ // TODO: a more precise exception message
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ OUString sInitialCurrentFilter;
+ if( aFilters.hasElements() )
+ sInitialCurrentFilter = aFilters[0].First;
+
+ ensureFilterVector( sInitialCurrentFilter );
+
+ // append the filter
+ for( const auto& rSubFilter : aFilters )
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( rSubFilter.First, rSubFilter.Second ) );
+
+}
+
+// XFilePicker functions
+
+void SAL_CALL SalGtkFilePicker::setMultiSelectionMode( sal_Bool bMode )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(m_pDialog), bMode );
+}
+
+void SAL_CALL SalGtkFilePicker::setDefaultName( const OUString& aName )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aStr = OUStringToOString( aName, RTL_TEXTENCODING_UTF8 );
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) );
+
+ // set_current_name launches a Gtk critical error if called for other than save
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( m_pDialog ), aStr.getStr() );
+}
+
+void SAL_CALL SalGtkFilePicker::setDisplayDirectory( const OUString& rDirectory )
+{
+ SolarMutexGuard g;
+
+ implsetDisplayDirectory(rDirectory);
+}
+
+OUString SAL_CALL SalGtkFilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ return implgetDisplayDirectory();
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getFiles()
+{
+ // no member access => no mutex needed
+
+ uno::Sequence< OUString > aFiles = getSelectedFiles();
+ /*
+ The previous multiselection API design was completely broken
+ and unimplementable for some heterogeneous pseudo-URIs eg. search:
+ Thus crop unconditionally to a single selection.
+ */
+ aFiles.realloc (1);
+ return aFiles;
+}
+
+namespace
+{
+
+bool lcl_matchFilter( std::u16string_view rFilter, std::u16string_view rExt )
+{
+ const sal_Unicode cSep {';'};
+ size_t nIdx {0};
+
+ for (;;)
+ {
+ const size_t nBegin = rFilter.find(rExt, nIdx);
+
+ if (nBegin == std::u16string_view::npos) // not found
+ break;
+
+ // Let nIdx point to end of matched string, useful in order to
+ // check string boundaries and also for a possible next iteration
+ nIdx = nBegin + rExt.size();
+
+ // Check if the found occurrence is an exact match: right side
+ if (nIdx < rFilter.size() && rFilter[nIdx]!=cSep)
+ continue;
+
+ // Check if the found occurrence is an exact match: left side
+ if (nBegin>0 && rFilter[nBegin-1]!=cSep)
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) );
+ int nCount = g_slist_length( pPathList );
+#else
+ GListModel* pPathList = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(m_pDialog));
+ int nCount = g_list_model_get_n_items(pPathList);
+#endif
+
+ int nIndex = 0;
+
+ // get the current action setting
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action(
+ GTK_FILE_CHOOSER( m_pDialog ));
+
+ uno::Sequence< OUString > aSelectedFiles(nCount);
+ auto aSelectedFilesRange = asNonConstRange(aSelectedFiles);
+
+ // Convert to OOo
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for( GSList *pElem = pPathList; pElem; pElem = pElem->next)
+ {
+ gchar *pURI = static_cast<gchar*>(pElem->data);
+#else
+ while (gpointer pElem = g_list_model_get_item(pPathList, nIndex))
+ {
+ gchar *pURI = g_file_get_uri(static_cast<GFile*>(pElem));
+#endif
+
+ aSelectedFilesRange[ nIndex ] = uritounicode(pURI);
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString sFilterName;
+ sal_Int32 nTokenIndex = 0;
+ bool bExtensionTypedIn = false;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title = nullptr;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ if (title)
+ sFilterName = OUString( title, strlen( title ), RTL_TEXTENCODING_UTF8 );
+ else
+ sFilterName = OUString();
+ g_free (title);
+ }
+ else
+ {
+ if( aSelectedFiles[nIndex].indexOf('.') > 0 )
+ {
+ std::u16string_view sExtension;
+ nTokenIndex = 0;
+ do
+ sExtension = o3tl::getToken(aSelectedFiles[nIndex], 0, '.', nTokenIndex );
+ while( nTokenIndex >= 0 );
+
+ if( sExtension.size() >= 3 ) // 3 = typical/minimum extension length
+ {
+ OUString aNewFilter;
+ OUString aOldFilter = getCurrentFilter();
+ bool bChangeFilter = true;
+ if ( m_pFilterVector)
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if( lcl_matchFilter( filter.getFilter(), Concat2View(OUString::Concat("*.") + sExtension) ) )
+ {
+ if( aNewFilter.isEmpty() )
+ aNewFilter = filter.getTitle();
+
+ if( aOldFilter == filter.getTitle() )
+ bChangeFilter = false;
+
+ bExtensionTypedIn = true;
+ }
+ }
+ if( bChangeFilter && bExtensionTypedIn )
+ {
+ gchar* pCurrentName = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(m_pDialog));
+ setCurrentFilter( aNewFilter );
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDialog), pCurrentName);
+ g_free(pCurrentName);
+ }
+ }
+ }
+
+ GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog));
+ if (m_pPseudoFilter != filter)
+ {
+ const gchar* filtername = filter ? gtk_file_filter_get_name(filter) : nullptr;
+ if (filtername)
+ sFilterName = OUString(filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8);
+ else
+ sFilterName.clear();
+ }
+ else
+ sFilterName = m_aInitialFilter;
+ }
+
+ if (m_pFilterVector)
+ {
+ auto aVectorIter = ::std::find_if(
+ m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch(sFilterName) );
+
+ OUString aFilter;
+ if (aVectorIter != m_pFilterVector->end())
+ aFilter = aVectorIter->getFilter();
+
+ nTokenIndex = 0;
+ OUString sToken;
+ do
+ {
+ sToken = aFilter.getToken( 0, '.', nTokenIndex );
+
+ if ( sToken.lastIndexOf( ';' ) != -1 )
+ {
+ sToken = sToken.getToken(0, ';');
+ break;
+ }
+ }
+ while( nTokenIndex >= 0 );
+
+ if( !bExtensionTypedIn && ( sToken != "*" ) )
+ {
+ //if the filename does not already have the auto extension, stick it on
+ OUString sExtension = "." + sToken;
+ OUString &rBase = aSelectedFilesRange[nIndex];
+ sal_Int32 nExtensionIdx = rBase.getLength() - sExtension.getLength();
+ SAL_INFO(
+ "vcl.gtk",
+ "idx are " << rBase.lastIndexOf(sExtension) << " "
+ << nExtensionIdx);
+
+ if( rBase.lastIndexOf( sExtension ) != nExtensionIdx )
+ rBase += sExtension;
+ }
+ }
+
+ }
+
+ nIndex++;
+ g_free( pURI );
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( pPathList );
+#else
+ g_object_unref(pPathList);
+#endif
+
+ return aSelectedFiles;
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle )
+{
+ SolarMutexGuard g;
+
+ implsetTitle(rTitle);
+}
+
+sal_Int16 SAL_CALL SalGtkFilePicker::execute()
+{
+ SolarMutexGuard g;
+
+ if (!mbInitialized)
+ {
+ // tdf#144084 if not initialized default to FILEOPEN_SIMPLE
+ impl_initialize(nullptr, FILEOPEN_SIMPLE);
+ assert(mbInitialized);
+ }
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ SetFilters();
+
+ // tdf#84431 - set the filter after the corresponding widget is created
+ if ( !m_aCurrentFilter.isEmpty() )
+ SetCurFilter(m_aCurrentFilter);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_FolderChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed",
+ G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_FolderChange = 0;
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_SelectionChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed",
+ G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_SelectionChange = 0;
+#endif
+
+ int btn = GTK_RESPONSE_NO;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit(
+ awt::Toolkit::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ uno::Reference< frame::XDesktop > xDesktop(
+ frame::Desktop::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ while( GTK_RESPONSE_NO == btn )
+ {
+ btn = GTK_RESPONSE_YES; // we don't want to repeat unless user clicks NO for file save.
+
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ Sequence < OUString > aPathSeq = getFiles();
+ if( aPathSeq.getLength() == 1 )
+ {
+ OUString sFileName = aPathSeq[0];
+ if (::utl::UCBContentHelper::Exists(sFileName))
+ {
+ INetURLObject aFileObj(sFileName);
+
+ OString baseName(
+ OUStringToOString(
+ aFileObj.getName(
+ INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString aMsg(
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_PRIMARY ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString toReplace("$filename$"_ostr);
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ baseName
+ );
+
+ GtkWidget *dlg = gtk_message_dialog_new( nullptr,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ "%s",
+ aMsg.getStr()
+ );
+
+ GtkWidget* pOkButton = gtk_dialog_get_widget_for_response(GTK_DIALOG(dlg), GTK_RESPONSE_YES);
+ GtkStyleContext* pStyleContext = gtk_widget_get_style_context(pOkButton);
+ gtk_style_context_add_class(pStyleContext, "destructive-action");
+
+ sal_Int32 nSegmentCount = aFileObj.getSegmentCount();
+ if (nSegmentCount >= 2)
+ {
+ OString dirName(
+ OUStringToOString(
+ aFileObj.getName(
+ nSegmentCount-2,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+
+ aMsg =
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_SECONDARY ),
+ RTL_TEXTENCODING_UTF8
+ );
+
+ toReplace = "$dirname$"_ostr;
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ dirName
+ );
+
+ gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dlg ), "%s", aMsg.getStr() );
+ }
+
+ gtk_window_set_title( GTK_WINDOW( dlg ),
+ OUStringToOString(getResString(FILE_PICKER_TITLE_SAVE ),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(m_pDialog));
+ rtl::Reference<RunDialog> pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
+ btn = pAnotherDialog->run();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(dlg);
+#else
+ gtk_window_destroy(GTK_WINDOW(dlg));
+#endif
+ }
+
+ if( btn == GTK_RESPONSE_YES )
+ retVal = ExecutableDialogResults::OK;
+ }
+ }
+ else
+ retVal = ExecutableDialogResults::OK;
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+
+ case 1: //PLAY
+ {
+ FilePickerEvent evt;
+ evt.ElementId = PUSHBUTTON_PLAY;
+ impl_controlStateChanged( evt );
+ btn = GTK_RESPONSE_NO;
+ }
+ break;
+
+ default:
+ retVal = 0;
+ break;
+ }
+ }
+ gtk_widget_hide(m_pDialog);
+
+ if (mnHID_FolderChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_FolderChange);
+ if (mnHID_SelectionChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_SelectionChange);
+
+ return retVal;
+}
+
+// cf. offapi/com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.idl
+GtkWidget *SalGtkFilePicker::getWidget( sal_Int16 nControlId, GType *pType )
+{
+ GType tType = GTK_TYPE_CHECK_BUTTON; //prevent warning by initializing
+ GtkWidget *pWidget = nullptr;
+
+#define MAP_TOGGLE( elem ) \
+ case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
+ pWidget = m_pToggles[elem]; tType = GTK_TYPE_CHECK_BUTTON; \
+ break
+#define MAP_BUTTON( elem ) \
+ case CommonFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_EXT_BUTTON( elem ) \
+ case ExtendedFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_LIST( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem: \
+ pWidget = m_pLists[elem]; tType = GTK_TYPE_COMBO_BOX; \
+ break
+#define MAP_LIST_LABEL( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem##_LABEL: \
+ pWidget = m_pListLabels[elem]; tType = GTK_TYPE_LABEL; \
+ break
+
+ switch( nControlId )
+ {
+ MAP_TOGGLE( AUTOEXTENSION );
+ MAP_TOGGLE( PASSWORD );
+ MAP_TOGGLE( GPGENCRYPTION );
+ MAP_TOGGLE( FILTEROPTIONS );
+ MAP_TOGGLE( READONLY );
+ MAP_TOGGLE( LINK );
+ MAP_TOGGLE( PREVIEW );
+ MAP_TOGGLE( SELECTION );
+ MAP_BUTTON( OK );
+ MAP_BUTTON( CANCEL );
+ MAP_EXT_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 );
+ case CommonFilePickerElementIds::LISTBOX_FILTER_LABEL:
+ // the filter list in gtk typically is not labeled, but has a built-in
+ // tooltip to indicate what it does
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << nControlId);
+ break;
+ }
+#undef MAP
+
+ if( pType )
+ *pType = tType;
+ return pWidget;
+}
+
+// XFilePickerControlAccess functions
+
+static void HackWidthToFirst(GtkComboBox *pWidget)
+{
+ GtkRequisition requisition;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition);
+#else
+ gtk_widget_get_preferred_size(GTK_WIDGET(pWidget), &requisition, nullptr);
+#endif
+ gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1);
+}
+
+static void ComboBoxAppendText(GtkComboBox *pCombo, std::u16string_view rStr)
+{
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(gtk_combo_box_get_model(pCombo));
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+ gtk_list_store_append(pStore, &aIter);
+ gtk_list_store_set(pStore, &aIter, 0, aStr.getStr(), -1);
+}
+
+void SalGtkFilePicker::HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction, const uno::Any& rValue)
+{
+ switch (nControlAction)
+ {
+ case ControlActions::ADD_ITEM:
+ {
+ OUString sItem;
+ rValue >>= sItem;
+ ComboBoxAppendText(pWidget, sItem);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ break;
+ case ControlActions::ADD_ITEMS:
+ {
+ Sequence< OUString > aStringList;
+ rValue >>= aStringList;
+ for (const auto& rString : std::as_const(aStringList))
+ {
+ ComboBoxAppendText(pWidget, rString);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ }
+ break;
+ case ControlActions::DELETE_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ if(gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pStore), &aIter, nullptr, nPos))
+ gtk_list_store_remove(pStore, &aIter);
+ }
+ break;
+ case ControlActions::DELETE_ITEMS:
+ {
+ gtk_combo_box_set_active(pWidget, -1);
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ gtk_list_store_clear(pStore);
+ }
+ break;
+ case ControlActions::SET_SELECT_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+ gtk_combo_box_set_active(pWidget, nPos);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ //I think its best to make it insensitive unless there is the chance to
+ //actually select something from the list.
+ gint nItems = gtk_tree_model_iter_n_children(
+ gtk_combo_box_get_model(pWidget), nullptr);
+ gtk_widget_set_sensitive(GTK_WIDGET(pWidget), nItems > 1);
+}
+
+uno::Any SalGtkFilePicker::HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence< OUString > aItemList;
+
+ GtkTreeModel *pTree = gtk_combo_box_get_model(pWidget);
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter_first(pTree, &iter))
+ {
+ sal_Int32 nSize = gtk_tree_model_iter_n_children(
+ pTree, nullptr);
+
+ aItemList.realloc(nSize);
+ auto pItemList = aItemList.getArray();
+ for (sal_Int32 i=0; i < nSize; ++i)
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ pItemList[i] = OUString(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ g_free(item);
+ (void)gtk_tree_model_iter_next(pTree, &iter);
+ }
+ }
+ aAny <<= aItemList;
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM:
+ {
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(pWidget, &iter))
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ OUString sItem(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ aAny <<= sItem;
+ g_free(item);
+ }
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM_INDEX:
+ {
+ gint nActive = gtk_combo_box_get_active(pWidget);
+ aAny <<= static_cast< sal_Int32 >(nActive);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void SAL_CALL SalGtkFilePicker::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+ bool bChecked = false;
+ rValue >>= bChecked;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_active(GTK_CHECK_BUTTON(pWidget), bChecked);
+#else
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bChecked);
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ HandleSetListValue(GTK_COMBO_BOX(pWidget), nControlAction, rValue);
+ else
+ {
+ SAL_WARN( "vcl.gtk", "Can't set value on button / list " << nControlId << " " << nControlAction );
+ }
+}
+
+uno::Any SAL_CALL SalGtkFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ uno::Any aRetval;
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ aRetval <<= bool(gtk_check_button_get_active(GTK_CHECK_BUTTON(pWidget)));
+#else
+ aRetval <<= bool(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pWidget)));
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ aRetval = HandleGetListValue(GTK_COMBO_BOX(pWidget), nControlAction);
+ else
+ SAL_WARN( "vcl.gtk", "Can't get value on button / list " << nControlId << " " << nControlAction );
+
+ return aRetval;
+}
+
+void SAL_CALL SalGtkFilePicker::enableControl( sal_Int16 nControlId, sal_Bool bEnable )
+{
+ // skip this built-in one which is Enabled by default
+ if (nControlId == ExtendedFilePickerElementIds::LISTBOX_FILTER_SELECTOR && bEnable)
+ return;
+
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GtkWidget *pWidget;
+
+ if ( ( pWidget = getWidget( nControlId ) ) )
+ {
+ if( bEnable )
+ {
+ gtk_widget_set_sensitive( pWidget, true );
+ }
+ else
+ {
+ gtk_widget_set_sensitive( pWidget, false );
+ }
+ }
+ else
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId );
+}
+
+void SAL_CALL SalGtkFilePicker::setLabel( sal_Int16 nControlId, const OUString& rLabel )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ {
+ SAL_WARN_IF(nControlId != CommonFilePickerElementIds::LISTBOX_FILTER_LABEL,
+ "vcl.gtk", "Set label '" << rLabel << "' on unknown control " << nControlId);
+ return;
+ }
+
+ OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 );
+ if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ g_object_set( pWidget, "label", aTxt.getStr(),
+ "use_underline", true, nullptr );
+ else
+ SAL_WARN( "vcl.gtk", "Can't set label on list");
+}
+
+OUString SAL_CALL SalGtkFilePicker::getLabel( sal_Int16 nControlId )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ OString aTxt;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "Get label on unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ aTxt = gtk_button_get_label( GTK_BUTTON( pWidget ) );
+ else
+ SAL_WARN( "vcl.gtk", "Can't get label on list");
+
+ return OStringToOUString( aTxt, RTL_TEXTENCODING_UTF8 );
+}
+
+// XFilePreview functions
+
+uno::Sequence<sal_Int16> SAL_CALL SalGtkFilePicker::getSupportedImageFormats()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getSupportedImageFormats();
+ return uno::Sequence<sal_Int16>();
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getTargetColorDepth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getTargetColorDepth();
+ return 0;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableWidth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageWidth;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableHeight()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageHeight;
+}
+
+void SAL_CALL SalGtkFilePicker::setImage( sal_Int16 /*aImageFormat*/, const uno::Any& /*aImage*/ )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->setImage( aImageFormat, aImage );
+}
+
+void SalGtkFilePicker::implChangeType( GtkTreeSelection *selection )
+{
+ OUString aLabel = getResString( FILE_PICKER_FILE_TYPE );
+
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ aLabel += ": " +
+ OUString( title, strlen(title), RTL_TEXTENCODING_UTF8 );
+ g_free (title);
+ }
+ gtk_expander_set_label (GTK_EXPANDER (m_pFilterExpander),
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP )
+{
+ pobjFP->implChangeType(selection);
+}
+
+void SalGtkFilePicker::unselect_type()
+{
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView)));
+}
+
+void SalGtkFilePicker::expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP )
+{
+ if (gtk_expander_get_expanded(expander))
+ pobjFP->unselect_type();
+}
+
+void SalGtkFilePicker::filter_changed_cb( GtkFileChooser *, GParamSpec *,
+ SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ SAL_INFO( "vcl.gtk", "filter_changed, isn't it great " << pobjFP );
+ pobjFP->impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::folder_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "folder_changed, isn't it great " << pobjFP );
+ pobjFP->impl_directoryChanged( evt );
+}
+
+void SalGtkFilePicker::selection_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "selection_changed, isn't it great " << pobjFP );
+ pobjFP->impl_fileSelectionChanged( evt );
+}
+
+void SalGtkFilePicker::update_preview_cb( GtkFileChooser *file_chooser, SalGtkFilePicker* pobjFP )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool have_preview = false;
+
+ GtkWidget* preview = pobjFP->m_pPreview;
+ char* filename = gtk_file_chooser_get_preview_filename( file_chooser );
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pobjFP->m_pToggles[PREVIEW])) && filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR))
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(
+ filename,
+ g_PreviewImageWidth,
+ g_PreviewImageHeight, nullptr );
+
+ have_preview = ( pixbuf != nullptr );
+
+ gtk_image_set_from_pixbuf( GTK_IMAGE( preview ), pixbuf );
+ if( pixbuf )
+ g_object_unref( G_OBJECT( pixbuf ) );
+
+ }
+
+ gtk_file_chooser_set_preview_widget_active( file_chooser, have_preview );
+
+ if( filename )
+ g_free( filename );
+#else
+ (void)file_chooser;
+ (void)pobjFP;
+#endif
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::setShowState( sal_Bool bShowState )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->setShowState( bShowState );
+ if( bool(bShowState) != mbPreviewState )
+ {
+ if( bShowState )
+ {
+ // Show
+ if( !mHID_Preview )
+ {
+ mHID_Preview = g_signal_connect(
+ GTK_FILE_CHOOSER( m_pDialog ), "update-preview",
+ G_CALLBACK( update_preview_cb ), static_cast<gpointer>(this) );
+ }
+ gtk_widget_show( m_pPreview );
+ }
+ else
+ {
+ // Hide
+ gtk_widget_hide( m_pPreview );
+ }
+
+ // also emit the signal
+ g_signal_emit_by_name( G_OBJECT( m_pDialog ), "update-preview" );
+
+ mbPreviewState = bShowState;
+ }
+ return true;
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::getShowState()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return mbPreviewState;
+}
+
+GtkWidget* SalGtkPicker::GetParentWidget(const uno::Sequence<uno::Any>& rArguments)
+{
+ GtkWidget* pParentWidget = nullptr;
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (rArguments.getLength() > 1)
+ {
+ rArguments[1] >>= xParentWindow;
+ }
+
+ if (xParentWindow.is())
+ {
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get()))
+ pParentWidget = pGtkXWindow->getGtkWidget();
+ else
+ {
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysDepWin(xParentWindow, css::uno::UNO_QUERY);
+ if (xSysDepWin.is())
+ {
+ css::uno::Sequence<sal_Int8> aProcessIdent(16);
+ rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
+ uno::Any aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow tmp;
+ aAny >>= tmp;
+ pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle);
+ }
+ }
+ }
+
+ return pParentWidget;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFilePicker::initialize( const uno::Sequence<uno::Any>& aArguments )
+{
+ // parameter checking
+ uno::Any aAny;
+ if( !aArguments.hasElements() )
+ throw lang::IllegalArgumentException(
+ "no arguments",
+ static_cast<XFilePicker2*>( 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<XFilePicker2*>( this ), 1 );
+
+ sal_Int16 templateId = -1;
+ aAny >>= templateId;
+
+ impl_initialize(GetParentWidget(aArguments), templateId);
+}
+
+void SalGtkFilePicker::impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId)
+{
+ m_pParentWidget = pParentWidget;
+
+ GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ OString sOpen = getOpenText();
+ OString sSave = getSaveText();
+ const gchar *first_button_text;
+
+ SolarMutexGuard g;
+
+ // TODO: extract full semantic from
+ // svtools/source/filepicker/filepicker.cxx (getWinBits)
+ switch( templateId )
+ {
+ case FILEOPEN_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ break;
+ case FILESAVE_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ mbToggleVisibility[FILTEROPTIONS] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE; // SELECT_FOLDER ?
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[SELECTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbListVisibility[TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_ANCHOR] = true;
+ // TODO
+ break;
+ case FILEOPEN_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_READONLY_VERSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[READONLY] = true;
+ mbListVisibility[VERSION] = true;
+ break;
+ case FILEOPEN_LINK_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ // TODO
+ break;
+ case FILEOPEN_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ default:
+ throw lang::IllegalArgumentException(
+ "Unknown template",
+ static_cast< XFilePicker2* >( this ),
+ 1 );
+ }
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString aFilePickerTitle(getResString( FILE_PICKER_TITLE_SAVE ));
+ gtk_window_set_title ( GTK_WINDOW( m_pDialog ),
+ OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+
+ gtk_file_chooser_set_action( GTK_FILE_CHOOSER( m_pDialog ), eAction);
+ m_pButtons[CANCEL] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), getCancelText().getStr(), GTK_RESPONSE_CANCEL);
+ mbButtonVisibility[CANCEL] = true;
+
+ if (mbButtonVisibility[PLAY])
+ {
+ OString aPlay = OUStringToOString(getResString(PUSHBUTTON_PLAY), RTL_TEXTENCODING_UTF8);
+ m_pButtons[PLAY] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), aPlay.getStr(), 1);
+ }
+
+ m_pButtons[OK] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), first_button_text, GTK_RESPONSE_ACCEPT);
+ mbButtonVisibility[OK] = true;
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+ // Setup special flags
+ for( int nTVIndex = 0; nTVIndex < TOGGLE_LAST; nTVIndex++ )
+ {
+ if( mbToggleVisibility[nTVIndex] )
+ gtk_widget_show( m_pToggles[ nTVIndex ] );
+ }
+
+ for( int nTVIndex = 0; nTVIndex < LIST_LAST; nTVIndex++ )
+ {
+ if( mbListVisibility[nTVIndex] )
+ {
+ gtk_widget_set_sensitive( m_pLists[ nTVIndex ], false );
+ gtk_widget_show( m_pLists[ nTVIndex ] );
+ gtk_widget_show( m_pListLabels[ nTVIndex ] );
+ gtk_widget_show( m_pHBoxs[ nTVIndex ] );
+ }
+ }
+
+ mbInitialized = true;
+}
+
+void SalGtkFilePicker::preview_toggled_cb( GObject *cb, SalGtkFilePicker* pobjFP )
+{
+ if( pobjFP->mbToggleVisibility[PREVIEW] )
+ pobjFP->setShowState( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( cb ) ) );
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFilePicker::cancel()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+// Misc
+
+void SalGtkFilePicker::SetCurFilter( const OUString& rFilter )
+{
+ // Get all the filters already added
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel *filters = gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(m_pDialog));
+#else
+ GSList *filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int nIndex = 0;
+ while (gpointer pElem = g_list_model_get_item(filters, nIndex))
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>(pElem);
+ ++nIndex;
+#else
+ for( GSList *iter = filters; iter; iter = iter->next )
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>( iter->data );
+#endif
+ const gchar * filtername = gtk_file_filter_get_name( pFilter );
+ OUString sFilterName( filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8 );
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ if( aShrunkName == sFilterName )
+ {
+ SAL_INFO( "vcl.gtk", "actually setting " << filtername );
+ gtk_file_chooser_set_filter( GTK_FILE_CHOOSER( m_pDialog ), pFilter );
+ break;
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( filters );
+#else
+ g_object_unref (filters);
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+extern "C"
+{
+static gboolean
+case_insensitive_filter (const GtkFileFilterInfo *filter_info, gpointer data)
+{
+ bool bRetval = false;
+ const char *pFilter = static_cast<const char *>(data);
+
+ g_return_val_if_fail( data != nullptr, false );
+ g_return_val_if_fail( filter_info != nullptr, false );
+
+ if( !filter_info->uri )
+ return false;
+
+ const char *pExtn = strrchr( filter_info->uri, '.' );
+ if( !pExtn )
+ return false;
+ pExtn++;
+
+ if( !g_ascii_strcasecmp( pFilter, pExtn ) )
+ bRetval = true;
+
+ SAL_INFO( "vcl.gtk", "'" << filter_info->uri << "' match extn '" << pExtn << "' vs '" << pFilter << "' yields " << bRetval );
+
+ return bRetval;
+}
+}
+#endif
+
+GtkFileFilter* SalGtkFilePicker::implAddFilter( const OUString& rFilter, const OUString& rType )
+{
+ GtkFileFilter *filter = gtk_file_filter_new();
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ OString aFilterName = OUStringToOString( aShrunkName, RTL_TEXTENCODING_UTF8 );
+ gtk_file_filter_set_name( filter, aFilterName.getStr() );
+
+ OUStringBuffer aTokens;
+
+ bool bAllGlob = rType == "*.*" || rType == "*";
+ if (bAllGlob)
+ gtk_file_filter_add_pattern( filter, "*" );
+ else
+ {
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aToken = rType.getToken( 0, ';', nIndex );
+ // Assume all have the "*.<extn>" syntax
+ sal_Int32 nStarDot = aToken.lastIndexOf( "*." );
+ if (nStarDot >= 0)
+ aToken = aToken.copy( nStarDot + 2 );
+ if (!aToken.isEmpty())
+ {
+ if (!aTokens.isEmpty())
+ aTokens.append(",");
+ aTokens.append(aToken);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_filter_add_suffix(filter, aToken.toUtf8().getStr());
+#else
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI,
+ case_insensitive_filter,
+ g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ),
+ g_free );
+#endif
+
+ SAL_INFO( "vcl.gtk", "fustering with " << aToken );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ g_warning( "Duff filter token '%s'\n",
+ OUStringToOString(
+ o3tl::getToken(rType, 0, ';', nIndex ), RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+#endif
+ }
+ while( nIndex >= 0 );
+ }
+
+ gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( m_pDialog ), filter );
+
+ if (!bAllGlob)
+ {
+ GtkTreeIter iter;
+ gtk_list_store_append (m_pFilterStore, &iter);
+ gtk_list_store_set (m_pFilterStore, &iter,
+ 0, OUStringToOString(shrinkFilterName( rFilter, true ), RTL_TEXTENCODING_UTF8).getStr(),
+ 1, OUStringToOString(aTokens, RTL_TEXTENCODING_UTF8).getStr(),
+ 2, aFilterName.getStr(),
+ 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ return filter;
+}
+
+void SalGtkFilePicker::implAddFilterGroup( const Sequence< StringPair >& _rFilters )
+{
+ // Gtk+ has no filter group concept I think so ...
+ // implAddFilter( _rFilter, String() );
+ for( const auto& rSubFilter : _rFilters )
+ implAddFilter( rSubFilter.First, rSubFilter.Second );
+}
+
+void SalGtkFilePicker::SetFilters()
+{
+ if (m_aInitialFilter.isEmpty())
+ m_aInitialFilter = m_aCurrentFilter;
+
+ OUString sPseudoFilter;
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ std::set<OUString> aAllFormats;
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+ for( const auto& rSubFilter : std::as_const(aSubFilters) )
+ aAllFormats.insert(rSubFilter.Second);
+ }
+ else
+ aAllFormats.insert(filter.getFilter());
+ }
+ }
+ if (aAllFormats.size() > 1)
+ {
+ OUStringBuffer sAllFilter;
+ for (auto const& format : aAllFormats)
+ {
+ if (!sAllFilter.isEmpty())
+ sAllFilter.append(";");
+ sAllFilter.append(format);
+ }
+ sPseudoFilter = getResString(FILE_PICKER_ALLFORMATS);
+ m_pPseudoFilter = implAddFilter( sPseudoFilter, sAllFilter.makeStringAndClear() );
+ }
+ }
+
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+
+ implAddFilterGroup( aSubFilters );
+ }
+ else
+ {
+ // it's a single filter
+
+ implAddFilter( filter.getTitle(), filter.getFilter() );
+ }
+ }
+ }
+
+ // We always hide the expander now and depend on the user using the glob
+ // list, or type a filename suffix, to select a filter by inference.
+ gtk_widget_hide(m_pFilterExpander);
+
+ // set the default filter
+ if (!sPseudoFilter.isEmpty())
+ SetCurFilter( sPseudoFilter );
+ else if(!m_aCurrentFilter.isEmpty())
+ SetCurFilter( m_aCurrentFilter );
+
+ SAL_INFO( "vcl.gtk", "end setting filters");
+}
+
+SalGtkFilePicker::~SalGtkFilePicker()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ SolarMutexGuard g;
+
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ gtk_widget_destroy( m_pToggles[i] );
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ gtk_widget_destroy( m_pListLabels[i] );
+ gtk_widget_destroy( m_pLists[i] );
+ gtk_widget_destroy( m_pHBoxs[i] );
+ }
+
+ m_pFilterVector.reset();
+
+ gtk_widget_destroy( m_pVBox );
+#endif
+}
+
+uno::Reference< ui::dialogs::XFilePicker2 >
+GtkInstance::createFilePicker( const css::uno::Reference< css::uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFilePicker2 >(
+ new SalGtkFilePicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
new file mode 100644
index 0000000000..fdc701a203
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.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/compbase.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#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/uno/XComponentContext.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+
+#include <vector>
+#include <memory>
+#include <rtl/ustring.hxx>
+
+#include "SalGtkPicker.hxx"
+
+// Implementation class for the XFilePicker Interface
+
+struct FilterEntry;
+struct ElementEntry_Impl;
+
+
+typedef cppu::WeakComponentImplHelper<
+ css::ui::dialogs::XFilePickerControlAccess,
+ css::ui::dialogs::XFilePreview,
+ css::ui::dialogs::XFilePicker3,
+ css::lang::XInitialization
+ > SalGtkFilePicker_Base;
+
+class SalGtkFilePicker : public SalGtkPicker, public SalGtkFilePicker_Base
+{
+ public:
+
+ // constructor
+ SalGtkFilePicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // 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;
+
+ // XFilePicker2 functions
+
+ 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;
+
+ // 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 aImageFormat, 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 >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ // FilePicker Event functions
+
+ private:
+ SalGtkFilePicker( const SalGtkFilePicker& ) = delete;
+ SalGtkFilePicker& operator=( const SalGtkFilePicker& ) = delete;
+
+ bool FilterNameExists( const OUString& rTitle );
+ bool FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters );
+
+ void ensureFilterVector( const OUString& _rInitialCurrentFilter );
+
+ void impl_fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId);
+
+ private:
+ css::uno::Reference< css::ui::dialogs::XFilePickerListener >
+ m_xListener;
+ std::unique_ptr<std::vector<FilterEntry>> m_pFilterVector;
+ GtkWidget *m_pVBox;
+ GtkWidget *m_pFilterExpander;
+ GtkWidget *m_pFilterView;
+ GtkListStore *m_pFilterStore;
+
+ enum {
+ AUTOEXTENSION,
+ PASSWORD,
+ FILTEROPTIONS,
+ READONLY,
+ LINK,
+ PREVIEW,
+ SELECTION,
+ GPGENCRYPTION,
+ TOGGLE_LAST
+ };
+
+ GtkWidget *m_pToggles[ TOGGLE_LAST ];
+
+ bool mbToggleVisibility[TOGGLE_LAST];
+
+ enum {
+ OK,
+ CANCEL,
+ PLAY,
+ BUTTON_LAST };
+
+ GtkWidget *m_pButtons[ BUTTON_LAST ];
+
+ enum {
+ VERSION,
+ TEMPLATE,
+ IMAGE_TEMPLATE,
+ IMAGE_ANCHOR,
+ LIST_LAST
+ };
+
+ GtkWidget *m_pHBoxs[ LIST_LAST ];
+ GtkWidget *m_pLists[ LIST_LAST ];
+ GtkWidget *m_pListLabels[ LIST_LAST ];
+ bool mbListVisibility[ LIST_LAST ];
+ bool mbButtonVisibility[ BUTTON_LAST ];
+ gulong mnHID_FolderChange;
+ gulong mnHID_SelectionChange;
+
+ OUString m_aCurrentFilter;
+ OUString m_aInitialFilter;
+
+ bool bVersionWidthUnset;
+ bool mbPreviewState;
+ bool mbInitialized;
+ gulong mHID_Preview;
+ GtkWidget* m_pPreview;
+ GtkFileFilter* m_pPseudoFilter;
+ static constexpr sal_Int32 g_PreviewImageWidth = 256;
+ static constexpr sal_Int32 g_PreviewImageHeight = 256;
+
+ GtkWidget *getWidget( sal_Int16 nControlId, GType *pType = nullptr);
+
+ void SetCurFilter( const OUString& rFilter );
+ void SetFilters();
+ void UpdateFilterfromUI();
+
+ void implChangeType( GtkTreeSelection *selection );
+ GtkFileFilter * implAddFilter( const OUString& rFilter, const OUString& rType );
+ void implAddFilterGroup(
+ const css::uno::Sequence< css::beans::StringPair>& _rFilters );
+ void updateCurrentFilterFromName(const gchar* filtername);
+ void unselect_type();
+ void InitialMapping();
+
+ void HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction,
+ const css::uno::Any& rValue);
+ static css::uno::Any HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction);
+
+ static void expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP );
+ static void preview_toggled_cb( GObject *cb, SalGtkFilePicker *pobjFP );
+ static void filter_changed_cb( GtkFileChooser *file_chooser, GParamSpec *pspec, SalGtkFilePicker *pobjFP );
+ static void type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP );
+ static void folder_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void selection_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void update_preview_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void dialog_mapped_cb(GtkWidget *widget, SalGtkFilePicker *pobjFP);
+ public:
+ virtual ~SalGtkFilePicker() override;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
new file mode 100644
index 0000000000..57b6e7c53e
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
@@ -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 .
+ */
+
+#include <config_gio.h>
+
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <vcl/svapp.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include "SalGtkFolderPicker.hxx"
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// constructor
+
+SalGtkFolderPicker::SalGtkFolderPicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext )
+{
+ m_pDialog = gtk_file_chooser_dialog_new(
+ OUStringToOString( getResString( FOLDERPICKER_TITLE ), RTL_TEXTENCODING_UTF8 ).getStr(),
+ nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, getCancelText().getStr(), GTK_RESPONSE_CANCEL,
+ getOKText().getStr(), GTK_RESPONSE_ACCEPT, nullptr );
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+}
+
+void SAL_CALL SalGtkFolderPicker::setDisplayDirectory( const OUString& aDirectory )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri( aDirectory );
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_file(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pSelectedFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pSelectedFolder =
+ gtk_file_chooser_get_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+#endif
+ OUString aSelectedFolderName = uritounicode(pSelectedFolder);
+ g_free( pSelectedFolder );
+
+ return aSelectedFolderName;
+}
+
+void SAL_CALL SalGtkFolderPicker::setDescription( const OUString& /*rDescription*/ )
+{
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFolderPicker::setTitle( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+sal_Int16 SAL_CALL SalGtkFolderPicker::execute()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit =
+ awt::Toolkit::create(m_xContext);
+
+ uno::Reference<frame::XDesktop> xDesktop = frame::Desktop::create(m_xContext);
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ retVal = ExecutableDialogResults::OK;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+ default:
+ retVal = 0;
+ break;
+ }
+ gtk_widget_hide(m_pDialog);
+
+ return retVal;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFolderPicker::initialize(const uno::Sequence<uno::Any>& aArguments)
+{
+ m_pParentWidget = GetParentWidget(aArguments);
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFolderPicker::cancel()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+uno::Reference< ui::dialogs::XFolderPicker2 >
+GtkInstance::createFolderPicker( const uno::Reference< uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFolderPicker2 >(
+ new SalGtkFolderPicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
new file mode 100644
index 0000000000..5b1d8dcebf
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.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/.
+ *
+ * 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 <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include "SalGtkPicker.hxx"
+
+class SalGtkFolderPicker :
+ public SalGtkPicker,
+ public cppu::WeakImplHelper<css::ui::dialogs::XFolderPicker2, css::lang::XInitialization>
+{
+ public:
+
+ // constructor
+ SalGtkFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // 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;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ private:
+ SalGtkFolderPicker( const SalGtkFolderPicker& ) = delete;
+ SalGtkFolderPicker& operator=( const SalGtkFolderPicker& ) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
new file mode 100644
index 0000000000..9d07a11f76
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,295 @@
+/* -*- 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/frame/TerminationVetoException.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+
+#include <vcl/window.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::rtl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+OUString SalGtkPicker::uritounicode(const gchar* pIn) const
+{
+ if (!pIn)
+ return OUString();
+
+ OUString sURL( pIn, strlen(pIn),
+ RTL_TEXTENCODING_UTF8 );
+
+ INetURLObject aURL(sURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ // all the URLs are handled by office in UTF-8
+ // so the Gnome FP related URLs should be converted accordingly
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToInternal(sURL);
+ if( !aNewURL.isEmpty() )
+ sURL = aNewURL;
+ }
+ return sURL;
+}
+
+OString SalGtkPicker::unicodetouri(const OUString &rURL) const
+{
+ // all the URLs are handled by office in UTF-8 ( and encoded with "%xx" codes based on UTF-8 )
+ // so the Gnome FP related URLs should be converted accordingly
+ OString sURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ INetURLObject aURL(rURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToExternal(rURL);
+
+ if( !aNewURL.isEmpty() )
+ {
+ // At this point the URL should contain ascii characters only actually
+ sURL = OUStringToOString( aNewURL, osl_getThreadTextEncoding() );
+ }
+ }
+ return sURL;
+}
+
+extern "C"
+{
+ static gboolean canceldialog(RunDialog *pDialog)
+ {
+ SolarMutexGuard g;
+ pDialog->cancel();
+ return false;
+ }
+}
+
+GtkWindow* RunDialog::GetTransientFor()
+{
+ vcl::Window * pWindow = ::Application::GetActiveTopWindow();
+ if (!pWindow)
+ return nullptr;
+ GtkSalFrame *pFrame = dynamic_cast<GtkSalFrame*>(pWindow->ImplGetFrame());
+ if (!pFrame)
+ return nullptr;
+ return GTK_WINDOW(widget_get_toplevel(pFrame->getWindow()));
+}
+
+RunDialog::RunDialog(GtkWidget *pDialog, uno::Reference<awt::XExtendedToolkit> xToolkit,
+ uno::Reference<frame::XDesktop> xDesktop)
+ : cppu::WeakComponentImplHelper<awt::XTopWindowListener, frame::XTerminateListener>(maLock)
+ , mpDialog(pDialog)
+ , mbTerminateDesktop(false)
+ , mxToolkit(std::move(xToolkit))
+ , mxDesktop(std::move(xDesktop))
+{
+}
+
+RunDialog::~RunDialog()
+{
+ SolarMutexGuard g;
+
+ g_source_remove_by_user_data (this);
+}
+
+void SAL_CALL RunDialog::windowOpened(const css::lang::EventObject& e)
+{
+ SolarMutexGuard g;
+
+ //Don't popdown dialogs if a tooltip appears elsewhere, that's ok, but do pop down
+ //if another dialog/frame is launched.
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(e.Source, css::uno::UNO_QUERY);
+ if (xAccessible.is())
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(xAccessible->getAccessibleContext());
+ if (xContext.is() && xContext->getAccessibleRole() == css::accessibility::AccessibleRole::TOOL_TIP)
+ {
+ return;
+ }
+ }
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+}
+
+void SAL_CALL RunDialog::queryTermination( const css::lang::EventObject& )
+{
+ SolarMutexGuard g;
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+
+ mbTerminateDesktop = true;
+
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL RunDialog::notifyTermination( const css::lang::EventObject& )
+{
+}
+
+void RunDialog::cancel()
+{
+ gtk_dialog_response( GTK_DIALOG( mpDialog ), GTK_RESPONSE_CANCEL );
+ gtk_widget_hide( mpDialog );
+}
+
+namespace
+{
+ class ExecuteInfo
+ {
+ private:
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ public:
+ ExecuteInfo(css::uno::Reference<css::frame::XDesktop> xDesktop)
+ : mxDesktop(std::move(xDesktop))
+ {
+ }
+ void terminate()
+ {
+ mxDesktop->terminate();
+ }
+ };
+}
+
+gint RunDialog::run()
+{
+ if (mxToolkit.is())
+ mxToolkit->addTopWindowListener(this);
+
+ mxDesktop->addTerminateListener(this);
+
+ // [Inc/Dec]ModalCount on parent frame so it knows it is in modal mode
+ GtkWindow* pParent = gtk_window_get_transient_for(GTK_WINDOW(mpDialog));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
+ VclPtr<vcl::Window> xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ if (xFrameWindow)
+ {
+ xFrameWindow->IncModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
+ }
+
+ gint nStatus = gtk_dialog_run(GTK_DIALOG(mpDialog));
+
+ if (xFrameWindow)
+ {
+ xFrameWindow->DecModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+
+ mxDesktop->removeTerminateListener(this);
+
+ if (mxToolkit.is())
+ mxToolkit->removeTopWindowListener(this);
+
+ if (mbTerminateDesktop)
+ {
+ ExecuteInfo* pExecuteInfo = new ExecuteInfo(mxDesktop);
+ Application::PostUserEvent(LINK(nullptr, RunDialog, TerminateDesktop), pExecuteInfo);
+ }
+
+ return nStatus;
+}
+
+IMPL_STATIC_LINK(RunDialog, TerminateDesktop, void*, p, void)
+{
+ ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p);
+ pExecuteInfo->terminate();
+ delete pExecuteInfo;
+}
+
+SalGtkPicker::SalGtkPicker( uno::Reference<uno::XComponentContext> xContext )
+ : m_pParentWidget(nullptr)
+ , m_pDialog(nullptr)
+ , m_xContext(std::move(xContext))
+{
+}
+
+SalGtkPicker::~SalGtkPicker()
+{
+ SolarMutexGuard g;
+
+ if (m_pDialog)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(m_pDialog);
+#else
+ gtk_window_destroy(GTK_WINDOW(m_pDialog));
+#endif
+ }
+}
+
+void SalGtkPicker::implsetDisplayDirectory( const OUString& aDirectory )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri(aDirectory);
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SalGtkPicker::implgetDisplayDirectory()
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+void SalGtkPicker::implsetTitle( std::u16string_view aTitle )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
new file mode 100644
index 0000000000..cb11445dae
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
@@ -0,0 +1,144 @@
+/* -*- 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 <osl/mutex.hxx>
+#include <tools/link.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/awt/XTopWindowListener.hpp>
+#include <com/sun/star/awt/XExtendedToolkit.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.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_PRIMARY 505
+#define FILE_PICKER_OVERWRITE_SECONDARY 506
+#define FILE_PICKER_ALLFORMATS 507
+
+class SalGtkPicker
+{
+ public:
+ SalGtkPicker( css::uno::Reference<css::uno::XComponentContext> xContext );
+ virtual ~SalGtkPicker();
+ protected:
+ osl::Mutex m_rbHelperMtx;
+ GtkWidget *m_pParentWidget;
+ GtkWidget *m_pDialog;
+ protected:
+ /// @throws css::uno::RuntimeException
+ void implsetTitle( std::u16string_view aTitle );
+
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ void implsetDisplayDirectory( const OUString& rDirectory );
+
+ /// @throws css::uno::RuntimeException
+ OUString implgetDisplayDirectory( );
+ OUString uritounicode(const gchar *pIn) const;
+ OString unicodetouri(const OUString &rURL) const;
+
+ // to instantiate own services
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ static GtkWidget* GetParentWidget(const css::uno::Sequence<css::uno::Any>& rArguments);
+
+ static OUString getResString( sal_Int32 aId );
+};
+
+//Run the Gtk Dialog. Watch for any "new windows" created while we're
+//executing and consider that a CANCEL event to avoid e.g. "file cannot be opened"
+//modal dialogs and this one getting locked if some other API call causes this
+//to happen while we're opened waiting for user input, e.g.
+//https://bugzilla.redhat.com/show_bug.cgi?id=441108
+class RunDialog :
+ public cppu::WeakComponentImplHelper<
+ css::awt::XTopWindowListener,
+ css::frame::XTerminateListener >
+{
+private:
+ osl::Mutex maLock;
+ GtkWidget *mpDialog;
+ bool mbTerminateDesktop;
+ css::uno::Reference<css::awt::XExtendedToolkit> mxToolkit;
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ DECL_STATIC_LINK(RunDialog, TerminateDesktop, void*, void);
+public:
+
+ // XTopWindowListener
+ using cppu::WeakComponentImplHelperBase::disposing;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowOpened( const css::lang::EventObject& e ) override;
+ virtual void SAL_CALL windowClosing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowClosed( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowMinimized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowNormalized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowActivated( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowDeactivated( const css::lang::EventObject& ) override {}
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+public:
+ RunDialog(GtkWidget *pDialog,
+ css::uno::Reference<css::awt::XExtendedToolkit> xToolkit,
+ css::uno::Reference<css::frame::XDesktop> xDesktop);
+ virtual ~RunDialog() override;
+ gint run();
+ void cancel();
+ static GtkWindow* GetTransientFor();
+};
+
+inline OString getCancelText()
+{
+ return VclResId(SV_BUTTONTEXT_CANCEL).replace('~', '_').toUtf8();
+}
+
+inline OString getOpenText()
+{
+ return VclResId(SV_BUTTONTEXT_OPEN).replace('~', '_').toUtf8();
+}
+
+inline OString getSaveText()
+{
+ return VclResId(SV_BUTTONTEXT_SAVE).replace('~', '_').toUtf8();
+}
+
+inline OString getOKText()
+{
+ return VclResId(SV_BUTTONTEXT_OK).replace('~', '_').toUtf8();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/eventnotification.hxx b/vcl/unx/gtk3/fpicker/eventnotification.hxx
new file mode 100644
index 0000000000..214da6da9b
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/eventnotification.hxx
@@ -0,0 +1,41 @@
+/* -*- 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/uno/XInterface.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+// encapsulate a filepicker event
+// notification, because there are
+// two types of filepicker notifications
+// with and without parameter
+// this is an application of the
+// "command" pattern see GoF
+
+class CEventNotification
+{
+public:
+ virtual ~CEventNotification(){};
+
+ virtual void SAL_CALL notifyEventListener(css::uno::Reference<css::uno::XInterface> xListener)
+ = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/resourceprovider.cxx b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
new file mode 100644
index 0000000000..fa90b81266
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
@@ -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 <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+
+#include <fpicker/strings.hrc>
+#include <fpicker/fpsofficeResMgr.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+
+// translate control ids to resource ids
+
+const struct
+{
+ sal_Int32 ctrlId;
+ TranslateId resId;
+} CtrlIdToResIdTable[] = {
+ { CHECKBOX_AUTOEXTENSION, STR_SVT_FILEPICKER_AUTO_EXTENSION },
+ { CHECKBOX_PASSWORD, STR_SVT_FILEPICKER_PASSWORD },
+ { CHECKBOX_GPGENCRYPTION, STR_SVT_FILEPICKER_GPGENCRYPT },
+ { 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_PRIMARY, STR_SVT_ALREADYEXISTOVERWRITE },
+ { FILE_PICKER_OVERWRITE_SECONDARY, STR_SVT_ALREADYEXISTOVERWRITE_SECONDARY },
+ { FILE_PICKER_ALLFORMATS, STR_SVT_ALLFORMATS },
+ { FILE_PICKER_TITLE_OPEN, STR_FILEDLG_OPEN },
+ { FILE_PICKER_TITLE_SAVE, STR_FILEDLG_SAVE },
+ { FILE_PICKER_FILE_TYPE, STR_FILEDLG_TYPE }
+};
+
+static TranslateId CtrlIdToResId( sal_Int32 aControlId )
+{
+ for (auto & i : CtrlIdToResIdTable)
+ {
+ if ( i.ctrlId == aControlId )
+ return i.resId;
+ }
+ return {};
+}
+
+OUString SalGtkPicker::getResString( sal_Int32 aId )
+{
+ OUString aResString;
+ // translate the control id to a resource id
+ TranslateId pResId = CtrlIdToResId( aId );
+ if (pResId)
+ aResString = FpsResId(pResId);
+ return aResString.replace('~', '_');
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */