diff options
Diffstat (limited to 'vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx')
-rw-r--r-- | vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx | 1991 |
1 files changed, 1991 insertions, 0 deletions
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx new file mode 100644 index 000000000..1015fcf67 --- /dev/null +++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx @@ -0,0 +1,1991 @@ +/* -*- 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 . + */ + +#ifdef AIX +#define _LINUX_SOURCE_COMPAT +#include <sys/timer.h> +#undef _LINUX_SOURCE_COMPAT +#endif + +#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 <vcl/svapp.hxx> + +#include <tools/urlobj.hxx> + +#include <algorithm> +#include <set> +#include <string.h> + +#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; + +void SalGtkFilePicker::dialog_mapped_cb(GtkWidget *, SalGtkFilePicker *pobjFP) +{ + pobjFP->InitialMapping(); +} + +void SalGtkFilePicker::InitialMapping() +{ + if (!mbPreviewState ) + { + gtk_widget_hide( m_pPreview ); + gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false); + } + gtk_widget_set_size_request (m_pPreview, -1, -1); +} + +SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) : + SalGtkPicker( xContext ), + SalGtkFilePicker_Base( m_rbHelperMtx ), + m_pParentWidget ( nullptr ), + m_pVBox ( nullptr ), + mnHID_FolderChange( 0 ), + mnHID_SelectionChange( 0 ), + bVersionWidthUnset( false ), + mbPreviewState( 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_pAligns[i] = nullptr; + m_pLists[i] = nullptr; + m_pListLabels[i] = nullptr; + mbListVisibility[i] = false; + } + + OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN ); + + m_pDialog = gtk_file_chooser_dialog_new( + OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr(), + nullptr, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + nullptr ); + + gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT ); + +#if ENABLE_GIO + gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false ); +#endif + gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false ); + + m_pVBox = gtk_vbox_new( false, 0 ); + + // We don't want clickable items to have a huge hit-area + GtkWidget *pHBox = gtk_hbox_new( false, 0 ); + GtkWidget *pThinVBox = gtk_vbox_new( false, 0 ); + + gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0); + gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0); + 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; + } + + gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 ); + } + + for( i = 0; i < LIST_LAST; i++ ) + { + m_pHBoxs[i] = gtk_hbox_new( false, 0 ); + + m_pAligns[i] = gtk_alignment_new(0, 0, 0, 1); + + 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; + } + + gtk_container_add( GTK_CONTAINER( m_pAligns[i]), m_pLists[i] ); + gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pAligns[i], false, false, 0 ); + gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 ); + gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] ); + gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 ); + + gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 ); + } + + aLabel = getResString( FILE_PICKER_FILE_TYPE ); + m_pFilterExpander = gtk_expander_new_with_mnemonic( + OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr()); + + gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 ); + + GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window); + 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); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(m_pFilterView), true); + + 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); + } + + gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView); + gtk_widget_show (m_pFilterView); + + gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox ); + + m_pPreview = gtk_image_new(); + gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview ); + + 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); + + gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true); +} + +// 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 ); +} + +struct FilterEntry +{ +protected: + OUString m_sTitle; + OUString m_sFilter; + + css::uno::Sequence< css::beans::StringPair > m_aSubFilters; + +public: + FilterEntry( const OUString& _rTitle, const OUString& _rFilter ) + :m_sTitle( _rTitle ) + ,m_sFilter( _rFilter ) + { + } + + 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; +} + +static bool +isFilterString( const OUString &rFilterString, const char *pMatch ) +{ + sal_Int32 nIndex = 0; + OUString aToken; + bool bIsFilter = true; + + OUString aMatch(OUString::createFromAscii(pMatch)); + + do + { + aToken = rFilterString.getToken( 0, ';', nIndex ); + if( !aToken.match( 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.copy( i + 1, nBracketLen - 1 ), "*." ) ) + aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() ); + else if (bAllowNoStar) + { + if( isFilterString( rFilterName.copy( i + 1, nBracketLen - 1 ), ".") ) + aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() ); + } + } + } + + return aRealName; +} + +static void +dialog_remove_buttons(GtkWidget *pActionArea) +{ + GtkContainer *pContainer = GTK_CONTAINER( pActionArea ); + + g_return_if_fail( pContainer != nullptr ); + + GList *pChildren = gtk_container_get_children( pContainer ); + + for( GList *p = pChildren; p; p = p->next ) + { + GtkWidget *pWidget = GTK_WIDGET( p->data ); + if ( GTK_IS_BUTTON( pWidget ) ) + gtk_widget_destroy( pWidget ); + } + + g_list_free( pChildren ); +} + +static void +dialog_remove_buttons( GtkDialog *pDialog ) +{ + g_return_if_fail( GTK_IS_DIALOG( pDialog ) ); + + GtkWidget *pHeaderBar = gtk_dialog_get_header_bar(pDialog); + if( pHeaderBar != nullptr ) + dialog_remove_buttons( pHeaderBar ); + else + dialog_remove_buttons(gtk_dialog_get_action_area(pDialog)); +} + +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( const OUString& rFilter, const OUString& rExt ) +{ + const sal_Unicode cSep {';'}; + sal_Int32 nIdx {0}; + + for (;;) + { + const sal_Int32 nBegin = rFilter.indexOf(rExt, nIdx); + + if (nBegin<0) // 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.getLength(); + + // Check if the found occurrence is an exact match: right side + if (nIdx<rFilter.getLength() && 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 ); + + GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) ); + + int nCount = g_slist_length( pPathList ); + 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); + + // Convert to OOo + for( GSList *pElem = pPathList; pElem; pElem = pElem->next) + { + gchar *pURI = static_cast<gchar*>(pElem->data); + aSelectedFiles[ 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 ) + { + OUString sExtension; + nTokenIndex = 0; + do + sExtension = aSelectedFiles[nIndex].getToken( 0, '.', nTokenIndex ); + while( nTokenIndex >= 0 ); + + if( sExtension.getLength() >= 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(), "*." + 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 = aSelectedFiles[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 ); + } + + g_slist_free( pPathList ); + + return aSelectedFiles; +} + +// XExecutableDialog functions + +void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle ) +{ + SolarMutexGuard g; + + implsetTitle(rTitle); +} + +sal_Int16 SAL_CALL SalGtkFilePicker::execute() +{ + SolarMutexGuard g; + + 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); + + mnHID_FolderChange = + g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed", + G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) ); + + mnHID_SelectionChange = + g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed", + G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) ); + + 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); + RunDialog* pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop); + uno::Reference < awt::XTopWindowListener > xLifeCycle(pRunDialog); + 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 ) + { + OString sFileName = unicodetouri( aPathSeq[0] ); + gchar *gFileName = g_filename_from_uri ( sFileName.getStr(), nullptr, nullptr ); + if( g_file_test( gFileName, G_FILE_TEST_IS_REGULAR ) ) + { + INetURLObject aFileObj( OStringToOUString(sFileName, RTL_TEXTENCODING_UTF8) ); + + 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$"); + + 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() + ); + + 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$"; + + 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)); + RunDialog* pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop); + uno::Reference < awt::XTopWindowListener > xAnotherLifeCycle(pAnotherDialog); + btn = pAnotherDialog->run(); + + gtk_widget_destroy( dlg ); + } + g_free (gFileName); + + 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_TOGGLE_BUTTON; //prevent warning by initializing + GtkWidget *pWidget = nullptr; + +#define MAP_TOGGLE( elem ) \ + case ExtendedFilePickerElementIds::CHECKBOX_##elem: \ + pWidget = m_pToggles[elem]; tType = GTK_TYPE_TOGGLE_BUTTON; \ + break +#define MAP_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( PLAY ); + MAP_LIST( VERSION ); + MAP_LIST( TEMPLATE ); + MAP_LIST( IMAGE_TEMPLATE ); + MAP_LIST( IMAGE_ANCHOR ); + MAP_LIST_LABEL( VERSION ); + MAP_LIST_LABEL( TEMPLATE ); + MAP_LIST_LABEL( IMAGE_TEMPLATE ); + MAP_LIST_LABEL( IMAGE_ANCHOR ); + default: + SAL_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; + gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition); + gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1); +} + +static void ComboBoxAppendText(GtkComboBox *pCombo, const OUString &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); + for (sal_Int32 i=0; i < nSize; ++i) + { + gchar *item; + gtk_tree_model_get(gtk_combo_box_get_model(pWidget), + &iter, 0, &item, -1); + aItemList[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_TOGGLE_BUTTON ) + { + bool bChecked = false; + rValue >>= bChecked; + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pWidget ), bChecked ); + } + 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_TOGGLE_BUTTON ) + aRetval <<= bool( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( pWidget ) ) ); + 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 ) +{ + 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( "vcl.gtk", "Set label on unknown control " << nControlId); + return; + } + + OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 ); + if (nControlId == ExtendedFilePickerElementIds::PUSHBUTTON_PLAY) + { +#ifdef GTK_STOCK_MEDIA_PLAY + if (msPlayLabel.isEmpty()) + msPlayLabel = rLabel; + if (msPlayLabel == rLabel) + gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_PLAY); + else + gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_STOP); +#else + gtk_button_set_label(GTK_BUTTON(pWidget), aTxt.getStr()); +#endif + } + else if( tType == GTK_TYPE_TOGGLE_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_TOGGLE_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 ) +{ + 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 ); +} + +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; +} + +// 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; + + css::uno::Reference<css::awt::XWindow> xParentWindow; + if (aArguments.getLength() > 1) + { + aArguments[1] >>= xParentWindow; + } + + if (xParentWindow.is()) + { + if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get())) + m_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())); + aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW); + css::awt::SystemDependentXWindow tmp; + aAny >>= tmp; + m_pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle); + } + } + } + + GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + const gchar *first_button_text = GTK_STOCK_OPEN; + + 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 = GTK_STOCK_OPEN; + break; + case FILESAVE_SIMPLE: + eAction = GTK_FILE_CHOOSER_ACTION_SAVE; + first_button_text = GTK_STOCK_SAVE; + break; + case FILESAVE_AUTOEXTENSION_PASSWORD: + eAction = GTK_FILE_CHOOSER_ACTION_SAVE; + first_button_text = GTK_STOCK_SAVE; + mbToggleVisibility[PASSWORD] = true; + mbToggleVisibility[GPGENCRYPTION] = true; + // TODO + break; + case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS: + eAction = GTK_FILE_CHOOSER_ACTION_SAVE; + first_button_text = GTK_STOCK_SAVE; + 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 = GTK_STOCK_SAVE; + mbToggleVisibility[SELECTION] = true; + // TODO + break; + case FILESAVE_AUTOEXTENSION_TEMPLATE: + eAction = GTK_FILE_CHOOSER_ACTION_SAVE; + first_button_text = GTK_STOCK_SAVE; + mbListVisibility[TEMPLATE] = true; + // TODO + break; + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + 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 = GTK_STOCK_OPEN; + mbToggleVisibility[LINK] = true; + mbToggleVisibility[PREVIEW] = true; + mbListVisibility[IMAGE_ANCHOR] = true; + // TODO + break; + case FILEOPEN_PLAY: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + mbButtonVisibility[PLAY] = true; + // TODO + break; + case FILEOPEN_LINK_PLAY: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + mbToggleVisibility[LINK] = true; + mbButtonVisibility[PLAY] = true; + // TODO + break; + case FILEOPEN_READONLY_VERSION: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + mbToggleVisibility[READONLY] = true; + mbListVisibility[VERSION] = true; + break; + case FILEOPEN_LINK_PREVIEW: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + mbToggleVisibility[LINK] = true; + mbToggleVisibility[PREVIEW] = true; + // TODO + break; + case FILESAVE_AUTOEXTENSION: + eAction = GTK_FILE_CHOOSER_ACTION_SAVE; + first_button_text = GTK_STOCK_SAVE; + // TODO + break; + case FILEOPEN_PREVIEW: + eAction = GTK_FILE_CHOOSER_ACTION_OPEN; + first_button_text = GTK_STOCK_OPEN; + 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); + dialog_remove_buttons( GTK_DIALOG( m_pDialog ) ); + gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL ); + for( int nTVIndex = 0; nTVIndex < BUTTON_LAST; nTVIndex++ ) + { + if( mbButtonVisibility[nTVIndex] ) + { +#ifdef GTK_STOCK_MEDIA_PLAY + m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_MEDIA_PLAY, 1 ); +#else + OString aPlay = OUStringToOString( getResString( PUSHBUTTON_PLAY ), RTL_TEXTENCODING_UTF8 ); + m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), aPlay.getStr(), 1 ); +#endif + } + } + gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), first_button_text, GTK_RESPONSE_ACCEPT ); + + 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_pAligns[ nTVIndex ] ); + gtk_widget_show( m_pHBoxs[ nTVIndex ] ); + } + } +} + +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 + GSList *filters = gtk_file_chooser_list_filters ( GTK_FILE_CHOOSER( m_pDialog ) ); + bool bFound = false; + + for( GSList *iter = filters; !bFound && iter; iter = iter->next ) + { + GtkFileFilter* pFilter = static_cast<GtkFileFilter *>( iter->data ); + 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 ); + bFound = true; + } + } + + g_slist_free( filters ); +} + +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; +} +} + +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); + gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI, + case_insensitive_filter, + g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ), + g_free ); + + SAL_INFO( "vcl.gtk", "fustering with " << aToken ); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + g_warning( "Duff filter token '%s'\n", + OUStringToOString( + rType.getToken( 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8).getStr(), + 2, aFilterName.getStr(), + 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(), + -1); + } + return filter; +} + +void SalGtkFilePicker::implAddFilterGroup( const OUString& /*_rFilter*/, 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( filter.getTitle(), 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() +{ + 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_pAligns[i] ); //m_pAligns[i] owns m_pLists[i] + gtk_widget_destroy( m_pHBoxs[i] ); + } + + m_pFilterVector.reset(); + + gtk_widget_destroy( m_pVBox ); +} + +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: */ |