tdf#165917 Improve Export directory pre-selection

When calling an Export dialog (PDF, epub, ...) the following
folder will be preselected:

For stored documents:
* Current document directory
* If another directory was chosen for the export,
  that directory will be preselected for subsequent exports
  (stored only during runtime - per document)

For unstored documents:
* The last used export directory is restored (last used in unsaved doc)

Change-Id: I97595d164cf1d3604166c38aa2a5ed31be56f113
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184062
Reviewed-by: Samuel Mehrbrodt <samuel.mehrbrodt@allotropia.de>
Tested-by: Jenkins
This commit is contained in:
Samuel Mehrbrodt 2025-04-11 18:21:37 +02:00
parent 7fac8458e3
commit 3fa39a4dad
15 changed files with 82 additions and 87 deletions

View File

@ -3687,7 +3687,6 @@ SfxApplication::Invalidate(unsigned short)
SfxApplication::MacroOrganizer(weld::Window*, com::sun::star::uno::Reference<com::sun::star::frame::XFrame> const&, short)
SfxApplication::RegisterInterface(SfxModule const*)
SfxApplication::ReleaseIndex(unsigned short)
SfxApplication::ResetLastDir()
SfxApplication::SaveBasicAndDialogContainer() const
SfxApplication::SfxApplication()
SfxApplication::loadBrandSvg(char const*, BitmapEx&, int)

View File

@ -464,9 +464,6 @@ void SvxPathTabPage::ChangeCurrentEntry( const OUString& _rFolder )
// will be used for the next open dialog.
SvtViewOptions aDlgOpt( EViewType::Dialog, IODLG_CONFIGNAME );
aDlgOpt.Delete();
// Reset also last used dir in the sfx application instance
SfxApplication *pSfxApp = SfxGetpApp();
pSfxApp->ResetLastDir();
}
}

View File

@ -167,7 +167,6 @@ public:
SAL_DLLPRIVATE virtual void Invalidate(sal_uInt16 nId = 0) override;
void NotifyEvent(const SfxEventHint& rEvent, bool bSynchron = true );
bool IsDowning() const;
void ResetLastDir();
SAL_DLLPRIVATE SfxDispatcher* GetAppDispatcher_Impl();
SAL_DLLPRIVATE SfxDispatcher* GetDispatcher_Impl();
@ -206,8 +205,6 @@ public:
SAL_DLLPRIVATE static void OfaState_Impl(SfxItemSet &);
SAL_DLLPRIVATE void SetProgress_Impl(SfxProgress *);
SAL_DLLPRIVATE const OUString& GetLastDir_Impl() const;
SAL_DLLPRIVATE void SetLastDir_Impl( const OUString & );
SAL_DLLPRIVATE static void Registrations_Impl();
SAL_DLLPRIVATE SfxWorkWindow* GetWorkWindow_Impl(const SfxViewFrame *pFrame) const;

View File

@ -301,6 +301,7 @@ class SvxZoomItem;
#define SID_GPGSIGN TypedWhichId<SfxBoolItem>(SID_SFX_START + 1748)
#define FN_INVERT_BACKGROUND (SID_SFX_START + 1749)
#define SID_MACROMANAGER (SID_SFX_START + 1750)
#define SID_EXPORTDIRECTORY TypedWhichId<SfxStringItem>(SID_SFX_START + 1751)
// SID_SFX_free_END (SID_SFX_START + 3999)
#define SID_OPEN_NEW_VIEW TypedWhichId<SfxBoolItem>(SID_SFX_START + 520)

View File

@ -102,6 +102,7 @@ class UNOTOOLS_DLLPUBLIC MediaDescriptor : public comphelper::SequenceAsHashMap
static constexpr OUString PROP_VIEWONLY = u"ViewOnly"_ustr;
static constexpr OUString PROP_DOCUMENTBASEURL = u"DocumentBaseURL"_ustr;
static constexpr OUString PROP_SUGGESTEDSAVEASNAME = u"SuggestedSaveAsName"_ustr;
static constexpr OUString PROP_EXPORTDIRECTORY = u"ExportDirectory"_ustr;
static constexpr OUString PROP_AUTOSAVEEVENT = u"AutoSaveEvent"_ustr;
// interface

View File

@ -449,6 +449,11 @@ service MediaDescriptor
*/
[optional,property] string SuggestedSaveAsName;
/** Directory to be used when exporting (to PDF, epub, ...).
Defaults to the current document directory.
*/
[optional,property] string ExportDirectory;
/** name of the template instead of the URL
<p>

View File

@ -28,7 +28,7 @@
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XContainerQuery.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/frame/XModel2.hpp>
#include <com/sun/star/frame/XModuleManager2.hpp>
#include <comphelper/sequenceashashmap.hxx>
@ -82,7 +82,7 @@ public:
SfxStoringHelper();
bool GUIStoreModel(
const css::uno::Reference< css::frame::XModel >& xModel,
const css::uno::Reference< css::frame::XModel2 >& xModel,
std::u16string_view aSlotName,
css::uno::Sequence< css::beans::PropertyValue >& aArgsSequence,
bool bPreselectPassword,
@ -95,18 +95,18 @@ public:
static void SetDocInfoState(
const css::uno::Reference< css::frame::XModel >& xModel,
const css::uno::Reference< css::frame::XModel2 >& xModel,
const css::uno::Reference< css::document::XDocumentProperties>& i_xOldDocInfo );
static bool WarnUnacceptableFormat(
const css::uno::Reference< css::frame::XModel >& xModel,
const css::uno::Reference< css::frame::XModel2 >& xModel,
std::u16string_view aOldUIName,
std::u16string_view aExtension,
const OUString& aDefExtension,
bool rDefaultIsAlien );
static css::uno::Reference<css::awt::XWindow> GetModelXWindow(const css::uno::Reference<css::frame::XModel>& rModel);
static weld::Window* GetModelWindow( const css::uno::Reference< css::frame::XModel >& xModel );
static css::uno::Reference<css::awt::XWindow> GetModelXWindow(const css::uno::Reference<css::frame::XModel2>& rModel);
static weld::Window* GetModelWindow( const css::uno::Reference< css::frame::XModel2 >& xModel );
};

View File

@ -183,49 +183,6 @@ SfxApplication::~SfxApplication()
}
const OUString& SfxApplication::GetLastDir_Impl() const
/* [Description]
Internal method by which the last set directory with the method
<SfxApplication::SetLastDir_Impl()> in SFX is returned.
This is usually the most recently addressed by the
SfxFileDialog directory.
[Cross-reference]
<SfxApplication::SetLastDir_Impl()>
*/
{
return pImpl->aLastDir;
}
void SfxApplication::SetLastDir_Impl
(
const OUString& rNewDir /* Complete directory path as a string */
)
/* [Description]
Internal Method, by which a directory path is set that was last addressed
(eg by the SfxFileDialog).
[Cross-reference]
<SfxApplication::GetLastDir_Impl()>
*/
{
pImpl->aLastDir = rNewDir;
}
void SfxApplication::ResetLastDir()
{
pImpl->aLastDir.clear();
}
SfxDispatcher* SfxApplication::GetDispatcher_Impl()
{
return pImpl->pViewFrame ? pImpl->pViewFrame->GetDispatcher() : &*pImpl->pAppDispat;

View File

@ -144,6 +144,7 @@ constexpr OUString sDenyList = u"DenyList"_ustr;
constexpr OUString sModifyPasswordInfo = u"ModifyPasswordInfo"_ustr;
constexpr OUString sSuggestedSaveAsDir = u"SuggestedSaveAsDir"_ustr;
constexpr OUString sSuggestedSaveAsName = u"SuggestedSaveAsName"_ustr;
constexpr OUString sExportDirectory = u"ExportDirectory"_ustr;
constexpr OUString sEncryptionData = u"EncryptionData"_ustr;
constexpr OUString sFailOnWarning = u"FailOnWarning"_ustr;
constexpr OUString sDocumentService = u"DocumentService"_ustr;
@ -810,6 +811,14 @@ void TransformParameters( sal_uInt16 nSlotId, const uno::Sequence<beans::Propert
if (bOK)
rSet.Put( SfxStringItem( SID_SUGGESTEDSAVEASNAME, sVal ) );
}
else if ( aName == sExportDirectory )
{
OUString sVal;
bool bOK = ((rProp.Value >>= sVal) && !sVal.isEmpty());
DBG_ASSERT( bOK, "invalid type or value for ExportDirectoy" );
if (bOK)
rSet.Put( SfxStringItem( SID_EXPORTDIRECTORY, sVal ) );
}
else if (aName == sDocumentService)
{
OUString aVal;
@ -1086,6 +1095,8 @@ void TransformItems( sal_uInt16 nSlotId, const SfxItemSet& rSet, uno::Sequence<b
nAdditional++;
if ( rSet.GetItemState( SID_SUGGESTEDSAVEASNAME ) == SfxItemState::SET )
nAdditional++;
if ( rSet.GetItemState( SID_EXPORTDIRECTORY ) == SfxItemState::SET )
nAdditional++;
if ( rSet.GetItemState( SID_DOC_SERVICE ) == SfxItemState::SET )
nAdditional++;
if (rSet.HasItem(SID_FILTER_PROVIDER))
@ -1260,6 +1271,8 @@ void TransformItems( sal_uInt16 nSlotId, const SfxItemSet& rSet, uno::Sequence<b
continue;
if ( nId == SID_SUGGESTEDSAVEASNAME )
continue;
if ( nId == SID_EXPORTDIRECTORY )
continue;
if ( nId == SID_LOCK_CONTENT_EXTRACTION )
continue;
if ( nId == SID_LOCK_EXPORT )
@ -1648,6 +1661,11 @@ void TransformItems( sal_uInt16 nSlotId, const SfxItemSet& rSet, uno::Sequence<b
pValue[nActProp].Name = sSuggestedSaveAsName;
pValue[nActProp++].Value <<= pItem->GetValue();
}
if ( const SfxStringItem *pItem = rSet.GetItemIfSet( SID_EXPORTDIRECTORY, false) )
{
pValue[nActProp].Name = sExportDirectory;
pValue[nActProp++].Value <<= pItem->GetValue();
}
if ( const SfxStringItem *pItem = rSet.GetItemIfSet( SID_DOC_SERVICE, false) )
{
pValue[nActProp].Name = sDocumentService;

View File

@ -925,7 +925,8 @@ FileDialogHelper_Impl::FileDialogHelper_Impl(
const OUString& sStandardDir,
const css::uno::Sequence< OUString >& rDenyList
)
:maPreviewIdle("sfx2 FileDialogHelper_Impl maPreviewIdle")
:msStandardDir ( sStandardDir )
,maPreviewIdle("sfx2 FileDialogHelper_Impl maPreviewIdle")
,m_nDialogType ( nDialogType )
,meContext ( FileDialogHelper::UnknownContext )
{
@ -2194,16 +2195,11 @@ void FileDialogHelper_Impl::saveConfig()
aDlgOpt.SetUserItem( USERITEM_NAME, Any( aUserData ) );
}
// Store to config, if explicit context is set. Otherwise store in (global) runtime var.
if (meContext != FileDialogHelper::UnknownContext)
// Store to config, if explicit context is set (and default directory is not given)
if (meContext != FileDialogHelper::UnknownContext && msStandardDir.isEmpty())
{
SaveLastDirectory(FileDialogHelper::contextToString(meContext), getPath());
}
else
{
SfxApplication *pSfxApp = SfxGetpApp();
pSfxApp->SetLastDir_Impl( getPath() );
}
}
OUString FileDialogHelper_Impl::getInitPath(std::u16string_view _rFallback,
@ -2211,7 +2207,12 @@ OUString FileDialogHelper_Impl::getInitPath(std::u16string_view _rFallback,
{
OUString sPath;
// Load from config, if explicit context is set. Otherwise load from (global) runtime var.
if (meContext != FileDialogHelper::UnknownContext)
if (meContext == FileDialogHelper::UnknownContext || !msStandardDir.isEmpty())
{
// For export, the default directory is passed on
sPath = msStandardDir;
}
else
{
OUString sContext = FileDialogHelper::contextToString(meContext);
Reference<XNameAccess> set(officecfg::Office::Common::Misc::FilePickerLastDirectory::get());
@ -2226,11 +2227,6 @@ OUString FileDialogHelper_Impl::getInitPath(std::u16string_view _rFallback,
{
}
}
else
{
SfxApplication *pSfxApp = SfxGetpApp();
sPath = pSfxApp->GetLastDir_Impl();
}
if ( sPath.isEmpty() )
sPath = o3tl::getToken(_rFallback, _nFallbackToken, ' ' );

View File

@ -61,6 +61,7 @@ namespace sfx2
OUString maCurFilter;
OUString maSelectFilter;
OUString maButtonLabel;
OUString msStandardDir;
Idle maPreviewIdle;
Graphic maGraphic;

View File

@ -63,6 +63,7 @@
#include <svl/eitem.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/namedvaluecollection.hxx>
#include <tools/urlobj.hxx>
#include <tools/json_writer.hxx>
#include <comphelper/processfactory.hxx>
@ -220,7 +221,7 @@ class DocumentSettingsGuard
bool m_bRestoreSettings;
public:
DocumentSettingsGuard( const uno::Reference< frame::XModel >& xModel, bool bReadOnly, bool bRestore )
DocumentSettingsGuard( const uno::Reference< frame::XModel2 >& xModel, bool bReadOnly, bool bRestore )
: m_bPreserveReadOnly( false )
, m_bReadOnlySupported( false )
, m_bRestoreSettings( bRestore )
@ -272,7 +273,7 @@ public:
class ModelData_Impl
{
SfxStoringHelper* m_pOwner;
uno::Reference< frame::XModel > m_xModel;
uno::Reference< frame::XModel2 > m_xModel;
uno::Reference< frame::XStorable > m_xStorable;
uno::Reference< frame::XStorable2 > m_xStorable2;
@ -292,14 +293,14 @@ class ModelData_Impl
public:
ModelData_Impl( SfxStoringHelper& aOwner,
uno::Reference< frame::XModel > xModel,
uno::Reference< frame::XModel2 > xModel,
const uno::Sequence< beans::PropertyValue >& aMediaDescr );
~ModelData_Impl();
void FreeDocumentProps();
uno::Reference< frame::XModel > const & GetModel() const;
uno::Reference< frame::XModel2 > const & GetModel() const;
uno::Reference< frame::XStorable > const & GetStorable();
uno::Reference< frame::XStorable2 > const & GetStorable2();
@ -338,7 +339,7 @@ public:
bool bPreselectPassword,
OUString& aSuggestedDir,
sal_Int16 nDialog,
const OUString& rStandardDir,
OUString& rStandardDir,
const css::uno::Sequence< OUString >& rDenyList
);
@ -352,7 +353,7 @@ public:
ModelData_Impl::ModelData_Impl( SfxStoringHelper& aOwner,
uno::Reference< frame::XModel > xModel,
uno::Reference< frame::XModel2 > xModel,
const uno::Sequence< beans::PropertyValue >& aMediaDescr )
: m_pOwner( &aOwner )
, m_xModel(std::move( xModel ))
@ -380,7 +381,7 @@ void ModelData_Impl::FreeDocumentProps()
}
uno::Reference< frame::XModel > const & ModelData_Impl::GetModel() const
uno::Reference< frame::XModel2 > const & ModelData_Impl::GetModel() const
{
if ( !m_xModel.is() )
throw uno::RuntimeException();
@ -388,7 +389,6 @@ uno::Reference< frame::XModel > const & ModelData_Impl::GetModel() const
return m_xModel;
}
uno::Reference< frame::XStorable > const & ModelData_Impl::GetStorable()
{
if ( !m_xStorable.is() )
@ -894,7 +894,7 @@ bool ModelData_Impl::OutputFileDialog( sal_Int16 nStoreMode,
bool bPreselectPassword,
OUString& aSuggestedDir,
sal_Int16 nDialog,
const OUString& rStandardDir,
OUString& rStandardDir,
const css::uno::Sequence< OUString >& rDenyList)
{
if ( nStoreMode == SAVEASREMOTE_REQUESTED )
@ -955,6 +955,13 @@ bool ModelData_Impl::OutputFileDialog( sal_Int16 nStoreMode,
weld::Window* pFrameWin = SfxStoringHelper::GetModelWindow(m_xModel);
if ( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) )
{
const OUString aBaseUrl = GetDocProps().getUnpackedValueOrDefault("DocumentBaseURL", OUString());
OUString aExportDir = GetDocProps().getUnpackedValueOrDefault("ExportDirectory", aBaseUrl);
INetURLObject aObj( aExportDir );
aObj.removeSegment();
aExportDir = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
if (!aExportDir.isEmpty())
rStandardDir = aExportDir;
if ( ( nStoreMode & PDFEXPORT_REQUESTED ) && !aPreselectedFilterPropsHM.empty() )
{
// this is a PDF export
@ -985,7 +992,6 @@ bool ModelData_Impl::OutputFileDialog( sal_Int16 nStoreMode,
eCtxt = sfx2::FileDialogHelper::WriterExport;
else if ( aDocServiceName == "com.sun.star.sheet.SpreadsheetDocument" )
eCtxt = sfx2::FileDialogHelper::CalcExport;
if ( eCtxt != sfx2::FileDialogHelper::UnknownContext )
pFileDlg->SetContext( eCtxt );
@ -1247,6 +1253,16 @@ bool ModelData_Impl::OutputFileDialog( sal_Int16 nStoreMode,
GetMediaDescr()[u"URL"_ustr] <<= aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
GetMediaDescr()[sFilterNameString] <<= aFilterName;
// for Export - keep a runtime var for each document where the document was last exported to
if (GetStorable()->hasLocation() && (nStoreMode & EXPORT_REQUESTED))
{
uno::Sequence< beans::PropertyValue > descriptor{
beans::PropertyValue(u"ExportDirectory"_ustr,
-1, uno::Any(aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE )), beans::PropertyState_DIRECT_VALUE),
};
GetModel()->setArgs(descriptor);
}
return bUseFilterOptions;
}
@ -1457,7 +1473,7 @@ uno::Reference< css::frame::XModuleManager2 > const & SfxStoringHelper::GetModul
return m_xModuleManager;
}
bool SfxStoringHelper::GUIStoreModel( const uno::Reference< frame::XModel >& xModel,
bool SfxStoringHelper::GUIStoreModel( const uno::Reference< frame::XModel2 >& xModel,
std::u16string_view aSlotName,
uno::Sequence< beans::PropertyValue >& aArgsSequence,
bool bPreselectPassword,
@ -2023,7 +2039,7 @@ bool SfxStoringHelper::CheckFilterOptionsAppearance(
// static
void SfxStoringHelper::SetDocInfoState(
const uno::Reference< frame::XModel >& xModel,
const uno::Reference< frame::XModel2 >& xModel,
const uno::Reference< document::XDocumentProperties>& i_xOldDocProps )
{
uno::Reference<document::XDocumentPropertiesSupplier> const
@ -2098,7 +2114,7 @@ void SfxStoringHelper::SetDocInfoState(
// static
bool SfxStoringHelper::WarnUnacceptableFormat( const uno::Reference< frame::XModel >& xModel,
bool SfxStoringHelper::WarnUnacceptableFormat( const uno::Reference< frame::XModel2 >& xModel,
std::u16string_view aOldUIName,
std::u16string_view aExtension,
const OUString& aDefExtension,
@ -2135,7 +2151,7 @@ bool SfxStoringHelper::WarnUnacceptableFormat( const uno::Reference< frame::XMod
return nResult == RET_YES;
}
uno::Reference<awt::XWindow> SfxStoringHelper::GetModelXWindow(const uno::Reference<frame::XModel>& xModel)
uno::Reference<awt::XWindow> SfxStoringHelper::GetModelXWindow(const uno::Reference<frame::XModel2>& xModel)
{
try {
if ( xModel.is() )
@ -2158,7 +2174,7 @@ uno::Reference<awt::XWindow> SfxStoringHelper::GetModelXWindow(const uno::Refere
return uno::Reference<awt::XWindow>();
}
weld::Window* SfxStoringHelper::GetModelWindow( const uno::Reference< frame::XModel >& xModel )
weld::Window* SfxStoringHelper::GetModelWindow( const uno::Reference< frame::XModel2 >& xModel )
{
weld::Window* pWin = nullptr;

View File

@ -1135,6 +1135,14 @@ void SAL_CALL SfxBaseModel::setArgs(const Sequence<beans::PropertyValue>& aArgs)
ok = true;
}
}
else if (rArg.Name == "ExportDirectory")
{
if (rArg.Value >>= sValue)
{
pMedium->GetItemSet().Put(SfxStringItem(SID_EXPORTDIRECTORY, sValue));
ok = true;
}
}
else if (rArg.Name == "LockContentExtraction")
{
if (rArg.Value >>= bValue)

View File

@ -61,7 +61,6 @@ class SfxAppData_Impl
{
public:
IndexBitSet aIndexBitSet; // for counting noname documents
OUString aLastDir; // for IO dialog
// DDE stuff
std::unique_ptr<DdeService> pDdeService;

View File

@ -906,7 +906,7 @@ ErrCodeMsg SfxInPlaceClient::DoVerb(sal_Int32 nVerb)
{
svt::EmbeddedObjectRef::TryRunningState( m_xImp->m_xObject );
// TODO/LATER: this special verb should disappear when outplace activation is completely available
uno::Reference< frame::XModel > xEmbModel( m_xImp->m_xObject->getComponent(), uno::UNO_QUERY );
uno::Reference< frame::XModel2 > xEmbModel( m_xImp->m_xObject->getComponent(), uno::UNO_QUERY );
if ( xEmbModel.is() )
{
bSaveCopyAs = true;