diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/uibase/dbui | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/uibase/dbui')
-rw-r--r-- | sw/source/uibase/dbui/README | 48 | ||||
-rw-r--r-- | sw/source/uibase/dbui/dbmgr.cxx | 3298 | ||||
-rw-r--r-- | sw/source/uibase/dbui/dbtree.cxx | 454 | ||||
-rw-r--r-- | sw/source/uibase/dbui/dbui.cxx | 86 | ||||
-rw-r--r-- | sw/source/uibase/dbui/maildispatcher.cxx | 249 | ||||
-rw-r--r-- | sw/source/uibase/dbui/mailmergehelper.cxx | 832 | ||||
-rw-r--r-- | sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx | 370 | ||||
-rw-r--r-- | sw/source/uibase/dbui/mmconfigitem.cxx | 1708 |
8 files changed, 7045 insertions, 0 deletions
diff --git a/sw/source/uibase/dbui/README b/sw/source/uibase/dbui/README new file mode 100644 index 0000000000..ba9745c777 --- /dev/null +++ b/sw/source/uibase/dbui/README @@ -0,0 +1,48 @@ +Mail merge (MM) has *four modes*. The modes 1-3 are directly exposed to the +user via different GUIs. The modes are: + +1. FILE = saves the result as documents +2. PRINTER = directly prints resulting documents +3. EMAIL = sends results as individual emails +4. SHELL = returns an internal document shell for further programming + +There is one property, which changes the overall behaviour of these modes: +*bCreateSingleFile*. This is the primary controller of the mail merge source +code workflow, as it affects all modes! + +This also has timing constraints: for individual files the result can be +defined in advance, while further processing of the single result can just +be done after the merging. It mainly affects printing and the generation +of huge (1000+ datasets) combined non-PRINT merge results. A valid +combined document has to follow constraints, which are already met by +individual documents, as these are just modified copies. + +LO currently has five working combinations of mode and *bCreateSingleFile* +The others, ''PRINTER+false'', ''EMAIL+true'' and ''SHELL+false'', are currently not +implemented. But the list contains implementation proposals for them. + +* Mode: FILE +** false: Saves each merged document as an individual file. The file name can + be selected from a database column! +** true: Saves a combined file of concatenated documents. Each document starts + with a new / reset page style. Depending on the page style this inserts + hidden blank pages +* Mode: PRINTER +** false: *not implemented*. This could generate individual print jobs, which could be grouped on some + platforms for easier abortion (needs verification!). All print options + affect only the individual document! Printing could start after the first + document is generated. +** true: Generates a single print job. All print options affect the combined + document! This especially effects the results of reverse and N-UP printing. + Printing can just start after all documents are generated. +* Mode: EMAIL +** false: Each document can either be used as the email body (html or txt) or + be attached to a general email text. The emails are extracted from a DB + column, which must be provided! +** true: *not implemented*. Could send the combined document to a single + email address. +* Mode: SHELL +** false: *not implemented*. Instead of a single shell this could return a + sequence of shells for the individual documents. +** true: Returns the shell of the generated document. This is mainly + interesting for programmers. The document is just generated internally. diff --git a/sw/source/uibase/dbui/dbmgr.cxx b/sw/source/uibase/dbui/dbmgr.cxx new file mode 100644 index 0000000000..bb4cdc12cd --- /dev/null +++ b/sw/source/uibase/dbui/dbmgr.cxx @@ -0,0 +1,3298 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> + +#include <unotxdoc.hxx> +#include <sfx2/app.hxx> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/XDocumentDataSource.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/VndSunStarPkgUrlReferenceFactory.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/TextConnectionSettings.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdb/XCompletedExecution.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/text/MailMergeEvent.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <vcl/errinf.hxx> +#include <vcl/print.hxx> +#include <vcl/scheduler.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <dbconfig.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/pathoptions.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/stritem.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/progress.hxx> +#include <sfx2/dispatch.hxx> +#include <cmdid.h> +#include <swmodule.hxx> +#include <view.hxx> +#include <docsh.hxx> +#include <edtwin.hxx> +#include <wrtsh.hxx> +#include <fldbas.hxx> +#include <dbui.hxx> +#include <dbmgr.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <swwait.hxx> +#include <swunohelper.hxx> +#include <strings.hrc> +#include <mmconfigitem.hxx> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdb/XColumn.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/mail/MailAttachment.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <mailmergehelper.hxx> +#include <maildispatcher.hxx> +#include <svtools/htmlcfg.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <svl/numuno.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/dbconversion.hxx> +#include <unotools/charclass.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <unomailmerge.hxx> +#include <sfx2/event.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <rtl/textenc.h> +#include <rtl/tencinfo.h> +#include <cppuhelper/implbase.hxx> +#include <ndindex.hxx> +#include <swevent.hxx> +#include <sal/log.hxx> +#include <swabstdlg.hxx> +#include <vector> +#include <section.hxx> +#include <rootfrm.hxx> +#include <calc.hxx> +#include <dbfld.hxx> +#include <IDocumentState.hxx> +#include <imaildsplistener.hxx> +#include <iodetect.hxx> +#include <IDocumentDeviceAccess.hxx> + +#include <memory> +#include <mutex> +#include <comphelper/propertysequence.hxx> + +using namespace ::com::sun::star; +using namespace sw; + +namespace { + +void lcl_emitEvent(SfxEventHintId nEventId, sal_Int32 nStrId, SfxObjectShell* pDocShell) +{ + SfxGetpApp()->NotifyEvent(SfxEventHint(nEventId, + SwDocShell::GetEventName(nStrId), + pDocShell)); +} + +// Construct vnd.sun.star.pkg:// URL +OUString ConstructVndSunStarPkgUrl(const OUString& rMainURL, std::u16string_view rStreamRelPath) +{ + auto xContext(comphelper::getProcessComponentContext()); + auto xUri = css::uri::UriReferenceFactory::create(xContext)->parse(rMainURL); + assert(xUri.is()); + xUri = css::uri::VndSunStarPkgUrlReferenceFactory::create(xContext) + ->createVndSunStarPkgUrlReference(xUri); + assert(xUri.is()); + return xUri->getUriReference() + "/" + + INetURLObject::encode( + rStreamRelPath, INetURLObject::PART_FPATH, + INetURLObject::EncodeMechanism::All); +} + +} + +std::vector<std::pair<SwDocShell*, OUString>> SwDBManager::s_aUncommittedRegistrations; + +namespace { + +enum class SwDBNextRecord { NEXT, FIRST }; + +} + +static bool lcl_ToNextRecord( SwDSParam* pParam, const SwDBNextRecord action = SwDBNextRecord::NEXT ); + +namespace { + +enum class WorkingDocType { SOURCE, TARGET, COPY }; + +} + +static SfxObjectShell* lcl_CreateWorkingDocument( + const WorkingDocType aType, const SwWrtShell &rSourceWrtShell, + const vcl::Window *pSourceWindow, + SwDBManager** const ppDBManager, + SwView** const pView, SwWrtShell** const pWrtShell, rtl::Reference<SwDoc>* const pDoc ); + +static bool lcl_getCountFromResultSet( sal_Int32& rCount, const SwDSParam* pParam ) +{ + rCount = pParam->aSelection.getLength(); + if ( rCount > 0 ) + return true; + + uno::Reference<beans::XPropertySet> xPrSet(pParam->xResultSet, uno::UNO_QUERY); + if ( xPrSet.is() ) + { + try + { + bool bFinal = false; + uno::Any aFinal = xPrSet->getPropertyValue("IsRowCountFinal"); + aFinal >>= bFinal; + if(!bFinal) + { + pParam->xResultSet->last(); + pParam->xResultSet->first(); + } + uno::Any aCount = xPrSet->getPropertyValue("RowCount"); + if( aCount >>= rCount ) + return true; + } + catch(const uno::Exception&) + { + } + } + return false; +} + +class SwDBManager::ConnectionDisposedListener_Impl + : public cppu::WeakImplHelper< lang::XEventListener > +{ +private: + SwDBManager * m_pDBManager; + + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; + +public: + explicit ConnectionDisposedListener_Impl(SwDBManager& rMgr); + + void Dispose() { m_pDBManager = nullptr; } + +}; + +namespace { + +/// Listens to removed data sources, and if it's one that's embedded into this document, triggers embedding removal. +class SwDataSourceRemovedListener : public cppu::WeakImplHelper<sdb::XDatabaseRegistrationsListener> +{ + uno::Reference<sdb::XDatabaseContext> m_xDatabaseContext; + SwDBManager* m_pDBManager; + +public: + explicit SwDataSourceRemovedListener(SwDBManager& rDBManager); + virtual ~SwDataSourceRemovedListener() override; + virtual void SAL_CALL registeredDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override; + virtual void SAL_CALL revokedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override; + virtual void SAL_CALL changedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override; + virtual void SAL_CALL disposing(const lang::EventObject& rObject) override; + void Dispose(); +}; + +} + +SwDataSourceRemovedListener::SwDataSourceRemovedListener(SwDBManager& rDBManager) + : m_pDBManager(&rDBManager) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + m_xDatabaseContext = sdb::DatabaseContext::create(xComponentContext); + m_xDatabaseContext->addDatabaseRegistrationsListener(this); +} + +SwDataSourceRemovedListener::~SwDataSourceRemovedListener() +{ + if (m_xDatabaseContext.is()) + m_xDatabaseContext->removeDatabaseRegistrationsListener(this); +} + +void SAL_CALL SwDataSourceRemovedListener::registeredDatabaseLocation(const sdb::DatabaseRegistrationEvent& /*rEvent*/) +{ +} + +void SAL_CALL SwDataSourceRemovedListener::revokedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) +{ + if (!m_pDBManager || m_pDBManager->getEmbeddedName().isEmpty()) + return; + + SwDoc* pDoc = m_pDBManager->getDoc(); + if (!pDoc) + return; + + SwDocShell* pDocShell = pDoc->GetDocShell(); + if (!pDocShell) + return; + + const OUString sTmpName = ConstructVndSunStarPkgUrl( + pDocShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE), + m_pDBManager->getEmbeddedName()); + + if (sTmpName != rEvent.OldLocation) + return; + + // The revoked database location is inside this document, then remove the + // embedding, as otherwise it would be back on the next reload of the + // document. + pDocShell->GetStorage()->removeElement(m_pDBManager->getEmbeddedName()); + m_pDBManager->setEmbeddedName(OUString(), *pDocShell); +} + +void SAL_CALL SwDataSourceRemovedListener::changedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) +{ + if (rEvent.OldLocation != rEvent.NewLocation) + revokedDatabaseLocation(rEvent); +} + +void SwDataSourceRemovedListener::disposing(const lang::EventObject& /*rObject*/) +{ + m_xDatabaseContext.clear(); +} + +void SwDataSourceRemovedListener::Dispose() +{ + m_pDBManager = nullptr; +} + +struct SwDBManager::SwDBManager_Impl +{ + std::unique_ptr<SwDSParam> pMergeData; + VclPtr<AbstractMailMergeDlg> pMergeDialog; + rtl::Reference<SwDBManager::ConnectionDisposedListener_Impl> m_xDisposeListener; + rtl::Reference<SwDataSourceRemovedListener> m_xDataSourceRemovedListener; + std::mutex m_aAllEmailSendMutex; + uno::Reference< mail::XMailMessage> m_xLastMessage; + + explicit SwDBManager_Impl(SwDBManager& rDBManager) + : m_xDisposeListener(new ConnectionDisposedListener_Impl(rDBManager)) + {} + + ~SwDBManager_Impl() + { + m_xDisposeListener->Dispose(); + if (m_xDataSourceRemovedListener.is()) + m_xDataSourceRemovedListener->Dispose(); + } +}; + +static void lcl_InitNumberFormatter(SwDSParam& rParam, uno::Reference<sdbc::XDataSource> const & xSource) +{ + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + rParam.xFormatter = util::NumberFormatter::create(xContext); + uno::Reference<beans::XPropertySet> xSourceProps( + (xSource.is() + ? xSource + : SwDBManager::getDataSourceAsParent( + rParam.xConnection, rParam.sDataSource)), + uno::UNO_QUERY); + if(!xSourceProps.is()) + return; + + uno::Any aFormats = xSourceProps->getPropertyValue("NumberFormatsSupplier"); + if(!aFormats.hasValue()) + return; + + uno::Reference<util::XNumberFormatsSupplier> xSuppl; + aFormats >>= xSuppl; + if(xSuppl.is()) + { + uno::Reference< beans::XPropertySet > xSettings = xSuppl->getNumberFormatSettings(); + uno::Any aNull = xSettings->getPropertyValue("NullDate"); + aNull >>= rParam.aNullDate; + if(rParam.xFormatter.is()) + rParam.xFormatter->attachNumberFormatsSupplier(xSuppl); + } +} + +static bool lcl_MoveAbsolute(SwDSParam* pParam, tools::Long nAbsPos) +{ + bool bRet = false; + try + { + if(pParam->aSelection.hasElements()) + { + if(pParam->aSelection.getLength() <= nAbsPos) + { + pParam->bEndOfDB = true; + bRet = false; + } + else + { + pParam->nSelectionIndex = nAbsPos; + sal_Int32 nPos = 0; + pParam->aSelection.getConstArray()[ pParam->nSelectionIndex ] >>= nPos; + pParam->bEndOfDB = !pParam->xResultSet->absolute( nPos ); + bRet = !pParam->bEndOfDB; + } + } + else if(pParam->bScrollable) + { + bRet = pParam->xResultSet->absolute( nAbsPos ); + } + else + { + OSL_FAIL("no absolute positioning available"); + } + } + catch(const uno::Exception&) + { + } + return bRet; +} + +static void lcl_GetColumnCnt(SwDSParam *pParam, + const uno::Reference< beans::XPropertySet > &rColumnProps, + LanguageType nLanguage, OUString &rResult, double* pNumber) +{ + SwDBFormatData aFormatData; + if(!pParam->xFormatter.is()) + { + uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent( + pParam->xConnection,pParam->sDataSource); + lcl_InitNumberFormatter(*pParam, xSource ); + } + aFormatData.aNullDate = pParam->aNullDate; + aFormatData.xFormatter = pParam->xFormatter; + + aFormatData.aLocale = LanguageTag( nLanguage ).getLocale(); + + rResult = SwDBManager::GetDBField( rColumnProps, aFormatData, pNumber); +} + +static bool lcl_GetColumnCnt(SwDSParam* pParam, const OUString& rColumnName, + LanguageType nLanguage, OUString& rResult, double* pNumber) +{ + uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( pParam->xResultSet, uno::UNO_QUERY ); + uno::Reference<container::XNameAccess> xCols; + try + { + xCols = xColsSupp->getColumns(); + } + catch(const lang::DisposedException&) + { + } + if(!xCols.is() || !xCols->hasByName(rColumnName)) + return false; + uno::Any aCol = xCols->getByName(rColumnName); + uno::Reference< beans::XPropertySet > xColumnProps; + aCol >>= xColumnProps; + lcl_GetColumnCnt( pParam, xColumnProps, nLanguage, rResult, pNumber ); + return true; +}; + +// import data +bool SwDBManager::Merge( const SwMergeDescriptor& rMergeDesc ) +{ + assert( !m_bInMerge && !m_pImpl->pMergeData && "merge already activated!" ); + + SfxObjectShellLock xWorkObjSh; + SwWrtShell *pWorkShell = nullptr; + rtl::Reference<SwDoc> pWorkDoc; + SwDBManager *pWorkDocOrigDBManager = nullptr; + + switch( rMergeDesc.nMergeType ) + { + case DBMGR_MERGE_PRINTER: + case DBMGR_MERGE_EMAIL: + case DBMGR_MERGE_FILE: + case DBMGR_MERGE_SHELL: + { + SwDocShell *pSourceDocSh = rMergeDesc.rSh.GetView().GetDocShell(); + if( pSourceDocSh->IsModified() ) + { + pWorkDocOrigDBManager = this; + xWorkObjSh = lcl_CreateWorkingDocument( + WorkingDocType::SOURCE, rMergeDesc.rSh, nullptr, + &pWorkDocOrigDBManager, nullptr, &pWorkShell, &pWorkDoc ); + } + [[fallthrough]]; + } + + default: + if( !xWorkObjSh.Is() ) + pWorkShell = &rMergeDesc.rSh; + break; + } + + SwDBData aData; + aData.nCommandType = sdb::CommandType::TABLE; + uno::Reference<sdbc::XResultSet> xResSet; + uno::Sequence<uno::Any> aSelection; + uno::Reference< sdbc::XConnection> xConnection; + + aData.sDataSource = rMergeDesc.rDescriptor.getDataSource(); + rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Command] >>= aData.sCommand; + rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= aData.nCommandType; + + if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Cursor) ) + rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Cursor] >>= xResSet; + if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Selection) ) + rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Selection] >>= aSelection; + if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Connection) ) + rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Connection] >>= xConnection; + + if((aData.sDataSource.isEmpty() || aData.sCommand.isEmpty()) && !xResSet.is()) + { + return false; + } + + m_pImpl->pMergeData.reset(new SwDSParam(aData, xResSet, aSelection)); + SwDSParam* pTemp = FindDSData(aData, false); + if(pTemp) + *pTemp = *m_pImpl->pMergeData; + else + { + // calls from the calculator may have added a connection with an invalid commandtype + //"real" data base connections added here have to re-use the already available + //DSData and set the correct CommandType + aData.nCommandType = -1; + pTemp = FindDSData(aData, false); + if(pTemp) + *pTemp = *m_pImpl->pMergeData; + else + { + m_DataSourceParams.push_back(std::make_unique<SwDSParam>(*m_pImpl->pMergeData)); + try + { + uno::Reference<lang::XComponent> xComponent(m_DataSourceParams.back()->xConnection, uno::UNO_QUERY); + if(xComponent.is()) + xComponent->addEventListener(m_pImpl->m_xDisposeListener); + } + catch(const uno::Exception&) + { + } + } + } + if(!m_pImpl->pMergeData->xConnection.is()) + m_pImpl->pMergeData->xConnection = xConnection; + // add an XEventListener + + lcl_ToNextRecord(m_pImpl->pMergeData.get(), SwDBNextRecord::FIRST); + + uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(xConnection,aData.sDataSource); + + lcl_InitNumberFormatter(*m_pImpl->pMergeData, xSource); + + pWorkShell->ChgDBData(aData); + m_bInMerge = true; + + if (IsInitDBFields()) + { + // with database fields without DB-Name, use DB-Name from Doc + std::vector<OUString> aDBNames; + aDBNames.emplace_back(); + SwDBData aInsertData = pWorkShell->GetDBData(); + OUString sDBName = aInsertData.sDataSource + + OUStringChar(DB_DELIM) + aInsertData.sCommand + + OUStringChar(DB_DELIM) + + OUString::number(aInsertData.nCommandType); + pWorkShell->ChangeDBFields( aDBNames, sDBName); + SetInitDBFields(false); + } + + bool bRet = true; + switch(rMergeDesc.nMergeType) + { + case DBMGR_MERGE: + pWorkShell->StartAllAction(); + pWorkShell->SwViewShell::UpdateFields( true ); + pWorkShell->SetModified(); + pWorkShell->EndAllAction(); + break; + + case DBMGR_MERGE_PRINTER: + case DBMGR_MERGE_EMAIL: + case DBMGR_MERGE_FILE: + case DBMGR_MERGE_SHELL: + // save files and send them as e-Mail if required + bRet = MergeMailFiles(pWorkShell, rMergeDesc); + break; + + default: + // insert selected entries + // (was: InsertRecord) + ImportFromConnection(pWorkShell); + break; + } + + m_pImpl->pMergeData.reset(); + + if( xWorkObjSh.Is() ) + { + pWorkDoc->SetDBManager( pWorkDocOrigDBManager ); + xWorkObjSh->DoClose(); + } + + m_bInMerge = false; + + return bRet; +} + +void SwDBManager::ImportFromConnection( SwWrtShell* pSh ) +{ + if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB) + return; + + pSh->StartAllAction(); + pSh->StartUndo(); + bool bGroupUndo(pSh->DoesGroupUndo()); + pSh->DoGroupUndo(false); + + if( pSh->HasSelection() ) + pSh->DelRight(); + + std::optional<SwWait> oWait; + + { + sal_uInt32 i = 0; + do { + + ImportDBEntry(pSh); + if( 10 == ++i ) + oWait.emplace( *pSh->GetView().GetDocShell(), true); + + } while(ToNextMergeRecord()); + } + + pSh->DoGroupUndo(bGroupUndo); + pSh->EndUndo(); + pSh->EndAllAction(); +} + +void SwDBManager::ImportDBEntry(SwWrtShell* pSh) +{ + if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB) + return; + + uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY ); + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + OUStringBuffer sStr; + uno::Sequence<OUString> aColNames = xCols->getElementNames(); + const OUString* pColNames = aColNames.getConstArray(); + tools::Long nLength = aColNames.getLength(); + for(tools::Long i = 0; i < nLength; i++) + { + uno::Any aCol = xCols->getByName(pColNames[i]); + uno::Reference< beans::XPropertySet > xColumnProp; + aCol >>= xColumnProp; + SwDBFormatData aDBFormat; + sStr.append(GetDBField( xColumnProp, aDBFormat)); + if (i < nLength - 1) + sStr.append("\t"); + } + pSh->SwEditShell::Insert2(sStr.makeStringAndClear()); + pSh->SwFEShell::SplitNode(); // line feed +} + +bool SwDBManager::GetTableNames(weld::ComboBox& rBox, const OUString& rDBName) +{ + bool bRet = false; + OUString sOldTableName(rBox.get_active_text()); + rBox.clear(); + SwDSParam* pParam = FindDSConnection(rDBName, false); + uno::Reference< sdbc::XConnection> xConnection; + if (pParam && pParam->xConnection.is()) + xConnection = pParam->xConnection; + else + { + if ( !rDBName.isEmpty() ) + xConnection = RegisterConnection( rDBName ); + } + if (xConnection.is()) + { + uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY); + if(xTSupplier.is()) + { + uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables(); + const uno::Sequence<OUString> aTables = xTables->getElementNames(); + for (const OUString& rTable : aTables) + rBox.append("0", rTable); + } + uno::Reference<sdb::XQueriesSupplier> xQSupplier(xConnection, uno::UNO_QUERY); + if(xQSupplier.is()) + { + uno::Reference<container::XNameAccess> xQueries = xQSupplier->getQueries(); + const uno::Sequence<OUString> aQueries = xQueries->getElementNames(); + for (const OUString& rQuery : aQueries) + rBox.append("1", rQuery); + } + if (!sOldTableName.isEmpty()) + rBox.set_active_text(sOldTableName); + bRet = true; + } + return bRet; +} + +// fill Listbox with column names of a database +void SwDBManager::GetColumnNames(weld::ComboBox& rBox, + const OUString& rDBName, const OUString& rTableName) +{ + SwDBData aData; + aData.sDataSource = rDBName; + aData.sCommand = rTableName; + aData.nCommandType = -1; + SwDSParam* pParam = FindDSData(aData, false); + uno::Reference< sdbc::XConnection> xConnection; + if(pParam && pParam->xConnection.is()) + xConnection = pParam->xConnection; + else + { + xConnection = RegisterConnection( rDBName ); + } + GetColumnNames(rBox, xConnection, rTableName); +} + +void SwDBManager::GetColumnNames(weld::ComboBox& rBox, + uno::Reference< sdbc::XConnection> const & xConnection, + const OUString& rTableName) +{ + rBox.clear(); + uno::Reference< sdbcx::XColumnsSupplier> xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName); + if(xColsSupp.is()) + { + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + const uno::Sequence<OUString> aColNames = xCols->getElementNames(); + for (const OUString& rColName : aColNames) + { + rBox.append_text(rColName); + } + ::comphelper::disposeComponent( xColsSupp ); + } +} + +SwDBManager::SwDBManager(SwDoc* pDoc) + : m_aMergeStatus( MergeStatus::Ok ) + , m_bInitDBFields(false) + , m_bInMerge(false) + , m_bMergeSilent(false) + , m_pImpl(new SwDBManager_Impl(*this)) + , m_pMergeEvtSrc(nullptr) + , m_pDoc(pDoc) +{ +} + +void SwDBManager::ImplDestroy() +{ + RevokeLastRegistrations(); + + // copy required, m_DataSourceParams can be modified while disposing components + std::vector<uno::Reference<sdbc::XConnection>> aCopiedConnections; + for (const auto & pParam : m_DataSourceParams) + { + if(pParam->xConnection.is()) + { + aCopiedConnections.push_back(pParam->xConnection); + } + } + for (const auto & xConnection : aCopiedConnections) + { + try + { + uno::Reference<lang::XComponent> xComp(xConnection, uno::UNO_QUERY); + if(xComp.is()) + xComp->dispose(); + } + catch(const uno::RuntimeException&) + { + //may be disposed already since multiple entries may have used the same connection + } + } +} + +SwDBManager::~SwDBManager() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +static void lcl_RemoveSectionLinks( SwWrtShell& rWorkShell ) +{ + //reset all links of the sections of synchronized labels + size_t nSections = rWorkShell.GetSectionFormatCount(); + for (size_t nSection = 0; nSection < nSections; ++nSection) + { + SwSectionData aSectionData( *rWorkShell.GetSectionFormat( nSection ).GetSection() ); + if( aSectionData.GetType() == SectionType::FileLink ) + { + aSectionData.SetType( SectionType::Content ); + aSectionData.SetLinkFileName( OUString() ); + rWorkShell.UpdateSection( nSection, aSectionData ); + } + } + rWorkShell.SetLabelDoc( false ); +} + +static void lcl_SaveDebugDoc( SfxObjectShell *xTargetDocShell, + const char *name, int no = 0 ) +{ + static OUString sTempDirURL; + if( sTempDirURL.isEmpty() ) + { + SvtPathOptions aPathOpt; + utl::TempFileNamed aTempDir( &aPathOpt.GetTempPath(), true ); + if( aTempDir.IsValid() ) + { + INetURLObject aTempDirURL( aTempDir.GetURL() ); + sTempDirURL = aTempDirURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + SAL_INFO( "sw.mailmerge", "Dump directory: " << sTempDirURL ); + } + } + if( sTempDirURL.isEmpty() ) + return; + + OUString basename = OUString::createFromAscii( name ); + if (no > 0) + basename += OUString::number(no) + "-"; + // aTempFile is not deleted, but that seems to be intentional + utl::TempFileNamed aTempFile( basename, true, u".odt", &sTempDirURL ); + INetURLObject aTempFileURL( aTempFile.GetURL() ); + auto pDstMed = std::make_unique<SfxMedium>( + aTempFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + StreamMode::STD_READWRITE ); + bool bAnyError = !xTargetDocShell->DoSaveAs( *pDstMed ); + // xObjectShell->DoSaveCompleted crashes the mail merge unit tests, so skip it + bAnyError |= (ERRCODE_NONE != xTargetDocShell->GetErrorIgnoreWarning()); + if( bAnyError ) + SAL_WARN( "sw.mailmerge", "Error saving: " << aTempFile.GetURL() ); + else + SAL_INFO( "sw.mailmerge", "Saved doc as: " << aTempFile.GetURL() ); +} + +static bool lcl_SaveDoc( + const INetURLObject* pFileURL, + const std::shared_ptr<const SfxFilter>& pStoreToFilter, + const OUString* pStoreToFilterOptions, + const uno::Sequence< beans::PropertyValue >* pSaveToFilterData, + const bool bIsPDFexport, + SfxObjectShell* xObjectShell, + SwWrtShell& rWorkShell, + OUString * const decodedURL = nullptr ) +{ + OUString url = pFileURL->GetMainURL( INetURLObject::DecodeMechanism::NONE ); + if( decodedURL ) + (*decodedURL) = url; + + SfxMedium* pDstMed = new SfxMedium( url, StreamMode::STD_READWRITE ); + pDstMed->SetFilter( pStoreToFilter ); + if( pStoreToFilterOptions ) + pDstMed->GetItemSet().Put( SfxStringItem(SID_FILE_FILTEROPTIONS, + *pStoreToFilterOptions)); + if( pSaveToFilterData->hasElements() ) + pDstMed->GetItemSet().Put( SfxUnoAnyItem(SID_FILTER_DATA, + uno::Any(*pSaveToFilterData))); + + // convert fields to text if we are exporting to PDF. + // this prevents a second merge while updating the fields + // in SwXTextDocument::getRendererCount() + if( bIsPDFexport ) + rWorkShell.ConvertFieldsToText(); + + bool bAnyError = !xObjectShell->DoSaveAs(*pDstMed); + // Actually this should be a bool... so in case of email and individual + // files, where this is set, we skip the recently used handling + bAnyError |= !xObjectShell->DoSaveCompleted( pDstMed, !decodedURL ); + bAnyError |= (ERRCODE_NONE != xObjectShell->GetErrorIgnoreWarning()); + if( bAnyError ) + { + // error message ?? + ErrorHandler::HandleError( xObjectShell->GetErrorIgnoreWarning() ); + } + return !bAnyError; +} + +static void lcl_PreparePrinterOptions( + const uno::Sequence< beans::PropertyValue >& rInPrintOptions, + uno::Sequence< beans::PropertyValue >& rOutPrintOptions) +{ + // printing should be done synchronously otherwise the document + // might already become invalid during the process + + rOutPrintOptions = { comphelper::makePropertyValue("Wait", true) }; + + // copy print options + sal_Int32 nIndex = 1; + for( const beans::PropertyValue& rOption : rInPrintOptions) + { + if( rOption.Name == "CopyCount" || rOption.Name == "FileName" + || rOption.Name == "Collate" || rOption.Name == "Pages" + || rOption.Name == "Wait" || rOption.Name == "PrinterName" ) + { + // add an option + rOutPrintOptions.realloc( nIndex + 1 ); + auto pOutPrintOptions = rOutPrintOptions.getArray(); + pOutPrintOptions[ nIndex ].Name = rOption.Name; + pOutPrintOptions[ nIndex++ ].Value = rOption.Value ; + } + } +} + +static void lcl_PrepareSaveFilterDataOptions( + const uno::Sequence< beans::PropertyValue >& rInSaveFilterDataptions, + uno::Sequence< beans::PropertyValue >& rOutSaveFilterDataOptions, + const OUString& sPassword) +{ + rOutSaveFilterDataOptions + = { comphelper::makePropertyValue("EncryptFile", true), + comphelper::makePropertyValue("DocumentOpenPassword", sPassword) }; + + // copy other options + sal_Int32 nIndex = 2; + for( const beans::PropertyValue& rOption : rInSaveFilterDataptions) + { + rOutSaveFilterDataOptions.realloc( nIndex + 1 ); + auto pOutSaveFilterDataOptions = rOutSaveFilterDataOptions.getArray(); + pOutSaveFilterDataOptions[ nIndex ].Name = rOption.Name; + pOutSaveFilterDataOptions[ nIndex++ ].Value = rOption.Value ; + } + +} + + +static SfxObjectShell* lcl_CreateWorkingDocument( + // input + const WorkingDocType aType, const SwWrtShell &rSourceWrtShell, + // optional input + const vcl::Window *pSourceWindow, + // optional in and output to swap the DB manager + SwDBManager** const ppDBManager, + // optional output + SwView** const pView, SwWrtShell** const pWrtShell, rtl::Reference<SwDoc>* const pDoc ) +{ + const SwDoc *pSourceDoc = rSourceWrtShell.GetDoc(); + SfxObjectShellRef xWorkObjectShell = pSourceDoc->CreateCopy( true, (aType == WorkingDocType::TARGET) ); + SfxViewFrame* pWorkFrame = SfxViewFrame::LoadHiddenDocument( *xWorkObjectShell, SFX_INTERFACE_NONE ); + + if( pSourceWindow ) + { + // the created window has to be located at the same position as the source window + vcl::Window& rTargetWindow = pWorkFrame->GetFrame().GetWindow(); + rTargetWindow.SetPosPixel( pSourceWindow->GetPosPixel() ); + } + + SwView* pWorkView = static_cast< SwView* >( pWorkFrame->GetViewShell() ); + + if (SwWrtShell* pWorkWrtShell = pWorkView->GetWrtShellPtr()) + { + pWorkWrtShell->GetViewOptions()->SetIdle( false ); + pWorkView->AttrChangedNotify(nullptr);// in order for SelectShell to be called + SwDoc* pWorkDoc = pWorkWrtShell->GetDoc(); + pWorkDoc->GetIDocumentUndoRedo().DoUndo( false ); + pWorkDoc->ReplaceDocumentProperties( *pSourceDoc ); + + // import print settings + const SwPrintData &rPrintData = pSourceDoc->getIDocumentDeviceAccess().getPrintData(); + pWorkDoc->getIDocumentDeviceAccess().setPrintData(rPrintData); + const JobSetup *pJobSetup = pSourceDoc->getIDocumentDeviceAccess().getJobsetup(); + if (pJobSetup) + pWorkDoc->getIDocumentDeviceAccess().setJobsetup(*pJobSetup); + + if( aType == WorkingDocType::TARGET ) + { + assert( !ppDBManager ); + pWorkDoc->SetInMailMerge( true ); + pWorkWrtShell->SetLabelDoc( false ); + } + else + { + // We have to swap the DBmanager of the new doc, so we also need input + assert(ppDBManager && *ppDBManager); + SwDBManager *pWorkDBManager = pWorkDoc->GetDBManager(); + pWorkDoc->SetDBManager( *ppDBManager ); + *ppDBManager = pWorkDBManager; + + if( aType == WorkingDocType::SOURCE ) + { + // the GetDBData call constructs the data, if it's missing - kind of const... + pWorkWrtShell->ChgDBData( const_cast<SwDoc*>(pSourceDoc)->GetDBData() ); + // some DocumentSettings are currently not copied by SwDoc::CreateCopy + pWorkWrtShell->SetLabelDoc( rSourceWrtShell.IsLabelDoc() ); + pWorkDoc->getIDocumentState().ResetModified(); + } + else + pWorkDoc->getIDocumentLinksAdministration().EmbedAllLinks(); + } + + if( pView ) *pView = pWorkView; + if( pWrtShell ) *pWrtShell = pWorkWrtShell; + if( pDoc ) *pDoc = pWorkDoc; + } + + return xWorkObjectShell.get(); +} + +static rtl::Reference<SwMailMessage> lcl_CreateMailFromDoc( + const SwMergeDescriptor &rMergeDescriptor, + const OUString &sFileURL, const OUString &sMailRecipient, + const OUString &sMailBodyMimeType, rtl_TextEncoding sMailEncoding, + const OUString &sAttachmentMimeType ) +{ + rtl::Reference<SwMailMessage> pMessage = new SwMailMessage; + if( rMergeDescriptor.pMailMergeConfigItem->IsMailReplyTo() ) + pMessage->setReplyToAddress(rMergeDescriptor.pMailMergeConfigItem->GetMailReplyTo()); + pMessage->addRecipient( sMailRecipient ); + pMessage->SetSenderAddress( rMergeDescriptor.pMailMergeConfigItem->GetMailAddress() ); + + OUStringBuffer sBody; + if( rMergeDescriptor.bSendAsAttachment ) + { + sBody = rMergeDescriptor.sMailBody; + mail::MailAttachment aAttach; + aAttach.Data = new SwMailTransferable( sFileURL, + rMergeDescriptor.sAttachmentName, sAttachmentMimeType ); + aAttach.ReadableName = rMergeDescriptor.sAttachmentName; + pMessage->addAttachment( aAttach ); + } + else + { + //read in the temporary file and use it as mail body + SfxMedium aMedium( sFileURL, StreamMode::READ ); + SvStream* pInStream = aMedium.GetInStream(); + assert( pInStream && "no output file created?" ); + if( !pInStream ) + return pMessage; + + pInStream->SetStreamCharSet( sMailEncoding ); + OStringBuffer sLine; + while ( pInStream->ReadLine( sLine ) ) + { + sBody.append(OStringToOUString( sLine, sMailEncoding ) + "\n"); + } + } + pMessage->setSubject( rMergeDescriptor.sSubject ); + uno::Reference< datatransfer::XTransferable> xBody = + new SwMailTransferable( sBody.makeStringAndClear(), sMailBodyMimeType ); + pMessage->setBody( xBody ); + + for( const OUString& sCcRecipient : rMergeDescriptor.aCopiesTo ) + pMessage->addCcRecipient( sCcRecipient ); + for( const OUString& sBccRecipient : rMergeDescriptor.aBlindCopiesTo ) + pMessage->addBccRecipient( sBccRecipient ); + + return pMessage; +} + +class SwDBManager::MailDispatcherListener_Impl : public IMailDispatcherListener +{ + SwDBManager &m_rDBManager; + +public: + explicit MailDispatcherListener_Impl( SwDBManager &rDBManager ) + : m_rDBManager( rDBManager ) {} + + virtual void idle() override {} + + virtual void mailDelivered( uno::Reference< mail::XMailMessage> xMessage ) override + { + std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex ); + if ( m_rDBManager.m_pImpl->m_xLastMessage == xMessage ) + m_rDBManager.m_pImpl->m_xLastMessage.clear(); + } + + virtual void mailDeliveryError( ::rtl::Reference<MailDispatcher> xMailDispatcher, + uno::Reference< mail::XMailMessage>, const OUString& ) override + { + std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex ); + m_rDBManager.m_aMergeStatus = MergeStatus::Error; + m_rDBManager.m_pImpl->m_xLastMessage.clear(); + xMailDispatcher->stop(); + } +}; + +/** + * Please have a look at the README in the same directory, before you make + * larger changes in this function! + */ +bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + const SwMergeDescriptor& rMergeDescriptor) +{ + // deconstruct mail merge type for better readability. + // uppercase naming is intentional! + const bool bMT_EMAIL = rMergeDescriptor.nMergeType == DBMGR_MERGE_EMAIL; + const bool bMT_SHELL = rMergeDescriptor.nMergeType == DBMGR_MERGE_SHELL; + const bool bMT_PRINTER = rMergeDescriptor.nMergeType == DBMGR_MERGE_PRINTER; + const bool bMT_FILE = rMergeDescriptor.nMergeType == DBMGR_MERGE_FILE; + + //check if the doc is synchronized and contains at least one linked section + const bool bSynchronizedDoc = pSourceShell->IsLabelDoc() && pSourceShell->GetSectionFormatCount() > 1; + const bool bNeedsTempFiles = ( bMT_EMAIL || bMT_FILE ); + const bool bIsMergeSilent = IsMergeSilent(); + + bool bCheckSingleFile_ = rMergeDescriptor.bCreateSingleFile; + OUString sPrefix_ = rMergeDescriptor.sPrefix; + if( bMT_EMAIL ) + { + assert( !rMergeDescriptor.bPrefixIsFilename ); + assert(!bCheckSingleFile_); + bCheckSingleFile_ = false; + } + else if( bMT_SHELL || bMT_PRINTER ) + { + assert(bCheckSingleFile_); + bCheckSingleFile_ = true; + assert(sPrefix_.isEmpty()); + sPrefix_.clear(); + } + const bool bCreateSingleFile = bCheckSingleFile_; + const OUString sDescriptorPrefix = sPrefix_; + + // Setup for dumping debugging documents + static const sal_Int32 nMaxDumpDocs = []() { + if (const char* sEnv = getenv("SW_DEBUG_MAILMERGE_DOCS")) + return OUString(sEnv, strlen(sEnv), osl_getThreadTextEncoding()).toInt32(); + else + return sal_Int32(0); + }(); + + ::rtl::Reference< MailDispatcher > xMailDispatcher; + ::rtl::Reference< IMailDispatcherListener > xMailListener; + OUString sMailBodyMimeType; + rtl_TextEncoding sMailEncoding = ::osl_getThreadTextEncoding(); + + uno::Reference< beans::XPropertySet > xColumnProp; + uno::Reference< beans::XPropertySet > xPasswordColumnProp; + + // Check for (mandatory) email or (optional) filename column + SwDBFormatData aColumnDBFormat; + bool bColumnName = !rMergeDescriptor.sDBcolumn.isEmpty(); + bool bPasswordColumnName = !rMergeDescriptor.sDBPasswordColumn.isEmpty(); + + if( ! bColumnName ) + { + if( bMT_EMAIL ) + return false; + } + else + { + uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY ); + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + if( !xCols->hasByName( rMergeDescriptor.sDBcolumn ) ) + return false; + uno::Any aCol = xCols->getByName( rMergeDescriptor.sDBcolumn ); + aCol >>= xColumnProp; + + if(bPasswordColumnName) + { + aCol = xCols->getByName( rMergeDescriptor.sDBPasswordColumn ); + aCol >>= xPasswordColumnProp; + } + + aColumnDBFormat.xFormatter = m_pImpl->pMergeData->xFormatter; + aColumnDBFormat.aNullDate = m_pImpl->pMergeData->aNullDate; + + if( bMT_EMAIL ) + { + // Reset internal mail accounting data + m_pImpl->m_xLastMessage.clear(); + + xMailDispatcher.set( new MailDispatcher(rMergeDescriptor.xSmtpServer) ); + xMailListener = new MailDispatcherListener_Impl( *this ); + xMailDispatcher->addListener( xMailListener ); + if(!rMergeDescriptor.bSendAsAttachment && rMergeDescriptor.bSendAsHTML) + { + sMailBodyMimeType = "text/html; charset=utf-8"; + sMailEncoding = RTL_TEXTENCODING_UTF8; + } + else + sMailBodyMimeType = "text/plain; charset=UTF-8; format=flowed"; + } + } + + SwDocShell *pSourceDocSh = pSourceShell->GetView().GetDocShell(); + + // setup the output format + std::shared_ptr<const SfxFilter> pStoreToFilter = SwIoSystem::GetFileFilter( + pSourceDocSh->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE)); + SfxFilterContainer* pFilterContainer = SwDocShell::Factory().GetFilterContainer(); + const OUString* pStoreToFilterOptions = nullptr; + + // if a save_to filter is set then use it - otherwise use the default + if( bMT_EMAIL && !rMergeDescriptor.bSendAsAttachment ) + { + OUString sExtension = rMergeDescriptor.bSendAsHTML ? OUString("html") : OUString("txt"); + pStoreToFilter = pFilterContainer->GetFilter4Extension(sExtension, SfxFilterFlags::EXPORT); + } + else if( !rMergeDescriptor.sSaveToFilter.isEmpty()) + { + std::shared_ptr<const SfxFilter> pFilter = + pFilterContainer->GetFilter4FilterName( rMergeDescriptor.sSaveToFilter ); + if(pFilter) + { + pStoreToFilter = pFilter; + if(!rMergeDescriptor.sSaveToFilterOptions.isEmpty()) + pStoreToFilterOptions = &rMergeDescriptor.sSaveToFilterOptions; + } + } + const bool bIsPDFexport = pStoreToFilter && pStoreToFilter->GetFilterName() == "writer_pdf_Export"; + const bool bIsMultiFile = bMT_FILE && !bCreateSingleFile; + + m_aMergeStatus = MergeStatus::Ok; + + // in case of creating a single resulting file this has to be created here + SwView* pTargetView = rMergeDescriptor.pMailMergeConfigItem ? + rMergeDescriptor.pMailMergeConfigItem->GetTargetView() : nullptr; + SwWrtShell* pTargetShell = nullptr; + SfxObjectShellRef xTargetDocShell; + rtl::Reference<SwDoc> pTargetDoc; + + std::unique_ptr< utl::TempFileNamed > aTempFile; + sal_uInt16 nStartingPageNo = 0; + + std::shared_ptr<weld::GenericDialogController> xProgressDlg; + + try + { + vcl::Window *pSourceWindow = nullptr; + if( !bIsMergeSilent ) + { + // construct the process dialog + pSourceWindow = &pSourceShell->GetView().GetEditWin(); + if (!bMT_PRINTER) + xProgressDlg = std::make_shared<CreateMonitor>(pSourceWindow->GetFrameWeld()); + else + { + xProgressDlg = std::make_shared<PrintMonitor>(pSourceWindow->GetFrameWeld()); + static_cast<PrintMonitor*>(xProgressDlg.get())->set_title( + pSourceDocSh->GetTitle(22)); + } + weld::DialogController::runAsync(xProgressDlg, [this, &xProgressDlg](sal_Int32 nResult){ + if (nResult == RET_CANCEL) + MergeCancel(); + xProgressDlg.reset(); + }); + + Application::Reschedule( true ); + } + + if( bCreateSingleFile && !pTargetView ) + { + // create a target docshell to put the merged document into + xTargetDocShell = lcl_CreateWorkingDocument( WorkingDocType::TARGET, + *pSourceShell, bMT_SHELL ? pSourceWindow : nullptr, + nullptr, &pTargetView, &pTargetShell, &pTargetDoc ); + + if (nMaxDumpDocs) + lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" ); + } + else if( pTargetView ) + { + pTargetShell = pTargetView->GetWrtShellPtr(); + if (pTargetShell) + { + pTargetDoc = pTargetShell->GetDoc(); + xTargetDocShell = pTargetView->GetDocShell(); + } + } + + if( bCreateSingleFile ) + { + // determine the page style and number used at the start of the source document + pSourceShell->SttEndDoc(true); + nStartingPageNo = pSourceShell->GetVirtPageNum(); + } + + // Progress, to prohibit KeyInputs + SfxProgress aProgress(pSourceDocSh, OUString(), 1); + + // lock all dispatchers + SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh); + while (pViewFrame) + { + pViewFrame->GetDispatcher()->Lock(true); + pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh); + } + + sal_Int32 nDocNo = 1; + + // For single file mode, the number of pages in the target document so far, which is used + // by AppendDoc() to adjust position of page-bound objects. Getting this information directly + // from the target doc would require repeated layouts of the doc, which is expensive, but + // it can be manually computed from the source documents (for which we do layouts, so the page + // count is known, and there is a blank page between each of them in the target document). + int targetDocPageCount = 0; + + if( !bIsMergeSilent && !bMT_PRINTER ) + { + sal_Int32 nRecordCount = 1; + lcl_getCountFromResultSet( nRecordCount, m_pImpl->pMergeData.get() ); + + // Synchronized docs don't auto-advance the record set, but there is a + // "security" check, which will always advance the record set, if there + // is no "next record" field in a synchronized doc => nRecordPerDoc > 0 + sal_Int32 nRecordPerDoc = pSourceShell->GetDoc() + ->getIDocumentFieldsAccess().GetRecordsPerDocument(); + if ( bSynchronizedDoc && (nRecordPerDoc > 1) ) + --nRecordPerDoc; + assert( nRecordPerDoc > 0 ); + + sal_Int32 nMaxDocs = nRecordCount / nRecordPerDoc; + if ( 0 != nRecordCount % nRecordPerDoc ) + nMaxDocs += 1; + static_cast<CreateMonitor*>(xProgressDlg.get())->SetTotalCount(nMaxDocs); + } + + sal_Int32 nStartRow, nEndRow; + bool bFreezedLayouts = false; + // to collect temporary email files + std::vector< OUString> aFilesToRemove; + + // The SfxObjectShell will be closed explicitly later but + // it is more safe to use SfxObjectShellLock here + SfxObjectShellLock xWorkDocSh; + SwView* pWorkView = nullptr; + rtl::Reference<SwDoc> pWorkDoc; + SwDBManager* pWorkDocOrigDBManager = nullptr; + SwWrtShell* pWorkShell = nullptr; + bool bWorkDocInitialized = false; + + do + { + nStartRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0; + + OUString sColumnData; + + // Read the indicated data column, which should contain a valid mail + // address or an optional file name + if( bMT_EMAIL || bColumnName ) + { + sColumnData = GetDBField( xColumnProp, aColumnDBFormat ); + } + + // create a new temporary file name - only done once in case of bCreateSingleFile + if( bNeedsTempFiles && ( !bWorkDocInitialized || !bCreateSingleFile )) + { + OUString sPrefix = sDescriptorPrefix; + OUString sLeading; + + //#i97667# if the name is from a database field then it will be used _as is_ + if( bColumnName && !bMT_EMAIL ) + { + if (!sColumnData.isEmpty()) + sLeading = sColumnData; + else + sLeading = "_"; + } + else + { + INetURLObject aEntry( sPrefix ); + sLeading = aEntry.GetBase(); + aEntry.removeSegment(); + sPrefix = aEntry.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + OUString sExt(comphelper::string::stripStart(pStoreToFilter->GetDefaultExtension(), '*')); + aTempFile.reset( new utl::TempFileNamed(sLeading, sColumnData.isEmpty(), sExt, &sPrefix, true) ); + if( !aTempFile->IsValid() ) + { + ErrorHandler::HandleError( ERRCODE_IO_NOTSUPPORTED ); + m_aMergeStatus = MergeStatus::Error; + } + } + + uno::Sequence< beans::PropertyValue > aSaveToFilterDataOptions( rMergeDescriptor.aSaveToFilterData ); + + if( bMT_EMAIL || bPasswordColumnName ) + { + OUString sPasswordColumnData = GetDBField( xPasswordColumnProp, aColumnDBFormat ); + lcl_PrepareSaveFilterDataOptions( rMergeDescriptor.aSaveToFilterData, aSaveToFilterDataOptions, sPasswordColumnData ); + } + + if( IsMergeOk() ) + { + std::unique_ptr< INetURLObject > aTempFileURL; + if( bNeedsTempFiles ) + aTempFileURL.reset( new INetURLObject(aTempFile->GetURL())); + if( !bIsMergeSilent ) { + if( !bMT_PRINTER ) + static_cast<CreateMonitor*>(xProgressDlg.get())->SetCurrentPosition(nDocNo); + else { + PrintMonitor *pPrintMonDlg = static_cast<PrintMonitor*>(xProgressDlg.get()); + pPrintMonDlg->m_xPrinter->set_label(bNeedsTempFiles + ? aTempFileURL->GetBase() : pSourceDocSh->GetTitle( 2)); + OUString sStat = SwResId(STR_STATSTR_LETTER) + " " + OUString::number( nDocNo ); + pPrintMonDlg->m_xPrintInfo->set_label(sStat); + } + //TODO xProgressDlg->queue_draw(); + } + + Scheduler::ProcessEventsToIdle(); + + // Create a copy of the source document and work with that one instead of the source. + // If we're not in the single file mode (which requires modifying the document for the merging), + // it is enough to do this just once. Currently PDF also has to be treated special. + if( !bWorkDocInitialized || bCreateSingleFile || bIsPDFexport || bIsMultiFile ) + { + assert( !xWorkDocSh.Is()); + pWorkDocOrigDBManager = this; + xWorkDocSh = lcl_CreateWorkingDocument( WorkingDocType::COPY, + *pSourceShell, nullptr, &pWorkDocOrigDBManager, + &pWorkView, &pWorkShell, &pWorkDoc ); + if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) ) + lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo ); + + // #i69458# lock fields to prevent access to the result set while calculating layout + // tdf#92324: and do not unlock: keep document locked during printing to avoid + // ExpFields update during printing, generation of preview, etc. + pWorkShell->LockExpFields(); + pWorkShell->CalcLayout(); + // tdf#121168: Now force correct page descriptions applied to page frames. Without + // this, e.g., page frames starting with sections could have page descriptions set + // wrong. This would lead to wrong page styles applied in SwDoc::AppendDoc below. + pWorkShell->GetViewOptions()->SetIdle(true); + for (auto aLayout : pWorkShell->GetDoc()->GetAllLayouts()) + { + aLayout->FreezeLayout(false); + aLayout->AllCheckPageDescs(); + } + } + + lcl_emitEvent(SfxEventHintId::SwEventFieldMerge, STR_SW_EVENT_FIELD_MERGE, xWorkDocSh); + + // tdf#92324: Allow ExpFields update only by explicit instruction to avoid + // database cursor movement on any other fields update, for example during + // print preview and other operations + if ( pWorkShell->IsExpFieldsLocked() ) + pWorkShell->UnlockExpFields(); + pWorkShell->SwViewShell::UpdateFields(); + pWorkShell->LockExpFields(); + + lcl_emitEvent(SfxEventHintId::SwEventFieldMergeFinished, STR_SW_EVENT_FIELD_MERGE_FINISHED, xWorkDocSh); + + // also emit MailMergeEvent on XInterface if possible + const SwXMailMerge *pEvtSrc = GetMailMergeEvtSrc(); + if(pEvtSrc) + { + rtl::Reference< SwXMailMerge > xRef( + const_cast<SwXMailMerge*>(pEvtSrc) ); + text::MailMergeEvent aEvt( static_cast<text::XMailMergeBroadcaster*>(xRef.get()), xWorkDocSh->GetModel() ); + SolarMutexReleaser rel; + xRef->LaunchMailMergeEvent( aEvt ); + } + + // working copy is merged - prepare final steps depending on merge options + + if( bCreateSingleFile ) + { + assert( pTargetShell && "no target shell available!" ); + + // prepare working copy and target to append + + pWorkDoc->RemoveInvisibleContent(); + // remove of invisible content has influence on page count and so on fields for page count, + // therefore layout has to be updated before fields are converted to text + pWorkShell->CalcLayout(); + pWorkShell->ConvertFieldsToText(); + pWorkShell->SetNumberingRestart(); + if( bSynchronizedDoc ) + { + lcl_RemoveSectionLinks( *pWorkShell ); + } + + if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) ) + lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo ); + + // append the working document to the target document + if( targetDocPageCount % 2 == 1 ) + ++targetDocPageCount; // Docs always start on odd pages (so offset must be even). + SwNodeIndex appendedDocStart = pTargetDoc->AppendDoc( *pWorkDoc, + nStartingPageNo, !bWorkDocInitialized, targetDocPageCount, nDocNo); + targetDocPageCount += pWorkShell->GetPageCnt(); + + if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) ) + lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" ); + + if (bMT_SHELL) + { + SwDocMergeInfo aMergeInfo; + // Name of the mark is actually irrelevant, UNO bookmarks have internals names. + aMergeInfo.startPageInTarget = pTargetDoc->getIDocumentMarkAccess()->makeMark( + SwPaM(appendedDocStart), "", IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + aMergeInfo.nDBRow = nStartRow; + rMergeDescriptor.pMailMergeConfigItem->AddMergedDocument( aMergeInfo ); + } + } + else + { + assert( bNeedsTempFiles ); + assert( pWorkShell->IsExpFieldsLocked() ); + + if (bIsMultiFile && pWorkDoc->HasInvisibleContent()) + { + pWorkDoc->RemoveInvisibleContent(); + pWorkShell->CalcLayout(); + pWorkShell->ConvertFieldsToText(); + } + + // fields are locked, so it's fine to + // restore the old / empty DB manager for save + pWorkDoc->SetDBManager( pWorkDocOrigDBManager ); + + // save merged document + OUString sFileURL; + if( !lcl_SaveDoc( aTempFileURL.get(), pStoreToFilter, pStoreToFilterOptions, + &aSaveToFilterDataOptions, bIsPDFexport, + xWorkDocSh, *pWorkShell, &sFileURL ) ) + { + m_aMergeStatus = MergeStatus::Error; + } + + // back to the MM DB manager + pWorkDoc->SetDBManager( this ); + + if( bMT_EMAIL && !IsMergeError() ) + { + // schedule file for later removal + aFilesToRemove.push_back( sFileURL ); + + if( !SwMailMergeHelper::CheckMailAddress( sColumnData ) ) + { + OSL_FAIL("invalid e-Mail address in database column"); + } + else + { + uno::Reference< mail::XMailMessage > xMessage = lcl_CreateMailFromDoc( + rMergeDescriptor, sFileURL, sColumnData, sMailBodyMimeType, + sMailEncoding, pStoreToFilter->GetMimeType() ); + if( xMessage.is() ) + { + std::unique_lock aGuard( m_pImpl->m_aAllEmailSendMutex ); + m_pImpl->m_xLastMessage.set( xMessage ); + xMailDispatcher->enqueueMailMessage( xMessage ); + if( !xMailDispatcher->isStarted() ) + xMailDispatcher->start(); + } + } + } + } + if( bCreateSingleFile || bIsPDFexport || bIsMultiFile) + { + pWorkDoc->SetDBManager( pWorkDocOrigDBManager ); + pWorkDoc.clear(); + xWorkDocSh->DoClose(); + xWorkDocSh = nullptr; + } + } + + bWorkDocInitialized = true; + nDocNo++; + nEndRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0; + + // Freeze the layouts of the target document after the first inserted + // sub-document, to get the correct PageDesc. + if(!bFreezedLayouts && bCreateSingleFile) + { + for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() ) + aLayout->FreezeLayout(true); + bFreezedLayouts = true; + } + } while( IsMergeOk() && + ((bSynchronizedDoc && (nStartRow != nEndRow)) ? IsValidMergeRecord() : ToNextMergeRecord())); + + if ( xWorkDocSh.Is() && pWorkView->GetWrtShell().IsExpFieldsLocked() ) + { + // Unlock document fields after merge complete + pWorkView->GetWrtShell().UnlockExpFields(); + } + + if( !bCreateSingleFile ) + { + if( bMT_PRINTER ) + Printer::FinishPrintJob( pWorkView->GetPrinterController()); + if( !bIsPDFexport ) + { + if (pWorkDoc) + pWorkDoc->SetDBManager(pWorkDocOrigDBManager); + if (xWorkDocSh.Is()) + xWorkDocSh->DoClose(); + } + } + else if( IsMergeOk() ) // && bCreateSingleFile + { + Application::Reschedule( true ); + + // sw::DocumentLayoutManager::CopyLayoutFormat() did not generate + // unique fly names, do it here once. + pTargetDoc->SetInMailMerge(false); + pTargetDoc->SetAllUniqueFlyNames(); + + // Unfreeze target document layouts and correct all PageDescs. + SAL_INFO( "sw.pageframe", "(MergeMailFiles pTargetShell->CalcLayout in" ); + pTargetShell->CalcLayout(); + SAL_INFO( "sw.pageframe", "MergeMailFiles pTargetShell->CalcLayout out)" ); + pTargetShell->GetViewOptions()->SetIdle( true ); + pTargetDoc->GetIDocumentUndoRedo().DoUndo( true ); + for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() ) + { + aLayout->FreezeLayout(false); + aLayout->AllCheckPageDescs(); + } + + Application::Reschedule( true ); + + if( IsMergeOk() && bMT_FILE ) + { + // save merged document + assert( aTempFile ); + INetURLObject aTempFileURL; + if (sDescriptorPrefix.isEmpty() || !rMergeDescriptor.bPrefixIsFilename) + aTempFileURL.SetURL( aTempFile->GetURL() ); + else + { + aTempFileURL.SetURL(sDescriptorPrefix); + // remove the unneeded temporary file + aTempFile->EnableKillingFile(); + } + if( !lcl_SaveDoc( &aTempFileURL, pStoreToFilter, + pStoreToFilterOptions, &rMergeDescriptor.aSaveToFilterData, + bIsPDFexport, xTargetDocShell.get(), *pTargetShell ) ) + { + m_aMergeStatus = MergeStatus::Error; + } + } + else if( IsMergeOk() && bMT_PRINTER ) + { + // print the target document + uno::Sequence< beans::PropertyValue > aOptions( rMergeDescriptor.aPrintOptions ); + lcl_PreparePrinterOptions( rMergeDescriptor.aPrintOptions, aOptions ); + pTargetView->ExecPrint( aOptions, bIsMergeSilent, false/*bPrintAsync*/ ); + } + } + + // we also show canceled documents, as long as there was no error + if( !IsMergeError() && bMT_SHELL ) + // leave docshell available for caller (e.g. MM wizard) + rMergeDescriptor.pMailMergeConfigItem->SetTargetView( pTargetView ); + else if( xTargetDocShell.is() ) + xTargetDocShell->DoClose(); + + Application::Reschedule( true ); + + if (xProgressDlg) + { + xProgressDlg->response(RET_OK); + } + + // unlock all dispatchers + pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh); + while (pViewFrame) + { + pViewFrame->GetDispatcher()->Lock(false); + pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh); + } + + SW_MOD()->SetView(&pSourceShell->GetView()); + + if( xMailDispatcher.is() ) + { + if( IsMergeOk() ) + { + // TODO: Instead of polling via an AutoTimer, post an Idle event, + // if the main loop has been made thread-safe. + AutoTimer aEmailDispatcherPollTimer("sw::SwDBManager aEmailDispatcherPollTimer"); + aEmailDispatcherPollTimer.SetTimeout( 500 ); + aEmailDispatcherPollTimer.Start(); + while( IsMergeOk() && m_pImpl->m_xLastMessage.is() && !Application::IsQuit()) + Application::Yield(); + aEmailDispatcherPollTimer.Stop(); + } + xMailDispatcher->stop(); + xMailDispatcher->shutdown(); + } + + // remove the temporary files + // has to be done after xMailDispatcher is finished, as mails may be + // delivered as message attachments! + for( const OUString &sFileURL : aFilesToRemove ) + SWUnoHelper::UCB_DeleteFile( sFileURL ); + } + catch (const uno::Exception&) + { + if (xProgressDlg) + { + xProgressDlg->response(RET_CANCEL); + } + } + + return !IsMergeError(); +} + +void SwDBManager::MergeCancel() +{ + if (m_aMergeStatus < MergeStatus::Cancel) + m_aMergeStatus = MergeStatus::Cancel; +} + +// determine the column's Numberformat and transfer to the forwarded Formatter, +// if applicable. +sal_uInt32 SwDBManager::GetColumnFormat( const OUString& rDBName, + const OUString& rTableName, + const OUString& rColNm, + SvNumberFormatter* pNFormatr, + LanguageType nLanguage ) +{ + sal_uInt32 nRet = 0; + if(pNFormatr) + { + uno::Reference< sdbc::XDataSource> xSource; + uno::Reference< sdbc::XConnection> xConnection; + bool bUseMergeData = false; + uno::Reference< sdbcx::XColumnsSupplier> xColsSupp; + bool bDisposeConnection = false; + if(m_pImpl->pMergeData && + ((m_pImpl->pMergeData->sDataSource == rDBName && m_pImpl->pMergeData->sCommand == rTableName) || + (rDBName.isEmpty() && rTableName.isEmpty()))) + { + xConnection = m_pImpl->pMergeData->xConnection; + xSource = SwDBManager::getDataSourceAsParent(xConnection,rDBName); + bUseMergeData = true; + xColsSupp.set(m_pImpl->pMergeData->xResultSet, css::uno::UNO_QUERY); + } + if(!xConnection.is()) + { + SwDBData aData; + aData.sDataSource = rDBName; + aData.sCommand = rTableName; + aData.nCommandType = -1; + SwDSParam* pParam = FindDSData(aData, false); + if(pParam && pParam->xConnection.is()) + { + xConnection = pParam->xConnection; + xColsSupp.set(pParam->xResultSet, css::uno::UNO_QUERY); + } + else + { + xConnection = RegisterConnection( rDBName ); + bDisposeConnection = true; + } + if(bUseMergeData) + m_pImpl->pMergeData->xConnection = xConnection; + } + bool bDispose = !xColsSupp.is(); + if(bDispose) + { + xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName); + } + if(xColsSupp.is()) + { + uno::Reference<container::XNameAccess> xCols; + try + { + xCols = xColsSupp->getColumns(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in getColumns()"); + } + if(!xCols.is() || !xCols->hasByName(rColNm)) + return nRet; + uno::Any aCol = xCols->getByName(rColNm); + uno::Reference< beans::XPropertySet > xColumn; + aCol >>= xColumn; + nRet = GetColumnFormat(xSource, xConnection, xColumn, pNFormatr, nLanguage); + if(bDispose) + { + ::comphelper::disposeComponent( xColsSupp ); + } + if(bDisposeConnection) + { + ::comphelper::disposeComponent( xConnection ); + } + } + else + nRet = pNFormatr->GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_SYSTEM ); + } + return nRet; +} + +sal_uInt32 SwDBManager::GetColumnFormat( uno::Reference< sdbc::XDataSource> const & xSource_in, + uno::Reference< sdbc::XConnection> const & xConnection, + uno::Reference< beans::XPropertySet> const & xColumn, + SvNumberFormatter* pNFormatr, + LanguageType nLanguage ) +{ + auto xSource = xSource_in; + + // set the NumberFormat in the doc if applicable + sal_uInt32 nRet = 0; + + if(!xSource.is()) + { + uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY); + if ( xChild.is() ) + xSource.set(xChild->getParent(), uno::UNO_QUERY); + } + if(xSource.is() && xConnection.is() && xColumn.is() && pNFormatr) + { + rtl::Reference<SvNumberFormatsSupplierObj> pNumFormat = new SvNumberFormatsSupplierObj( pNFormatr ); + uno::Reference< util::XNumberFormats > xDocNumberFormats = pNumFormat->getNumberFormats(); + uno::Reference< util::XNumberFormatTypes > xDocNumberFormatTypes(xDocNumberFormats, uno::UNO_QUERY); + + css::lang::Locale aLocale( LanguageTag( nLanguage ).getLocale()); + + //get the number formatter of the data source + uno::Reference<beans::XPropertySet> xSourceProps(xSource, uno::UNO_QUERY); + uno::Reference< util::XNumberFormats > xNumberFormats; + if(xSourceProps.is()) + { + uno::Any aFormats = xSourceProps->getPropertyValue("NumberFormatsSupplier"); + if(aFormats.hasValue()) + { + uno::Reference<util::XNumberFormatsSupplier> xSuppl; + aFormats >>= xSuppl; + if(xSuppl.is()) + { + xNumberFormats = xSuppl->getNumberFormats(); + } + } + } + bool bUseDefault = true; + try + { + uno::Any aFormatKey = xColumn->getPropertyValue("FormatKey"); + if(aFormatKey.hasValue()) + { + sal_Int32 nFormat = 0; + aFormatKey >>= nFormat; + if(xNumberFormats.is()) + { + try + { + uno::Reference<beans::XPropertySet> xNumProps = xNumberFormats->getByKey( nFormat ); + uno::Any aFormatString = xNumProps->getPropertyValue("FormatString"); + uno::Any aLocaleVal = xNumProps->getPropertyValue("Locale"); + OUString sFormat; + aFormatString >>= sFormat; + lang::Locale aLoc; + aLocaleVal >>= aLoc; + nFormat = xDocNumberFormats->queryKey( sFormat, aLoc, false ); + if(NUMBERFORMAT_ENTRY_NOT_FOUND == sal::static_int_cast< sal_uInt32, sal_Int32>(nFormat)) + nFormat = xDocNumberFormats->addNew( sFormat, aLoc ); + + nRet = nFormat; + bUseDefault = false; + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", "illegal number format key"); + } + } + } + } + catch(const uno::Exception&) + { + SAL_WARN("sw.mailmerge", "no FormatKey property found"); + } + if(bUseDefault) + nRet = dbtools::getDefaultNumberFormat(xColumn, xDocNumberFormatTypes, aLocale); + } + return nRet; +} + +sal_Int32 SwDBManager::GetColumnType( const OUString& rDBName, + const OUString& rTableName, + const OUString& rColNm ) +{ + sal_Int32 nRet = sdbc::DataType::SQLNULL; + SwDBData aData; + aData.sDataSource = rDBName; + aData.sCommand = rTableName; + aData.nCommandType = -1; + SwDSParam* pParam = FindDSData(aData, false); + uno::Reference< sdbc::XConnection> xConnection; + uno::Reference< sdbcx::XColumnsSupplier > xColsSupp; + bool bDispose = false; + if(pParam && pParam->xConnection.is()) + { + xConnection = pParam->xConnection; + xColsSupp.set( pParam->xResultSet, uno::UNO_QUERY ); + } + else + { + xConnection = RegisterConnection( rDBName ); + } + if( !xColsSupp.is() ) + { + xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName); + bDispose = true; + } + if(xColsSupp.is()) + { + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + if(xCols->hasByName(rColNm)) + { + uno::Any aCol = xCols->getByName(rColNm); + uno::Reference<beans::XPropertySet> xCol; + aCol >>= xCol; + uno::Any aType = xCol->getPropertyValue("Type"); + aType >>= nRet; + } + if(bDispose) + ::comphelper::disposeComponent( xColsSupp ); + } + return nRet; +} + +uno::Reference< sdbc::XConnection> SwDBManager::GetConnection(const OUString& rDataSource, + uno::Reference<sdbc::XDataSource>& rxSource, const SwView *pView) +{ + uno::Reference< sdbc::XConnection> xConnection; + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + try + { + uno::Reference<sdb::XCompletedConnection> xComplConnection(dbtools::getDataSource(rDataSource, xContext), uno::UNO_QUERY); + if ( xComplConnection.is() ) + { + rxSource.set(xComplConnection, uno::UNO_QUERY); + weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr; + uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(xContext, pWindow ? pWindow->GetXWindow() : nullptr), uno::UNO_QUERY_THROW ); + xConnection = xComplConnection->connectWithCompletion( xHandler ); + } + } + catch(const uno::Exception&) + { + } + + return xConnection; +} + +uno::Reference< sdbcx::XColumnsSupplier> SwDBManager::GetColumnSupplier(uno::Reference<sdbc::XConnection> const & xConnection, + const OUString& rTableOrQuery, + SwDBSelect eTableOrQuery) +{ + uno::Reference< sdbcx::XColumnsSupplier> xRet; + try + { + if(eTableOrQuery == SwDBSelect::UNKNOWN) + { + //search for a table with the given command name + uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY); + if(xTSupplier.is()) + { + uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables(); + eTableOrQuery = xTables->hasByName(rTableOrQuery) ? + SwDBSelect::TABLE : SwDBSelect::QUERY; + } + } + sal_Int32 nCommandType = SwDBSelect::TABLE == eTableOrQuery ? + sdb::CommandType::TABLE : sdb::CommandType::QUERY; + uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() ); + uno::Reference<sdbc::XRowSet> xRowSet(xMgr->createInstance("com.sun.star.sdb.RowSet"), uno::UNO_QUERY); + + OUString sDataSource; + uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(xConnection, sDataSource); + uno::Reference<beans::XPropertySet> xSourceProperties(xSource, uno::UNO_QUERY); + if(xSourceProperties.is()) + { + xSourceProperties->getPropertyValue("Name") >>= sDataSource; + } + + uno::Reference<beans::XPropertySet> xRowProperties(xRowSet, uno::UNO_QUERY); + xRowProperties->setPropertyValue("DataSourceName", uno::Any(sDataSource)); + xRowProperties->setPropertyValue("Command", uno::Any(rTableOrQuery)); + xRowProperties->setPropertyValue("CommandType", uno::Any(nCommandType)); + xRowProperties->setPropertyValue("FetchSize", uno::Any(sal_Int32(10))); + xRowProperties->setPropertyValue("ActiveConnection", uno::Any(xConnection)); + xRowSet->execute(); + xRet.set( xRowSet, uno::UNO_QUERY ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in SwDBManager::GetColumnSupplier"); + } + + return xRet; +} + +OUString SwDBManager::GetDBField(uno::Reference<beans::XPropertySet> const & xColumnProps, + const SwDBFormatData& rDBFormatData, + double* pNumber) +{ + uno::Reference< sdb::XColumn > xColumn(xColumnProps, uno::UNO_QUERY); + OUString sRet; + assert( xColumn.is() && "SwDBManager::::ImportDBField: illegal arguments" ); + if(!xColumn.is()) + return sRet; + + uno::Any aType = xColumnProps->getPropertyValue("Type"); + sal_Int32 eDataType = sdbc::DataType::SQLNULL; + aType >>= eDataType; + switch(eDataType) + { + case sdbc::DataType::CHAR: + case sdbc::DataType::VARCHAR: + case sdbc::DataType::LONGVARCHAR: + try + { + sRet = xColumn->getString(); + sRet = sRet.replace( '\xb', '\n' ); // MSWord uses \xb as a newline + } + catch(const sdbc::SQLException&) + { + } + break; + case sdbc::DataType::BIT: + case sdbc::DataType::BOOLEAN: + case sdbc::DataType::TINYINT: + case sdbc::DataType::SMALLINT: + case sdbc::DataType::INTEGER: + case sdbc::DataType::BIGINT: + case sdbc::DataType::FLOAT: + case sdbc::DataType::REAL: + case sdbc::DataType::DOUBLE: + case sdbc::DataType::NUMERIC: + case sdbc::DataType::DECIMAL: + case sdbc::DataType::DATE: + case sdbc::DataType::TIME: + case sdbc::DataType::TIMESTAMP: + { + + try + { + sRet = dbtools::DBTypeConversion::getFormattedValue( + xColumnProps, + rDBFormatData.xFormatter, + rDBFormatData.aLocale, + rDBFormatData.aNullDate); + if (pNumber) + { + double fVal = xColumn->getDouble(); + if(!xColumn->wasNull()) + { + *pNumber = fVal; + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", ""); + } + + } + break; + } + + return sRet; +} + +// checks if a desired data source table or query is open +bool SwDBManager::IsDataSourceOpen(const OUString& rDataSource, + const OUString& rTableOrQuery, bool bMergeShell) +{ + if(m_pImpl->pMergeData) + { + return ((rDataSource == m_pImpl->pMergeData->sDataSource + && rTableOrQuery == m_pImpl->pMergeData->sCommand) + || (rDataSource.isEmpty() && rTableOrQuery.isEmpty())) + && m_pImpl->pMergeData->xResultSet.is(); + } + else if(!bMergeShell) + { + SwDBData aData; + aData.sDataSource = rDataSource; + aData.sCommand = rTableOrQuery; + aData.nCommandType = -1; + SwDSParam* pFound = FindDSData(aData, false); + return (pFound && pFound->xResultSet.is()); + } + return false; +} + +// read column data at a specified position +bool SwDBManager::GetColumnCnt(const OUString& rSourceName, const OUString& rTableName, + const OUString& rColumnName, sal_uInt32 nAbsRecordId, + LanguageType nLanguage, + OUString& rResult, double* pNumber) +{ + bool bRet = false; + SwDSParam* pFound = nullptr; + //check if it's the merge data source + if(m_pImpl->pMergeData && + rSourceName == m_pImpl->pMergeData->sDataSource && + rTableName == m_pImpl->pMergeData->sCommand) + { + pFound = m_pImpl->pMergeData.get(); + } + else + { + SwDBData aData; + aData.sDataSource = rSourceName; + aData.sCommand = rTableName; + aData.nCommandType = -1; + pFound = FindDSData(aData, false); + } + if (!pFound) + return false; + //check validity of supplied record Id + if(pFound->aSelection.hasElements()) + { + //the destination has to be an element of the selection + bool bFound = std::any_of(std::cbegin(pFound->aSelection), std::cend(pFound->aSelection), + [nAbsRecordId](const uno::Any& rSelection) { + sal_Int32 nSelection = 0; + rSelection >>= nSelection; + return nSelection == static_cast<sal_Int32>(nAbsRecordId); + }); + if(!bFound) + return false; + } + if( pFound->HasValidRecord() ) + { + sal_Int32 nOldRow = 0; + try + { + nOldRow = pFound->xResultSet->getRow(); + } + catch(const uno::Exception&) + { + return false; + } + //position to the desired index + bool bMove = true; + if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) ) + bMove = lcl_MoveAbsolute(pFound, nAbsRecordId); + if(bMove) + bRet = lcl_GetColumnCnt(pFound, rColumnName, nLanguage, rResult, pNumber); + if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) ) + lcl_MoveAbsolute(pFound, nOldRow); + } + return bRet; +} + +// reads the column data at the current position +bool SwDBManager::GetMergeColumnCnt(const OUString& rColumnName, LanguageType nLanguage, + OUString &rResult, double *pNumber) +{ + if( !IsValidMergeRecord() ) + { + rResult.clear(); + return false; + } + + bool bRet = lcl_GetColumnCnt(m_pImpl->pMergeData.get(), rColumnName, nLanguage, rResult, pNumber); + return bRet; +} + +bool SwDBManager::ToNextMergeRecord() +{ + assert( m_pImpl->pMergeData && m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" ); + return lcl_ToNextRecord( m_pImpl->pMergeData.get() ); +} + +bool SwDBManager::FillCalcWithMergeData( SvNumberFormatter *pDocFormatter, + LanguageType nLanguage, SwCalc &rCalc ) +{ + if( !IsValidMergeRecord() ) + return false; + + uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY ); + if( !xColsSupp.is() ) + return false; + + { + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + const uno::Sequence<OUString> aColNames = xCols->getElementNames(); + OUString aString; + + // add the "record number" variable, as SwCalc::VarLook would. + rCalc.VarChange( GetAppCharClass().lowercase( + SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber) ), GetSelectedRecordId() ); + + for( const OUString& rColName : aColNames ) + { + // get the column type + sal_Int32 nColumnType = sdbc::DataType::SQLNULL; + uno::Any aCol = xCols->getByName( rColName ); + uno::Reference<beans::XPropertySet> xColumnProps; + aCol >>= xColumnProps; + uno::Any aType = xColumnProps->getPropertyValue( "Type" ); + aType >>= nColumnType; + double aNumber = DBL_MAX; + + lcl_GetColumnCnt( m_pImpl->pMergeData.get(), xColumnProps, nLanguage, aString, &aNumber ); + + sal_uInt32 nFormat = GetColumnFormat( m_pImpl->pMergeData->sDataSource, + m_pImpl->pMergeData->sCommand, + rColName, pDocFormatter, nLanguage ); + // aNumber is overwritten by SwDBField::FormatValue, so store initial status + bool colIsNumber = aNumber != DBL_MAX; + bool bValidValue = SwDBField::FormatValue( pDocFormatter, aString, nFormat, + aNumber, nColumnType ); + if( colIsNumber ) + { + if( bValidValue ) + { + SwSbxValue aValue; + aValue.PutDouble( aNumber ); + aValue.SetDBvalue( true ); + SAL_INFO( "sw.ui", "'" << rColName << "': " << aNumber << " / " << aString ); + rCalc.VarChange( rColName, aValue ); + } + } + else + { + SwSbxValue aValue; + aValue.PutString( aString ); + aValue.SetDBvalue( true ); + SAL_INFO( "sw.ui", "'" << rColName << "': " << aString ); + rCalc.VarChange( rColName, aValue ); + } + } + } + + return true; +} + +void SwDBManager::ToNextRecord( + const OUString& rDataSource, const OUString& rCommand) +{ + SwDSParam* pFound = nullptr; + if(m_pImpl->pMergeData && + rDataSource == m_pImpl->pMergeData->sDataSource && + rCommand == m_pImpl->pMergeData->sCommand) + { + pFound = m_pImpl->pMergeData.get(); + } + else + { + SwDBData aData; + aData.sDataSource = rDataSource; + aData.sCommand = rCommand; + aData.nCommandType = -1; + pFound = FindDSData(aData, false); + } + lcl_ToNextRecord( pFound ); +} + +static bool lcl_ToNextRecord( SwDSParam* pParam, const SwDBNextRecord action ) +{ + bool bRet = true; + + assert( SwDBNextRecord::NEXT == action || + (SwDBNextRecord::FIRST == action && pParam) ); + if( nullptr == pParam ) + return false; + + if( action == SwDBNextRecord::FIRST ) + { + pParam->nSelectionIndex = 0; + pParam->bEndOfDB = false; + } + + if( !pParam->HasValidRecord() ) + return false; + + try + { + if( pParam->aSelection.hasElements() ) + { + if( pParam->nSelectionIndex >= pParam->aSelection.getLength() ) + pParam->bEndOfDB = true; + else + { + sal_Int32 nPos = 0; + pParam->aSelection.getConstArray()[ pParam->nSelectionIndex ] >>= nPos; + pParam->bEndOfDB = !pParam->xResultSet->absolute( nPos ); + } + } + else if( action == SwDBNextRecord::FIRST ) + { + pParam->bEndOfDB = !pParam->xResultSet->first(); + } + else + { + sal_Int32 nBefore = pParam->xResultSet->getRow(); + pParam->bEndOfDB = !pParam->xResultSet->next(); + if( !pParam->bEndOfDB && nBefore == pParam->xResultSet->getRow() ) + { + // next returned true but it didn't move + ::dbtools::throwFunctionSequenceException( pParam->xResultSet ); + } + } + + ++pParam->nSelectionIndex; + bRet = !pParam->bEndOfDB; + } + catch( const uno::Exception & ) + { + // we allow merging with empty databases, so don't warn on init + TOOLS_WARN_EXCEPTION_IF(action == SwDBNextRecord::NEXT, + "sw.mailmerge", "exception in ToNextRecord()"); + pParam->bEndOfDB = true; + bRet = false; + } + return bRet; +} + +// synchronized labels contain a next record field at their end +// to assure that the next page can be created in mail merge +// the cursor position must be validated +bool SwDBManager::IsValidMergeRecord() const +{ + return( m_pImpl->pMergeData && m_pImpl->pMergeData->HasValidRecord() ); +} + +sal_uInt32 SwDBManager::GetSelectedRecordId() +{ + sal_uInt32 nRet = 0; + assert( m_pImpl->pMergeData && + m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" ); + if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is()) + return 0; + try + { + nRet = m_pImpl->pMergeData->xResultSet->getRow(); + } + catch(const uno::Exception&) + { + } + return nRet; +} + +bool SwDBManager::ToRecordId(sal_Int32 nSet) +{ + assert( m_pImpl->pMergeData && + m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" ); + if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is()|| nSet < 0) + return false; + bool bRet = false; + sal_Int32 nAbsPos = nSet; + assert(nAbsPos >= 0); + bRet = lcl_MoveAbsolute(m_pImpl->pMergeData.get(), nAbsPos); + m_pImpl->pMergeData->bEndOfDB = !bRet; + return bRet; +} + +bool SwDBManager::OpenDataSource(const OUString& rDataSource, const OUString& rTableOrQuery) +{ + SwDBData aData; + aData.sDataSource = rDataSource; + aData.sCommand = rTableOrQuery; + aData.nCommandType = -1; + + SwDSParam* pFound = FindDSData(aData, true); + if(pFound->xResultSet.is()) + return true; + SwDSParam* pParam = FindDSConnection(rDataSource, false); + if(pParam && pParam->xConnection.is()) + pFound->xConnection = pParam->xConnection; + if(pFound->xConnection.is()) + { + try + { + uno::Reference< sdbc::XDatabaseMetaData > xMetaData = pFound->xConnection->getMetaData(); + try + { + pFound->bScrollable = xMetaData + ->supportsResultSetType(sal_Int32(sdbc::ResultSetType::SCROLL_INSENSITIVE)); + } + catch(const uno::Exception&) + { + // DB driver may not be ODBC 3.0 compliant + pFound->bScrollable = true; + } + pFound->xStatement = pFound->xConnection->createStatement(); + OUString aQuoteChar = xMetaData->getIdentifierQuoteString(); + OUString sStatement = "SELECT * FROM " + aQuoteChar + rTableOrQuery + aQuoteChar; + pFound->xResultSet = pFound->xStatement->executeQuery( sStatement ); + + //after executeQuery the cursor must be positioned + pFound->bEndOfDB = !pFound->xResultSet->next(); + ++pFound->nSelectionIndex; + } + catch (const uno::Exception&) + { + pFound->xResultSet = nullptr; + pFound->xStatement = nullptr; + pFound->xConnection = nullptr; + } + } + return pFound->xResultSet.is(); +} + +uno::Reference< sdbc::XConnection> const & SwDBManager::RegisterConnection(OUString const& rDataSource) +{ + SwDSParam* pFound = SwDBManager::FindDSConnection(rDataSource, true); + uno::Reference< sdbc::XDataSource> xSource; + if(!pFound->xConnection.is()) + { + SwView* pView = (m_pDoc && m_pDoc->GetDocShell()) ? m_pDoc->GetDocShell()->GetView() : nullptr; + pFound->xConnection = SwDBManager::GetConnection(rDataSource, xSource, pView); + try + { + uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY); + if(xComponent.is()) + xComponent->addEventListener(m_pImpl->m_xDisposeListener); + } + catch(const uno::Exception&) + { + } + } + return pFound->xConnection; +} + +sal_uInt32 SwDBManager::GetSelectedRecordId( + const OUString& rDataSource, const OUString& rTableOrQuery, sal_Int32 nCommandType) +{ + sal_uInt32 nRet = 0xffffffff; + //check for merge data source first + if(m_pImpl->pMergeData && + ((rDataSource == m_pImpl->pMergeData->sDataSource && + rTableOrQuery == m_pImpl->pMergeData->sCommand) || + (rDataSource.isEmpty() && rTableOrQuery.isEmpty())) && + (nCommandType == -1 || nCommandType == m_pImpl->pMergeData->nCommandType) && + m_pImpl->pMergeData->xResultSet.is()) + { + nRet = GetSelectedRecordId(); + } + else + { + SwDBData aData; + aData.sDataSource = rDataSource; + aData.sCommand = rTableOrQuery; + aData.nCommandType = nCommandType; + SwDSParam* pFound = FindDSData(aData, false); + if(pFound && pFound->xResultSet.is()) + { + try + { //if a selection array is set the current row at the result set may not be set yet + if(pFound->aSelection.hasElements()) + { + sal_Int32 nSelIndex = pFound->nSelectionIndex; + if(nSelIndex >= pFound->aSelection.getLength()) + nSelIndex = pFound->aSelection.getLength() -1; + pFound->aSelection.getConstArray()[nSelIndex] >>= nRet; + + } + else + nRet = pFound->xResultSet->getRow(); + } + catch(const uno::Exception&) + { + } + } + } + return nRet; +} + +// close all data sources - after fields were updated +void SwDBManager::CloseAll(bool bIncludingMerge) +{ + //the only thing done here is to reset the selection index + //all connections stay open + for (auto & pParam : m_DataSourceParams) + { + if (bIncludingMerge || pParam.get() != m_pImpl->pMergeData.get()) + { + pParam->nSelectionIndex = 0; + pParam->bEndOfDB = false; + try + { + if(!m_bInMerge && pParam->xResultSet.is()) + pParam->xResultSet->first(); + } + catch(const uno::Exception&) + {} + } + } +} + +SwDSParam* SwDBManager::FindDSData(const SwDBData& rData, bool bCreate) +{ + //prefer merge data if available + if(m_pImpl->pMergeData && + ((rData.sDataSource == m_pImpl->pMergeData->sDataSource && + rData.sCommand == m_pImpl->pMergeData->sCommand) || + (rData.sDataSource.isEmpty() && rData.sCommand.isEmpty())) && + (rData.nCommandType == -1 || rData.nCommandType == m_pImpl->pMergeData->nCommandType || + (bCreate && m_pImpl->pMergeData->nCommandType == -1))) + { + return m_pImpl->pMergeData.get(); + } + + SwDSParam* pFound = nullptr; + for (size_t nPos = m_DataSourceParams.size(); nPos; nPos--) + { + SwDSParam* pParam = m_DataSourceParams[nPos - 1].get(); + if(rData.sDataSource == pParam->sDataSource && + rData.sCommand == pParam->sCommand && + (rData.nCommandType == -1 || rData.nCommandType == pParam->nCommandType || + (bCreate && pParam->nCommandType == -1))) + { + // calls from the calculator may add a connection with an invalid commandtype + //later added "real" data base connections have to re-use the already available + //DSData and set the correct CommandType + if(bCreate && pParam->nCommandType == -1) + pParam->nCommandType = rData.nCommandType; + pFound = pParam; + break; + } + } + if(bCreate && !pFound) + { + pFound = new SwDSParam(rData); + m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound)); + try + { + uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY); + if(xComponent.is()) + xComponent->addEventListener(m_pImpl->m_xDisposeListener); + } + catch(const uno::Exception&) + { + } + } + return pFound; +} + +SwDSParam* SwDBManager::FindDSConnection(const OUString& rDataSource, bool bCreate) +{ + //prefer merge data if available + if(m_pImpl->pMergeData && rDataSource == m_pImpl->pMergeData->sDataSource ) + { + SetAsUsed(rDataSource); + return m_pImpl->pMergeData.get(); + } + SwDSParam* pFound = nullptr; + for (const auto & pParam : m_DataSourceParams) + { + if(rDataSource == pParam->sDataSource) + { + SetAsUsed(rDataSource); + pFound = pParam.get(); + break; + } + } + if(bCreate && !pFound) + { + SwDBData aData; + aData.sDataSource = rDataSource; + pFound = new SwDSParam(aData); + SetAsUsed(rDataSource); + m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound)); + try + { + uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY); + if(xComponent.is()) + xComponent->addEventListener(m_pImpl->m_xDisposeListener); + } + catch(const uno::Exception&) + { + } + } + return pFound; +} + +const SwDBData& SwDBManager::GetAddressDBName() +{ + return SW_MOD()->GetDBConfig()->GetAddressSource(); +} + +uno::Sequence<OUString> SwDBManager::GetExistingDatabaseNames() +{ + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext); + return xDBContext->getElementNames(); +} + +namespace sw +{ +DBConnURIType GetDBunoType(const INetURLObject &rURL) +{ + OUString sExt(rURL.GetFileExtension()); + DBConnURIType type = DBConnURIType::UNKNOWN; + + if (sExt == "odb") + { + type = DBConnURIType::ODB; + } + else if (sExt.equalsIgnoreAsciiCase("sxc") + || sExt.equalsIgnoreAsciiCase("ods") + || sExt.equalsIgnoreAsciiCase("xls") + || sExt.equalsIgnoreAsciiCase("xlsx")) + { + type = DBConnURIType::CALC; + } + else if (sExt.equalsIgnoreAsciiCase("sxw") || sExt.equalsIgnoreAsciiCase("odt") || sExt.equalsIgnoreAsciiCase("doc") || sExt.equalsIgnoreAsciiCase("docx")) + { + type = DBConnURIType::WRITER; + } + else if (sExt.equalsIgnoreAsciiCase("dbf")) + { + type = DBConnURIType::DBASE; + } + else if (sExt.equalsIgnoreAsciiCase("csv") || sExt.equalsIgnoreAsciiCase("txt")) + { + type = DBConnURIType::FLAT; + } +#ifdef _WIN32 + else if (sExt.equalsIgnoreAsciiCase("mdb") || sExt.equalsIgnoreAsciiCase("mde")) + { + type = DBConnURIType::MSJET; + } + else if (sExt.equalsIgnoreAsciiCase("accdb") || sExt.equalsIgnoreAsciiCase("accde")) + { + type = DBConnURIType::MSACE; + } +#endif + return type; +} +} + +namespace +{ +uno::Any GetDBunoURI(const INetURLObject &rURL, DBConnURIType& rType) +{ + uno::Any aURLAny; + + if (rType == DBConnURIType::UNKNOWN) + rType = GetDBunoType(rURL); + + switch (rType) { + case DBConnURIType::UNKNOWN: + case DBConnURIType::ODB: + break; + case DBConnURIType::CALC: + { + OUString sDBURL = "sdbc:calc:" + + rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + aURLAny <<= sDBURL; + } + break; + case DBConnURIType::WRITER: + { + OUString sDBURL = "sdbc:writer:" + + rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + aURLAny <<= sDBURL; + } + break; + case DBConnURIType::DBASE: + { + INetURLObject aUrlTmp(rURL); + aUrlTmp.removeSegment(); + aUrlTmp.removeFinalSlash(); + OUString sDBURL = "sdbc:dbase:" + + aUrlTmp.GetMainURL(INetURLObject::DecodeMechanism::NONE); + aURLAny <<= sDBURL; + } + break; + case DBConnURIType::FLAT: + { + INetURLObject aUrlTmp(rURL); + aUrlTmp.removeSegment(); + aUrlTmp.removeFinalSlash(); + OUString sDBURL = "sdbc:flat:" + + //only the 'path' has to be added + aUrlTmp.GetMainURL(INetURLObject::DecodeMechanism::NONE); + aURLAny <<= sDBURL; + } + break; + case DBConnURIType::MSJET: +#ifdef _WIN32 + { + OUString sDBURL("sdbc:ado:access:PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + rURL.PathToFileName()); + aURLAny <<= sDBURL; + } +#endif + break; + case DBConnURIType::MSACE: +#ifdef _WIN32 + { + OUString sDBURL("sdbc:ado:PROVIDER=Microsoft.ACE.OLEDB.12.0;DATA SOURCE=" + rURL.PathToFileName()); + aURLAny <<= sDBURL; + } +#endif + break; + } + return aURLAny; +} + +/// Returns the URL of this SwDoc. +OUString getOwnURL(SfxObjectShell const * pDocShell) +{ + OUString aRet; + + if (!pDocShell) + return aRet; + + const INetURLObject& rURLObject = pDocShell->GetMedium()->GetURLObject(); + aRet = rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE); + return aRet; +} + +/** +Loads a data source from file and registers it. + +In case of success it returns the registered name, otherwise an empty string. +Optionally add a prefix to the registered DB name. +*/ +OUString LoadAndRegisterDataSource_Impl(DBConnURIType type, const uno::Reference< beans::XPropertySet > *pSettings, + const INetURLObject &rURL, const OUString *pDestDir, SfxObjectShell* pDocShell) +{ + OUString sExt(rURL.GetFileExtension()); + uno::Any aTableFilterAny; + uno::Any aSuppressVersionsAny; + uno::Any aInfoAny; + bool bStore = true; + OUString sFind; + + uno::Any aURLAny = GetDBunoURI(rURL, type); + switch (type) { + case DBConnURIType::UNKNOWN: + case DBConnURIType::CALC: + case DBConnURIType::WRITER: + break; + case DBConnURIType::ODB: + bStore = false; + break; + case DBConnURIType::FLAT: + case DBConnURIType::DBASE: + //set the filter to the file name without extension + { + uno::Sequence<OUString> aFilters { rURL.getBase(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset) }; + aTableFilterAny <<= aFilters; + } + break; + case DBConnURIType::MSJET: + case DBConnURIType::MSACE: + aSuppressVersionsAny <<= true; + break; + } + + try + { + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext); + + OUString sNewName = rURL.getName( + INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::Unambiguous); + sal_Int32 nExtLen = sExt.getLength(); + sNewName = sNewName.replaceAt(sNewName.getLength() - nExtLen - 1, nExtLen + 1, u""); + + //find a unique name if sNewName already exists + sFind = sNewName; + sal_Int32 nIndex = 0; + while (xDBContext->hasByName(sFind)) + sFind = sNewName + OUString::number(++nIndex); + + uno::Reference<uno::XInterface> xNewInstance; + if (!bStore) + { + //odb-file + uno::Any aDataSource = xDBContext->getByName(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + aDataSource >>= xNewInstance; + } + else + { + xNewInstance = xDBContext->createInstance(); + uno::Reference<beans::XPropertySet> xDataProperties(xNewInstance, uno::UNO_QUERY); + + if (aURLAny.hasValue()) + xDataProperties->setPropertyValue("URL", aURLAny); + if (aTableFilterAny.hasValue()) + xDataProperties->setPropertyValue("TableFilter", aTableFilterAny); + if (aSuppressVersionsAny.hasValue()) + xDataProperties->setPropertyValue("SuppressVersionColumns", aSuppressVersionsAny); + if (aInfoAny.hasValue()) + xDataProperties->setPropertyValue("Info", aInfoAny); + + if (DBConnURIType::FLAT == type && pSettings) + { + uno::Any aSettings = xDataProperties->getPropertyValue("Settings"); + uno::Reference < beans::XPropertySet > xDSSettings; + aSettings >>= xDSSettings; + ::comphelper::copyProperties(*pSettings, xDSSettings); + xDSSettings->setPropertyValue("Extension", uno::Any(sExt)); + } + + uno::Reference<sdb::XDocumentDataSource> xDS(xNewInstance, uno::UNO_QUERY_THROW); + uno::Reference<frame::XStorable> xStore(xDS->getDatabaseDocument(), uno::UNO_QUERY_THROW); + OUString aOwnURL = getOwnURL(pDocShell); + if (aOwnURL.isEmpty()) + { + // Cannot embed, as embedded data source would need the URL of the parent document. + OUString sHomePath(SvtPathOptions().GetWorkPath()); + const OUString sTmpName = utl::CreateTempURL(sNewName, true, u".odb", pDestDir ? pDestDir : &sHomePath); + xStore->storeAsURL(sTmpName, uno::Sequence<beans::PropertyValue>()); + } + else + { + // Embed. + OUString aStreamRelPath = "EmbeddedDatabase"; + uno::Reference<embed::XStorage> xStorage = pDocShell->GetStorage(); + + // Refer to the sub-storage name in the document settings, so + // we can load it again next time the file is imported. + uno::Reference<lang::XMultiServiceFactory> xFactory(pDocShell->GetModel(), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY); + xPropertySet->setPropertyValue("EmbeddedDatabaseName", uno::Any(aStreamRelPath)); + + // Store it only after setting the above property, so that only one data source gets registered. + SwDBManager::StoreEmbeddedDataSource(xStore, xStorage, aStreamRelPath, aOwnURL); + } + } + xDBContext->registerObject(sFind, xNewInstance); + } + catch (const uno::Exception&) + { + sFind.clear(); + } + return sFind; +} +} + +OUString SwDBManager::LoadAndRegisterDataSource(weld::Window* pParent, SwDocShell* pDocShell) +{ + sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, pParent); + aDlgHelper.SetContext(sfx2::FileDialogHelper::WriterRegisterDataSource); + uno::Reference < ui::dialogs::XFilePicker3 > xFP = aDlgHelper.GetFilePicker(); + + OUString sFilterAll(SwResId(STR_FILTER_ALL)); + OUString sFilterAllData(SwResId(STR_FILTER_ALL_DATA)); + + const std::vector<std::pair<OUString, OUString>> filters{ + { SwResId(STR_FILTER_SXB), "*.odb" }, + { SwResId(STR_FILTER_SXC), "*.ods;*.sxc" }, + { SwResId(STR_FILTER_SXW), "*.odt;*.sxw" }, + { SwResId(STR_FILTER_DBF), "*.dbf" }, + { SwResId(STR_FILTER_XLS), "*.xls;*.xlsx" }, + { SwResId(STR_FILTER_DOC), "*.doc;*.docx" }, + { SwResId(STR_FILTER_TXT), "*.txt" }, + { SwResId(STR_FILTER_CSV), "*.csv" }, +#ifdef _WIN32 + { SwResId(STR_FILTER_MDB), "*.mdb;*.mde" }, + { SwResId(STR_FILTER_ACCDB), "*.accdb;*.accde" }, +#endif + }; + + OUStringBuffer sAllDataFilter; + for (const auto& [name, filter] : filters) + { + (void)name; + if (!sAllDataFilter.isEmpty()) + sAllDataFilter.append(';'); + sAllDataFilter.append(filter); + } + + xFP->appendFilter( sFilterAll, "*" ); + xFP->appendFilter( sFilterAllData, sAllDataFilter.makeStringAndClear()); + + // Similar to sfx2::addExtension from sfx2/source/dialog/filtergrouping.cxx + for (const auto& [name, filter] : filters) + xFP->appendFilter(name + " (" + filter + ")", filter); + + xFP->setCurrentFilter( sFilterAll ) ; + OUString sFind; + if( ERRCODE_NONE == aDlgHelper.Execute() ) + { + uno::Reference< beans::XPropertySet > aSettings; + const INetURLObject aURL( xFP->getSelectedFiles()[0] ); + const DBConnURIType type = GetDBunoType( aURL ); + + if( DBConnURIType::FLAT == type ) + { + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference < sdb::XTextConnectionSettings > xSettingsDlg = sdb::TextConnectionSettings::create(xContext); + if( xSettingsDlg->execute() ) + aSettings.set( uno::Reference < beans::XPropertySet >( xSettingsDlg, uno::UNO_QUERY_THROW ) ); + } + sFind = LoadAndRegisterDataSource_Impl( type, DBConnURIType::FLAT == type ? &aSettings : nullptr, aURL, nullptr, pDocShell ); + + s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(pDocShell, sFind)); + } + return sFind; +} + +void SwDBManager::StoreEmbeddedDataSource(const uno::Reference<frame::XStorable>& xStorable, + const uno::Reference<embed::XStorage>& xStorage, + const OUString& rStreamRelPath, + const OUString& rOwnURL, bool bCopyTo) +{ + // Construct vnd.sun.star.pkg:// URL for later loading, and TargetStorage/StreamRelPath for storing. + OUString const sTmpName = ConstructVndSunStarPkgUrl(rOwnURL, rStreamRelPath); + + uno::Sequence<beans::PropertyValue> aSequence = comphelper::InitPropertySequence( + { + {"TargetStorage", uno::Any(xStorage)}, + {"StreamRelPath", uno::Any(rStreamRelPath)}, + {"BaseURI", uno::Any(rOwnURL)} + }); + if (bCopyTo) + xStorable->storeToURL(sTmpName, aSequence); + else + xStorable->storeAsURL(sTmpName, aSequence); +} + +OUString SwDBManager::LoadAndRegisterDataSource(std::u16string_view rURI, const OUString *pDestDir) +{ + return LoadAndRegisterDataSource_Impl( DBConnURIType::UNKNOWN, nullptr, INetURLObject(rURI), pDestDir, nullptr ); +} + +namespace +{ + // tdf#117824 switch the embedded database away from using its current storage and point it to temporary storage + // which allows the original storage to be deleted + void switchEmbeddedDatabaseStorage(const uno::Reference<sdb::XDatabaseContext>& rDatabaseContext, const OUString& rName) + { + uno::Reference<sdb::XDocumentDataSource> xDS(rDatabaseContext->getByName(rName), uno::UNO_QUERY); + if (!xDS) + return; + uno::Reference<document::XStorageBasedDocument> xStorageDoc(xDS->getDatabaseDocument(), uno::UNO_QUERY); + if (!xStorageDoc) + return; + xStorageDoc->switchToStorage(comphelper::OStorageHelper::GetTemporaryStorage()); + } +} + +void SwDBManager::RevokeDataSource(const OUString& rName) +{ + uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext()); + if (xDatabaseContext->hasByName(rName)) + { + switchEmbeddedDatabaseStorage(xDatabaseContext, rName); + xDatabaseContext->revokeObject(rName); + } +} + +void SwDBManager::LoadAndRegisterEmbeddedDataSource(const SwDBData& rData, const SwDocShell& rDocShell) +{ + uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext()); + + OUString sDataSource = rData.sDataSource; + + // Fallback, just in case the document would contain an embedded data source, but no DB fields. + if (sDataSource.isEmpty()) + sDataSource = "EmbeddedDatabase"; + + SwDBManager::RevokeDataSource( sDataSource ); + + // Encode the stream name and the real path into a single URL. + const INetURLObject& rURLObject = rDocShell.GetMedium()->GetURLObject(); + OUString const aURL = ConstructVndSunStarPkgUrl( + rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE), + m_sEmbeddedName); + + uno::Reference<uno::XInterface> xDataSource(xDatabaseContext->getByName(aURL), uno::UNO_QUERY); + xDatabaseContext->registerObject( sDataSource, xDataSource ); + + // temp file - don't remember connection + if (rData.sDataSource.isEmpty()) + s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(nullptr, sDataSource)); +} + +void SwDBManager::ExecuteFormLetter( SwWrtShell& rSh, + const uno::Sequence<beans::PropertyValue>& rProperties) +{ + //prevent second call + if(m_pImpl->pMergeDialog) + return ; + OUString sDataSource, sDataTableOrQuery; + uno::Sequence<uno::Any> aSelection; + + sal_Int32 nCmdType = sdb::CommandType::TABLE; + uno::Reference< sdbc::XConnection> xConnection; + + svx::ODataAccessDescriptor aDescriptor(rProperties); + sDataSource = aDescriptor.getDataSource(); + OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::Command] >>= sDataTableOrQuery); + OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= nCmdType); + + if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Selection) ) + aDescriptor[svx::DataAccessDescriptorProperty::Selection] >>= aSelection; + if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Connection) ) + aDescriptor[svx::DataAccessDescriptorProperty::Connection] >>= xConnection; + + if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty()) + { + OSL_FAIL("PropertyValues missing or unset"); + return; + } + + //always create a connection for the dialog and dispose it after the dialog has been closed + SwDSParam* pFound = nullptr; + if(!xConnection.is()) + { + xConnection = SwDBManager::RegisterConnection(sDataSource); + pFound = FindDSConnection(sDataSource, true); + } + SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create(); + m_pImpl->pMergeDialog = pFact->CreateMailMergeDlg(rSh.GetView().GetViewFrame().GetFrameWeld(), rSh, + sDataSource, + sDataTableOrQuery, + nCmdType, + xConnection); + if(m_pImpl->pMergeDialog->Execute() == RET_OK) + { + aDescriptor[svx::DataAccessDescriptorProperty::Selection] <<= m_pImpl->pMergeDialog->GetSelection(); + + uno::Reference<sdbc::XResultSet> xResSet = m_pImpl->pMergeDialog->GetResultSet(); + if(xResSet.is()) + aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet; + + // SfxObjectShellRef is ok, since there should be no control over the document lifetime here + SfxObjectShellRef xDocShell = rSh.GetView().GetViewFrame().GetObjectShell(); + + lcl_emitEvent(SfxEventHintId::SwMailMerge, STR_SW_EVENT_MAIL_MERGE, xDocShell.get()); + + // prepare mail merge descriptor + SwMergeDescriptor aMergeDesc( m_pImpl->pMergeDialog->GetMergeType(), rSh, aDescriptor ); + aMergeDesc.sSaveToFilter = m_pImpl->pMergeDialog->GetSaveFilter(); + aMergeDesc.bCreateSingleFile = m_pImpl->pMergeDialog->IsSaveSingleDoc(); + aMergeDesc.bPrefixIsFilename = aMergeDesc.bCreateSingleFile; + aMergeDesc.sPrefix = m_pImpl->pMergeDialog->GetTargetURL(); + + if(!aMergeDesc.bCreateSingleFile) + { + if(m_pImpl->pMergeDialog->IsGenerateFromDataBase()) + aMergeDesc.sDBcolumn = m_pImpl->pMergeDialog->GetColumnName(); + + if(m_pImpl->pMergeDialog->IsFileEncryptedFromDataBase()) + aMergeDesc.sDBPasswordColumn = m_pImpl->pMergeDialog->GetPasswordColumnName(); + } + + Merge( aMergeDesc ); + + lcl_emitEvent(SfxEventHintId::SwMailMergeEnd, STR_SW_EVENT_MAIL_MERGE_END, xDocShell.get()); + + // reset the cursor inside + xResSet = nullptr; + aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet; + } + if(pFound) + { + for (const auto & pParam : m_DataSourceParams) + { + if (pParam.get() == pFound) + { + try + { + uno::Reference<lang::XComponent> xComp(pParam->xConnection, uno::UNO_QUERY); + if(xComp.is()) + xComp->dispose(); + } + catch(const uno::RuntimeException&) + { + //may be disposed already since multiple entries may have used the same connection + } + break; + } + //pFound doesn't need to be removed/deleted - + //this has been done by the SwConnectionDisposedListener_Impl already + } + } + m_pImpl->pMergeDialog.disposeAndClear(); +} + +void SwDBManager::InsertText(SwWrtShell& rSh, + const uno::Sequence< beans::PropertyValue>& rProperties) +{ + OUString sDataSource, sDataTableOrQuery; + uno::Reference<sdbc::XResultSet> xResSet; + uno::Sequence<uno::Any> aSelection; + sal_Int16 nCmdType = sdb::CommandType::TABLE; + uno::Reference< sdbc::XConnection> xConnection; + for(const beans::PropertyValue& rValue : rProperties) + { + if ( rValue.Name == "DataSourceName" ) + rValue.Value >>= sDataSource; + else if ( rValue.Name == "Command" ) + rValue.Value >>= sDataTableOrQuery; + else if ( rValue.Name == "Cursor" ) + rValue.Value >>= xResSet; + else if ( rValue.Name == "Selection" ) + rValue.Value >>= aSelection; + else if ( rValue.Name == "CommandType" ) + rValue.Value >>= nCmdType; + else if ( rValue.Name == "ActiveConnection" ) + rValue.Value >>= xConnection; + } + if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty() || !xResSet.is()) + { + OSL_FAIL("PropertyValues missing or unset"); + return; + } + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference<sdbc::XDataSource> xSource; + uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY); + if(xChild.is()) + xSource.set(xChild->getParent(), uno::UNO_QUERY); + if(!xSource.is()) + xSource = dbtools::getDataSource(sDataSource, xContext); + uno::Reference< sdbcx::XColumnsSupplier > xColSupp( xResSet, uno::UNO_QUERY ); + SwDBData aDBData; + aDBData.sDataSource = sDataSource; + aDBData.sCommand = sDataTableOrQuery; + aDBData.nCommandType = nCmdType; + + SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSwInsertDBColAutoPilot> pDlg(pFact->CreateSwInsertDBColAutoPilot( rSh.GetView(), + xSource, + xColSupp, + aDBData )); + if( RET_OK != pDlg->Execute() ) + return; + + OUString sDummy; + if(!xConnection.is()) + xConnection = xSource->getConnection(sDummy, sDummy); + try + { + pDlg->DataToDoc( aSelection , xSource, xConnection, xResSet); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", ""); + } +} + +uno::Reference<sdbc::XDataSource> SwDBManager::getDataSourceAsParent(const uno::Reference< sdbc::XConnection>& _xConnection,const OUString& _sDataSourceName) +{ + uno::Reference<sdbc::XDataSource> xSource; + try + { + uno::Reference<container::XChild> xChild(_xConnection, uno::UNO_QUERY); + if ( xChild.is() ) + xSource.set(xChild->getParent(), uno::UNO_QUERY); + if ( !xSource.is() ) + xSource = dbtools::getDataSource(_sDataSourceName, ::comphelper::getProcessComponentContext()); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", "getDataSourceAsParent()"); + } + return xSource; +} + +uno::Reference<sdbc::XResultSet> SwDBManager::createCursor(const OUString& _sDataSourceName, + const OUString& _sCommand, + sal_Int32 _nCommandType, + const uno::Reference<sdbc::XConnection>& _xConnection, + const SwView* pView) +{ + uno::Reference<sdbc::XResultSet> xResultSet; + try + { + uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() ); + if( xMgr.is() ) + { + uno::Reference<uno::XInterface> xInstance = xMgr->createInstance("com.sun.star.sdb.RowSet"); + uno::Reference<beans::XPropertySet> xRowSetPropSet(xInstance, uno::UNO_QUERY); + if(xRowSetPropSet.is()) + { + xRowSetPropSet->setPropertyValue("DataSourceName", uno::Any(_sDataSourceName)); + xRowSetPropSet->setPropertyValue("ActiveConnection", uno::Any(_xConnection)); + xRowSetPropSet->setPropertyValue("Command", uno::Any(_sCommand)); + xRowSetPropSet->setPropertyValue("CommandType", uno::Any(_nCommandType)); + + uno::Reference< sdb::XCompletedExecution > xRowSet(xInstance, uno::UNO_QUERY); + + if ( xRowSet.is() ) + { + weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr; + uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(comphelper::getComponentContext(xMgr), pWindow ? pWindow->GetXWindow() : nullptr), uno::UNO_QUERY_THROW ); + xRowSet->executeWithCompletion(xHandler); + } + xResultSet.set(xRowSet, uno::UNO_QUERY); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.mailmerge", "Caught exception while creating a new RowSet"); + } + return xResultSet; +} + +void SwDBManager::setEmbeddedName(const OUString& rEmbeddedName, SwDocShell& rDocShell) +{ + bool bLoad = m_sEmbeddedName != rEmbeddedName && !rEmbeddedName.isEmpty(); + bool bRegisterListener = m_sEmbeddedName.isEmpty() && !rEmbeddedName.isEmpty(); + + m_sEmbeddedName = rEmbeddedName; + + if (bLoad) + { + uno::Reference<embed::XStorage> xStorage = rDocShell.GetStorage(); + // It's OK that we don't have the named sub-storage yet, in case + // we're in the process of creating it. + if (xStorage->hasByName(rEmbeddedName)) + LoadAndRegisterEmbeddedDataSource(rDocShell.GetDoc()->GetDBData(), rDocShell); + } + + if (bRegisterListener) + // Register a remove listener, so we know when the embedded data source is removed. + m_pImpl->m_xDataSourceRemovedListener = new SwDataSourceRemovedListener(*this); +} + +const OUString& SwDBManager::getEmbeddedName() const +{ + return m_sEmbeddedName; +} + +SwDoc* SwDBManager::getDoc() const +{ + return m_pDoc; +} + +void SwDBManager::releaseRevokeListener() +{ + if (m_pImpl->m_xDataSourceRemovedListener.is()) + { + m_pImpl->m_xDataSourceRemovedListener->Dispose(); + m_pImpl->m_xDataSourceRemovedListener.clear(); + } +} + +SwDBManager::ConnectionDisposedListener_Impl::ConnectionDisposedListener_Impl(SwDBManager& rManager) + : m_pDBManager(&rManager) +{ +} + +void SwDBManager::ConnectionDisposedListener_Impl::disposing( const lang::EventObject& rSource ) +{ + ::SolarMutexGuard aGuard; + + if (!m_pDBManager) return; // we're disposed too! + + uno::Reference<sdbc::XConnection> xSource(rSource.Source, uno::UNO_QUERY); + for (size_t nPos = m_pDBManager->m_DataSourceParams.size(); nPos; nPos--) + { + SwDSParam* pParam = m_pDBManager->m_DataSourceParams[nPos - 1].get(); + if(pParam->xConnection.is() && + (xSource == pParam->xConnection)) + { + m_pDBManager->m_DataSourceParams.erase( + m_pDBManager->m_DataSourceParams.begin() + nPos - 1); + } + } +} + +std::shared_ptr<SwMailMergeConfigItem> SwDBManager::PerformMailMerge(SwView const * pView) +{ + std::shared_ptr<SwMailMergeConfigItem> xConfigItem = pView->GetMailMergeConfigItem(); + if (!xConfigItem) + return xConfigItem; + + svx::ODataAccessDescriptor aDescriptor; + aDescriptor.setDataSource(xConfigItem->GetCurrentDBData().sDataSource); + aDescriptor[ svx::DataAccessDescriptorProperty::Connection ] <<= xConfigItem->GetConnection().getTyped(); + aDescriptor[ svx::DataAccessDescriptorProperty::Cursor ] <<= xConfigItem->GetResultSet(); + aDescriptor[ svx::DataAccessDescriptorProperty::Command ] <<= xConfigItem->GetCurrentDBData().sCommand; + aDescriptor[ svx::DataAccessDescriptorProperty::CommandType ] <<= xConfigItem->GetCurrentDBData().nCommandType; + aDescriptor[ svx::DataAccessDescriptorProperty::Selection ] <<= xConfigItem->GetSelection(); + + SwWrtShell& rSh = pView->GetWrtShell(); + xConfigItem->SetTargetView(nullptr); + + SwMergeDescriptor aMergeDesc(DBMGR_MERGE_SHELL, rSh, aDescriptor); + aMergeDesc.pMailMergeConfigItem = xConfigItem.get(); + aMergeDesc.bCreateSingleFile = true; + rSh.GetDBManager()->Merge(aMergeDesc); + + return xConfigItem; +} + +void SwDBManager::RevokeLastRegistrations() +{ + if (s_aUncommittedRegistrations.empty()) + return; + + SwView* pView = ( m_pDoc && m_pDoc->GetDocShell() ) ? m_pDoc->GetDocShell()->GetView() : nullptr; + if (pView) + { + const std::shared_ptr<SwMailMergeConfigItem>& xConfigItem = pView->GetMailMergeConfigItem(); + if (xConfigItem) + { + xConfigItem->DisposeResultSet(); + xConfigItem->DocumentReloaded(); + } + } + + for (auto it = s_aUncommittedRegistrations.begin(); it != s_aUncommittedRegistrations.end();) + { + if ((m_pDoc && it->first == m_pDoc->GetDocShell()) || it->first == nullptr) + { + RevokeDataSource(it->second); + it = s_aUncommittedRegistrations.erase(it); + } + else + ++it; + } +} + +void SwDBManager::CommitLastRegistrations() +{ + for (auto aIt = s_aUncommittedRegistrations.begin(); aIt != s_aUncommittedRegistrations.end();) + { + if (aIt->first == m_pDoc->GetDocShell() || aIt->first == nullptr) + { + m_aNotUsedConnections.push_back(aIt->second); + aIt = s_aUncommittedRegistrations.erase(aIt); + } + else + aIt++; + } +} + +void SwDBManager::SetAsUsed(const OUString& rName) +{ + auto aFound = std::find(m_aNotUsedConnections.begin(), m_aNotUsedConnections.end(), rName); + if (aFound != m_aNotUsedConnections.end()) + m_aNotUsedConnections.erase(aFound); +} + +void SwDBManager::RevokeNotUsedConnections() +{ + for (auto aIt = m_aNotUsedConnections.begin(); aIt != m_aNotUsedConnections.end();) + { + RevokeDataSource(*aIt); + aIt = m_aNotUsedConnections.erase(aIt); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/dbtree.cxx b/sw/source/uibase/dbui/dbtree.cxx new file mode 100644 index 0000000000..432a0a28e6 --- /dev/null +++ b/sw/source/uibase/dbui/dbtree.cxx @@ -0,0 +1,454 @@ +/* -*- 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/container/XNameAccess.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <com/sun/star/container/XContainerListener.hpp> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <osl/diagnose.h> + +#include <dbmgr.hxx> +#include <wrtsh.hxx> +#include <dbtree.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <bitmaps.hlst> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::beans; + +class SwDBTreeList_Impl : public cppu::WeakImplHelper < XContainerListener > +{ + Reference< XDatabaseContext > m_xDatabaseContext; + SwWrtShell* m_pWrtShell; + + public: + explicit SwDBTreeList_Impl() + : m_pWrtShell(nullptr) + { + } + virtual ~SwDBTreeList_Impl() override; + + virtual void SAL_CALL elementInserted( const ContainerEvent& Event ) override; + virtual void SAL_CALL elementRemoved( const ContainerEvent& Event ) override; + virtual void SAL_CALL elementReplaced( const ContainerEvent& Event ) override; + virtual void SAL_CALL disposing( const EventObject& Source ) override; + + bool HasContext(); + SwWrtShell* GetWrtShell() { return m_pWrtShell;} + void SetWrtShell(SwWrtShell& rSh) { m_pWrtShell = &rSh;} + const Reference<XDatabaseContext>& GetContext() const {return m_xDatabaseContext;} + Reference<XConnection> GetConnection(const OUString& rSourceName); +}; + +SwDBTreeList_Impl::~SwDBTreeList_Impl() +{ + if(m_xDatabaseContext.is()) + { + osl_atomic_increment(&m_refCount); + //block necessary due to solaris' compiler behaviour to + //remove temporaries at the block's end + { + m_xDatabaseContext->removeContainerListener( this ); + } + osl_atomic_decrement(&m_refCount); + } +} + +void SwDBTreeList_Impl::elementInserted( const ContainerEvent& ) +{ + // information not needed +} + +void SwDBTreeList_Impl::elementRemoved( const ContainerEvent& ) +{ +} + +void SwDBTreeList_Impl::disposing( const EventObject& ) +{ + m_xDatabaseContext = nullptr; +} + +void SwDBTreeList_Impl::elementReplaced( const ContainerEvent& rEvent ) +{ + elementRemoved(rEvent); +} + +bool SwDBTreeList_Impl::HasContext() +{ + if(!m_xDatabaseContext.is()) + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + m_xDatabaseContext = DatabaseContext::create(xContext); + m_xDatabaseContext->addContainerListener( this ); + } + return m_xDatabaseContext.is(); +} + +Reference<XConnection> SwDBTreeList_Impl::GetConnection(const OUString& rSourceName) +{ + Reference<XConnection> xRet; + if (m_xDatabaseContext.is() && m_pWrtShell) + { + xRet = m_pWrtShell->GetDBManager()->RegisterConnection(rSourceName); + } + return xRet; +} + +SwDBTreeList::SwDBTreeList(std::unique_ptr<weld::TreeView> xTreeView) + : m_bInitialized(false) + , m_bShowColumns(false) + , m_pImpl(new SwDBTreeList_Impl) + , m_xTreeView(std::move(xTreeView)) + , m_xScratchIter(m_xTreeView->make_iterator()) +{ + m_xTreeView->connect_expanding(LINK(this, SwDBTreeList, RequestingChildrenHdl)); +} + +SwDBTreeList::~SwDBTreeList() +{ +} + +void SwDBTreeList::InitTreeList() +{ + if (!m_pImpl->HasContext() && m_pImpl->GetWrtShell()) + return; + + Sequence< OUString > aDBNames = m_pImpl->GetContext()->getElementNames(); + auto const sort = comphelper::string::NaturalStringSorter( + comphelper::getProcessComponentContext(), + Application::GetSettings().GetUILanguageTag().getLocale()); + auto [begin, end] = asNonConstRange(aDBNames); + std::sort( + begin, end, + [&sort](OUString const & x, OUString const & y) + { return sort.compare(x, y) < 0; }); + + OUString aImg(RID_BMP_DB); + for (const OUString& rDBName : std::as_const(aDBNames)) + { + // If this database has a password or a (missing) remote connection, + // then it might take a long time or spam for unnecessary credentials. + // Just check that it basically exists to weed out any broken/obsolete registrations. + if (SwDBManager::getDataSourceAsParent(Reference<sdbc::XConnection>(), rDBName).is()) + { + m_xTreeView->insert(nullptr, -1, &rDBName, nullptr, nullptr, nullptr, true, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, aImg); + } + } + Select(u"", u"", u""); + + m_bInitialized = true; +} + +void SwDBTreeList::AddDataSource(const OUString& rSource) +{ + m_xTreeView->insert(nullptr, -1, &rSource, nullptr, nullptr, nullptr, true, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, RID_BMP_DB); + m_xTreeView->select(*m_xScratchIter); +} + +IMPL_LINK(SwDBTreeList, RequestingChildrenHdl, const weld::TreeIter&, rParent, bool) +{ + if (!m_xTreeView->iter_has_child(rParent)) + { + if (m_xTreeView->get_iter_depth(rParent)) // column names + { + try + { + std::unique_ptr<weld::TreeIter> xGrandParent(m_xTreeView->make_iterator(&rParent)); + m_xTreeView->iter_parent(*xGrandParent); + OUString sSourceName = m_xTreeView->get_text(*xGrandParent); + OUString sTableName = m_xTreeView->get_text(rParent); + + if(!m_pImpl->GetContext()->hasByName(sSourceName)) + return true; + Reference<XConnection> xConnection = m_pImpl->GetConnection(sSourceName); + bool bTable = m_xTreeView->get_id(rParent).isEmpty(); + Reference<XColumnsSupplier> xColsSupplier; + if(bTable) + { + Reference<XTablesSupplier> xTSupplier(xConnection, UNO_QUERY); + if(xTSupplier.is()) + { + Reference<XNameAccess> xTables = xTSupplier->getTables(); + OSL_ENSURE(xTables->hasByName(sTableName), "table not available anymore?"); + try + { + Any aTable = xTables->getByName(sTableName); + Reference<XPropertySet> xPropSet; + aTable >>= xPropSet; + xColsSupplier.set(xPropSet, UNO_QUERY); + } + catch (const Exception&) + { + } + } + } + else + { + Reference<XQueriesSupplier> xQSupplier(xConnection, UNO_QUERY); + if(xQSupplier.is()) + { + Reference<XNameAccess> xQueries = xQSupplier->getQueries(); + OSL_ENSURE(xQueries->hasByName(sTableName), "table not available anymore?"); + try + { + Any aQuery = xQueries->getByName(sTableName); + Reference<XPropertySet> xPropSet; + aQuery >>= xPropSet; + xColsSupplier.set(xPropSet, UNO_QUERY); + } + catch (const Exception&) + { + } + } + } + + if(xColsSupplier.is()) + { + Reference <XNameAccess> xCols = xColsSupplier->getColumns(); + const Sequence< OUString> aColNames = xCols->getElementNames(); + for (const OUString& rColName : aColNames) + { + m_xTreeView->append(&rParent, rColName); + } + } + } + catch (const Exception&) + { + } + } + else // table names + { + try + { + OUString sSourceName = m_xTreeView->get_text(rParent); + if (!m_pImpl->GetContext()->hasByName(sSourceName)) + return true; + Reference<XConnection> xConnection = m_pImpl->GetConnection(sSourceName); + if (xConnection.is()) + { + Reference<XTablesSupplier> xTSupplier(xConnection, UNO_QUERY); + if(xTSupplier.is()) + { + Reference<XNameAccess> xTables = xTSupplier->getTables(); + const Sequence< OUString> aTableNames = xTables->getElementNames(); + OUString aImg(RID_BMP_DBTABLE); + for (const OUString& rTableName : aTableNames) + { + m_xTreeView->insert(&rParent, -1, &rTableName, nullptr, + nullptr, nullptr, m_bShowColumns, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, aImg); + } + } + + Reference<XQueriesSupplier> xQSupplier(xConnection, UNO_QUERY); + if(xQSupplier.is()) + { + Reference<XNameAccess> xQueries = xQSupplier->getQueries(); + const Sequence< OUString> aQueryNames = xQueries->getElementNames(); + OUString aImg(RID_BMP_DBQUERY); + for (const OUString& rQueryName : aQueryNames) + { + //to discriminate between queries and tables the user data of query entries is set + OUString sId(OUString::number(1)); + m_xTreeView->insert(&rParent, -1, &rQueryName, &sId, + nullptr, nullptr, m_bShowColumns, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, aImg); + } + } + } + } + catch (const Exception&) + { + } + } + } + return true; +} + +OUString SwDBTreeList::GetDBName(OUString& rTableName, OUString& rColumnName, sal_Bool* pbIsTable) +{ + OUString sDBName; + std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator()); + if (m_xTreeView->get_selected(xIter.get())) + { + if (m_xTreeView->get_iter_depth(*xIter) == 2) + { + rColumnName = m_xTreeView->get_text(*xIter); + m_xTreeView->iter_parent(*xIter); // column name was selected + } + if (m_xTreeView->get_iter_depth(*xIter) == 1) + { + if (pbIsTable) + *pbIsTable = m_xTreeView->get_id(*xIter).isEmpty(); + rTableName = m_xTreeView->get_text(*xIter); + m_xTreeView->iter_parent(*xIter); + } + sDBName = m_xTreeView->get_text(*xIter); + } + return sDBName; +} + +// Format: database.table +void SwDBTreeList::Select(std::u16string_view rDBName, std::u16string_view rTableName, std::u16string_view rColumnName) +{ + std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator()); + if (!m_xTreeView->get_iter_first(*xParent)) + return; + + do + { + if (rDBName == m_xTreeView->get_text(*xParent)) + { + if (rTableName.empty() && rColumnName.empty()) + { + // Just select the database node, do not expand + m_xTreeView->scroll_to_row(*xParent); + m_xTreeView->select(*xParent); + return; + } + if (!m_xTreeView->iter_has_child(*xParent)) + { + m_xTreeView->set_children_on_demand(*xParent, false); // tdf#142294 drop placeholder on-demand node + RequestingChildrenHdl(*xParent); + // If successful, it will be expanded in a call to scroll_to_row for its children + } + std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(xParent.get())); + if (!m_xTreeView->iter_children(*xChild)) + { + m_xTreeView->scroll_to_row(*xParent); + m_xTreeView->select(*xParent); + continue; + } + do + { + if (rTableName == m_xTreeView->get_text(*xChild)) + { + m_xTreeView->copy_iterator(*xChild, *xParent); + + bool bNoChild = false; + if (m_bShowColumns && !rColumnName.empty()) + { + if (!m_xTreeView->iter_has_child(*xParent)) + { + m_xTreeView->set_children_on_demand(*xParent, false); // tdf#142294 drop placeholder on-demand node + RequestingChildrenHdl(*xParent); + m_xTreeView->expand_row(*xParent); + } + + bNoChild = true; + if (m_xTreeView->iter_children(*xChild)) + { + do + { + if (rColumnName == m_xTreeView->get_text(*xChild)) + { + bNoChild = false; + break; + } + } + while (m_xTreeView->iter_next_sibling(*xChild)); + } + } + + if (bNoChild) + m_xTreeView->copy_iterator(*xParent, *xChild); + + m_xTreeView->scroll_to_row(*xChild); + m_xTreeView->select(*xChild); + return; + } + } + while (m_xTreeView->iter_next_sibling(*xChild)); + } + } while (m_xTreeView->iter_next_sibling(*xParent)); +} + +void SwDBTreeList::SetWrtShell(SwWrtShell& rSh) +{ + m_pImpl->SetWrtShell(rSh); + if (m_xTreeView->get_visible() && !m_bInitialized) + InitTreeList(); +} + +namespace +{ + void GotoRootLevelParent(const weld::TreeView& rTreeView, weld::TreeIter& rEntry) + { + while (rTreeView.get_iter_depth(rEntry)) + rTreeView.iter_parent(rEntry); + } +} + +void SwDBTreeList::ShowColumns(bool bShowCol) +{ + if (bShowCol == m_bShowColumns) + return; + + m_bShowColumns = bShowCol; + OUString sTableName; + OUString sColumnName; + const OUString sDBName(GetDBName(sTableName, sColumnName)); + + m_xTreeView->freeze(); + + std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator()); + std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator()); + if (m_xTreeView->get_iter_first(*xIter)) + { + do + { + GotoRootLevelParent(*m_xTreeView, *xIter); + m_xTreeView->collapse_row(*xIter); + while (m_xTreeView->iter_has_child(*xIter)) + { + m_xTreeView->copy_iterator(*xIter, *xChild); + (void)m_xTreeView->iter_children(*xChild); + m_xTreeView->remove(*xChild); + } + } while (m_xTreeView->iter_next(*xIter)); + } + + m_xTreeView->thaw(); + + if (!sDBName.isEmpty()) + { + Select(sDBName, sTableName, sColumnName); // force RequestingChildren + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/dbui.cxx b/sw/source/uibase/dbui/dbui.cxx new file mode 100644 index 0000000000..7b420d5f86 --- /dev/null +++ b/sw/source/uibase/dbui/dbui.cxx @@ -0,0 +1,86 @@ +/* -*- 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 <dbui.hxx> + +SaveMonitor::SaveMonitor(weld::Window *pParent) + : GenericDialogController(pParent, "modules/swriter/ui/savemonitordialog.ui", + "SaveMonitorDialog") + , m_xDocName(m_xBuilder->weld_label("docname")) + , m_xPrinter(m_xBuilder->weld_label("printer")) + , m_xPrintInfo(m_xBuilder->weld_label("printinfo")) +{ +} + +SaveMonitor::~SaveMonitor() +{ +} + +PrintMonitor::PrintMonitor(weld::Window *pParent) + : GenericDialogController(pParent, "modules/swriter/ui/printmonitordialog.ui", + "PrintMonitorDialog") + , m_xPrinter(m_xBuilder->weld_label("printer")) + , m_xPrintInfo(m_xBuilder->weld_label("printinfo")) +{ +} + +PrintMonitor::~PrintMonitor() +{ +} + +// Progress Indicator for Creation of personalized Mail Merge documents: +CreateMonitor::CreateMonitor(weld::Window *pParent) + : GenericDialogController(pParent, "modules/swriter/ui/mmcreatingdialog.ui", + "MMCreatingDialog") + , m_nTotalCount(0) + , m_nCurrentPosition(0) + , m_xCounting(m_xBuilder->weld_label("progress")) +{ + m_sCountingPattern = m_xCounting->get_label(); + m_xCounting->set_label("..."); +} + +CreateMonitor::~CreateMonitor() +{ +} + +void CreateMonitor::UpdateCountingText() +{ + constexpr OUStringLiteral sVariable_Total(u"%Y"); + constexpr OUStringLiteral sVariable_Position(u"%X"); + + OUString sText(m_sCountingPattern); + sText = sText.replaceAll( sVariable_Total, OUString::number( m_nTotalCount ) ); + sText = sText.replaceAll( sVariable_Position, OUString::number( m_nCurrentPosition ) ); + m_xCounting->set_label(sText); +} + +void CreateMonitor::SetTotalCount( sal_Int32 nTotal ) +{ + m_nTotalCount = nTotal; + UpdateCountingText(); +} + +void CreateMonitor::SetCurrentPosition( sal_Int32 nCurrent ) +{ + m_nCurrentPosition = nCurrent; + UpdateCountingText(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/maildispatcher.cxx b/sw/source/uibase/dbui/maildispatcher.cxx new file mode 100644 index 0000000000..8af9b18007 --- /dev/null +++ b/sw/source/uibase/dbui/maildispatcher.cxx @@ -0,0 +1,249 @@ +/* -*- 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 <maildispatcher.hxx> +#include <imaildsplistener.hxx> + +#include <algorithm> + +#include <com/sun/star/mail/MailException.hpp> +#include <utility> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +typedef std::vector< ::rtl::Reference<IMailDispatcherListener> > MailDispatcherListenerContainer_t; + +namespace /* private */ +{ + class MailDeliveryNotifier + { + public: + MailDeliveryNotifier(uno::Reference<mail::XMailMessage> message) : + message_(std::move(message)) + {} + + void operator() (::rtl::Reference<IMailDispatcherListener> const & listener) const + { listener->mailDelivered(message_); } + + private: + uno::Reference<mail::XMailMessage> message_; + }; + + class MailDeliveryErrorNotifier + { + public: + MailDeliveryErrorNotifier( + ::rtl::Reference<MailDispatcher> xMailDispatcher, + uno::Reference<mail::XMailMessage> message, + OUString error_message) : + m_mail_dispatcher(std::move(xMailDispatcher)), + m_message(std::move(message)), + m_error_message(std::move(error_message)) + {} + + void operator() (::rtl::Reference<IMailDispatcherListener> const & listener) const + { listener->mailDeliveryError(m_mail_dispatcher, m_message, m_error_message); } + + private: + ::rtl::Reference<MailDispatcher> m_mail_dispatcher; + uno::Reference<mail::XMailMessage> m_message; + OUString m_error_message; + }; + +} // namespace private + +MailDispatcher::MailDispatcher(uno::Reference<mail::XSmtpService> mailserver) : + m_xMailserver(std::move( mailserver )), + m_bActive( false ), + m_bShutdownRequested( false ) +{ + m_aWakeupCondition.reset(); + m_aRunCondition.reset(); + + if (!create()) + throw uno::RuntimeException(); + + // wait until the mail dispatcher thread is really alive + // and has acquired a reference to this instance of the + // class + m_aRunCondition.wait(); +} + +MailDispatcher::~MailDispatcher() +{ +} + +void MailDispatcher::enqueueMailMessage(uno::Reference<mail::XMailMessage> const & message) +{ + ::osl::MutexGuard thread_status_guard( m_aThreadStatusMutex ); + ::osl::MutexGuard message_container_guard( m_aMessageContainerMutex ); + + OSL_PRECOND( !m_bShutdownRequested, "MailDispatcher thread is shutting down already" ); + + m_aXMessageList.push_back( message ); + if ( m_bActive ) + m_aWakeupCondition.set(); +} + +uno::Reference<mail::XMailMessage> MailDispatcher::dequeueMailMessage() +{ + ::osl::MutexGuard guard( m_aMessageContainerMutex ); + uno::Reference<mail::XMailMessage> message; + if ( !m_aXMessageList.empty() ) + { + message = m_aXMessageList.front(); + m_aXMessageList.pop_front(); + } + return message; +} + +void MailDispatcher::start() +{ + OSL_PRECOND(!isStarted(), "MailDispatcher is already started!"); + + ::osl::ClearableMutexGuard thread_status_guard( m_aThreadStatusMutex ); + + OSL_PRECOND(!m_bShutdownRequested, "MailDispatcher thread is shutting down already"); + + if ( !m_bShutdownRequested ) + { + m_bActive = true; + m_aWakeupCondition.set(); + thread_status_guard.clear(); + } +} + +void MailDispatcher::stop() +{ + OSL_PRECOND(isStarted(), "MailDispatcher not started!"); + + ::osl::ClearableMutexGuard thread_status_guard( m_aThreadStatusMutex ); + + OSL_PRECOND(!m_bShutdownRequested, "MailDispatcher thread is shutting down already"); + + if (!m_bShutdownRequested) + { + m_bActive = false; + m_aWakeupCondition.reset(); + thread_status_guard.clear(); + } +} + +void MailDispatcher::shutdown() +{ + ::osl::MutexGuard thread_status_guard( m_aThreadStatusMutex ); + + OSL_PRECOND(!m_bShutdownRequested, "MailDispatcher thread is shutting down already"); + + m_bShutdownRequested = true; + m_aWakeupCondition.set(); +} + + +void MailDispatcher::addListener(::rtl::Reference<IMailDispatcherListener> const & listener) +{ + OSL_PRECOND(!m_bShutdownRequested, "MailDispatcher thread is shutting down already"); + + ::osl::MutexGuard guard( m_aListenerContainerMutex ); + m_aListenerVector.push_back( listener ); +} + +std::vector< ::rtl::Reference<IMailDispatcherListener> > MailDispatcher::cloneListener() +{ + ::osl::MutexGuard guard( m_aListenerContainerMutex ); + return m_aListenerVector; +} + +void MailDispatcher::sendMailMessageNotifyListener(uno::Reference<mail::XMailMessage> const & message) +{ + try + { + m_xMailserver->sendMailMessage( message ); + MailDispatcherListenerContainer_t aClonedListenerVector(cloneListener()); + std::for_each( aClonedListenerVector.begin(), aClonedListenerVector.end(), + MailDeliveryNotifier(message) ); + } + catch (const mail::MailException& ex) + { + MailDispatcherListenerContainer_t aClonedListenerVector(cloneListener()); + std::for_each( aClonedListenerVector.begin(), aClonedListenerVector.end(), + MailDeliveryErrorNotifier(this, message, ex.Message) ); + } + catch (const uno::RuntimeException& ex) + { + MailDispatcherListenerContainer_t aClonedListenerVector(cloneListener()); + std::for_each( aClonedListenerVector.begin(), aClonedListenerVector.end(), + MailDeliveryErrorNotifier(this, message, ex.Message) ); + } +} + +void MailDispatcher::run() +{ + osl_setThreadName("MailDispatcher"); + + // acquire a self reference in order to avoid race + // conditions. The last client of this class must + // call shutdown before releasing his last reference + // to this class in order to shutdown this thread + // which will release his (the very last reference + // to the class and so force their destruction + m_xSelfReference = this; + + // signal that the mail dispatcher thread is now alive + m_aRunCondition.set(); + + for(;;) + { + m_aWakeupCondition.wait(); + + ::osl::ClearableMutexGuard thread_status_guard( m_aThreadStatusMutex ); + if ( m_bShutdownRequested ) + break; + + ::osl::ClearableMutexGuard message_container_guard( m_aMessageContainerMutex ); + + if ( !m_aXMessageList.empty() ) + { + thread_status_guard.clear(); + uno::Reference<mail::XMailMessage> message = m_aXMessageList.front(); + m_aXMessageList.pop_front(); + message_container_guard.clear(); + sendMailMessageNotifyListener( message ); + } + else // idle - put ourself to sleep + { + m_aWakeupCondition.reset(); + message_container_guard.clear(); + thread_status_guard.clear(); + MailDispatcherListenerContainer_t aListenerListcloned( cloneListener() ); + for( const auto & l : aListenerListcloned) + l->idle(); + } + } +} + +void MailDispatcher::onTerminated() +{ + //keep the reference until the end of onTerminated() because of the call order in the + //_threadFunc() from osl/thread.hxx + m_xSelfReference = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/mailmergehelper.cxx b/sw/source/uibase/dbui/mailmergehelper.cxx new file mode 100644 index 0000000000..4e6f7534da --- /dev/null +++ b/sw/source/uibase/dbui/mailmergehelper.cxx @@ -0,0 +1,832 @@ +/* -*- 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 <swtypes.hxx> +#include <mailmergehelper.hxx> +#include <mmconfigitem.hxx> +#include <docsh.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/docfile.hxx> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdb/XColumn.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/mail/MailServiceProvider.hpp> +#include <com/sun/star/mail/XSmtpService.hpp> +#include <comphelper/processfactory.hxx> +#include <o3tl/safeint.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/weldutils.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <o3tl/string_view.hxx> + +#include <sfx2/passwd.hxx> + +#include <dbui.hrc> +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; + +namespace SwMailMergeHelper +{ + +OUString CallSaveAsDialog(weld::Window* pParent, OUString& rFilter) +{ + ::sfx2::FileDialogHelper aDialog( ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION, + FileDialogFlags::NONE, + SwDocShell::Factory().GetFactoryName(), SfxFilterFlags::NONE, SfxFilterFlags::NONE, pParent); + aDialog.SetContext(sfx2::FileDialogHelper::WriterMailMergeSaveAs); + + if (aDialog.Execute()!=ERRCODE_NONE) + { + return OUString(); + } + + rFilter = aDialog.GetRealFilter(); + uno::Reference < ui::dialogs::XFilePicker3 > xFP = aDialog.GetFilePicker(); + return xFP->getSelectedFiles().getConstArray()[0]; +} + +/* + simple address check: check for '@' + for at least one '.' after the '@', + for at least one character before the dot + and for at least two characters after the dot +*/ +bool CheckMailAddress( std::u16string_view aMailAddress ) +{ + const size_t nPosAt = aMailAddress.find('@'); + if (nPosAt == std::u16string_view::npos || aMailAddress.rfind('@')!=nPosAt) + return false; + const size_t nPosDot = aMailAddress.find('.', nPosAt); + return !(nPosDot==std::u16string_view::npos || nPosDot-nPosAt<2 || aMailAddress.size()-nPosDot<3); +} + +uno::Reference< mail::XSmtpService > ConnectToSmtpServer( + SwMailMergeConfigItem const & rConfigItem, + uno::Reference< mail::XMailService >& rxInMailService, + const OUString& rInMailServerPassword, + const OUString& rOutMailServerPassword, + weld::Window* pDialogParentWindow ) +{ + uno::Reference< mail::XSmtpService > xSmtpServer; + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + try + { + uno::Reference< mail::XMailServiceProvider > xMailServiceProvider( + mail::MailServiceProvider::create( xContext ) ); + xSmtpServer.set(xMailServiceProvider->create(mail::MailServiceType_SMTP), uno::UNO_QUERY); + + uno::Reference< mail::XConnectionListener> xConnectionListener(new SwConnectionListener); + + if(rConfigItem.IsAuthentication() && rConfigItem.IsSMTPAfterPOP()) + { + uno::Reference< mail::XMailService > xInMailService = + xMailServiceProvider->create( + rConfigItem.IsInServerPOP() ? + mail::MailServiceType_POP3 : mail::MailServiceType_IMAP); + //authenticate at the POP or IMAP server first + OUString sPasswd = rConfigItem.GetInServerPassword(); + if(!rInMailServerPassword.isEmpty()) + sPasswd = rInMailServerPassword; + uno::Reference<mail::XAuthenticator> xAuthenticator = + new SwAuthenticator( + rConfigItem.GetInServerUserName(), + sPasswd, + pDialogParentWindow); + + xInMailService->addConnectionListener(xConnectionListener); + //check connection + uno::Reference< uno::XCurrentContext> xConnectionContext = + new SwConnectionContext( + rConfigItem.GetInServerName(), + rConfigItem.GetInServerPort(), + "Insecure"); + xInMailService->connect(xConnectionContext, xAuthenticator); + rxInMailService = xInMailService; + } + uno::Reference< mail::XAuthenticator> xAuthenticator; + if(rConfigItem.IsAuthentication() && + !rConfigItem.IsSMTPAfterPOP() && + !rConfigItem.GetMailUserName().isEmpty()) + { + OUString sPasswd = rConfigItem.GetMailPassword(); + if(!rOutMailServerPassword.isEmpty()) + sPasswd = rOutMailServerPassword; + xAuthenticator = + new SwAuthenticator(rConfigItem.GetMailUserName(), + sPasswd, + pDialogParentWindow); + } + else + xAuthenticator = new SwAuthenticator(); + //just to check if the server exists + xSmtpServer->getSupportedConnectionTypes(); + //check connection + + uno::Reference< uno::XCurrentContext> xConnectionContext = + new SwConnectionContext( + rConfigItem.GetMailServer(), + rConfigItem.GetMailPort(), + rConfigItem.IsSecureConnection() ? OUString("Ssl") : OUString("Insecure") ); + xSmtpServer->connect(xConnectionContext, xAuthenticator); + rxInMailService = xSmtpServer; + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw", ""); + } + return xSmtpServer; +} + +} //namespace + +struct SwAddressPreview_Impl +{ + std::vector< OUString > aAddresses; + sal_uInt16 nRows; + sal_uInt16 nColumns; + sal_uInt16 nSelectedAddress; + bool bEnableScrollBar; + + SwAddressPreview_Impl() : + nRows(1), + nColumns(1), + nSelectedAddress(0), + bEnableScrollBar(false) + { + } +}; + +OUString SwAddressPreview::FillData( + const OUString& rAddress, + SwMailMergeConfigItem const & rConfigItem, + const Sequence< OUString>* pAssignments) +{ + //find the column names in the address string (with name assignment!) and + //exchange the placeholder (like <Firstname>) with the database content + //unassigned columns are expanded to <not assigned> + Reference< XColumnsSupplier > xColsSupp( rConfigItem.GetResultSet(), UNO_QUERY); + Reference <XNameAccess> xColAccess = xColsSupp.is() ? xColsSupp->getColumns() : nullptr; + Sequence< OUString> aAssignment = pAssignments ? + *pAssignments : + rConfigItem.GetColumnAssignment( + rConfigItem.GetCurrentDBData() ); + const OUString* pAssignment = aAssignment.getConstArray(); + const std::vector<std::pair<OUString, int>>& rDefHeaders = rConfigItem.GetDefaultAddressHeaders(); + OUString sNotAssigned = "<" + SwResId(STR_NOTASSIGNED) + ">"; + + bool bIncludeCountry = rConfigItem.IsIncludeCountry(); + const OUString rExcludeCountry = rConfigItem.GetExcludeCountry(); + bool bSpecialReplacementForCountry = (!bIncludeCountry || !rExcludeCountry.isEmpty()); + OUString sCountryColumn; + if( bSpecialReplacementForCountry ) + { + sCountryColumn = rDefHeaders[MM_PART_COUNTRY].first; + Sequence< OUString> aSpecialAssignment = + rConfigItem.GetColumnAssignment( rConfigItem.GetCurrentDBData() ); + if(aSpecialAssignment.getLength() > MM_PART_COUNTRY && aSpecialAssignment[MM_PART_COUNTRY].getLength()) + sCountryColumn = aSpecialAssignment[MM_PART_COUNTRY]; + } + + SwAddressIterator aIter(rAddress); + OUStringBuffer sAddress; + while(aIter.HasMore()) + { + SwMergeAddressItem aItem = aIter.Next(); + if(aItem.bIsColumn) + { + //get the default column name + + //find the appropriate assignment + OUString sConvertedColumn = aItem.sText; + auto nSize = std::min(sal_uInt32(rDefHeaders.size()), sal_uInt32(aAssignment.getLength())); + for(sal_uInt32 nColumn = 0; nColumn < nSize; ++nColumn) + { + if (rDefHeaders[nColumn].first == aItem.sText && + !pAssignment[nColumn].isEmpty()) + { + sConvertedColumn = pAssignment[nColumn]; + break; + } + } + if(!sConvertedColumn.isEmpty() && + xColAccess.is() && + xColAccess->hasByName(sConvertedColumn)) + { + //get the content and exchange it in the address string + Any aCol = xColAccess->getByName(sConvertedColumn); + Reference< XColumn > xColumn; + aCol >>= xColumn; + if(xColumn.is()) + { + try + { + OUString sReplace = xColumn->getString(); + + if( bSpecialReplacementForCountry && sCountryColumn == sConvertedColumn ) + { + if( !rExcludeCountry.isEmpty() && sReplace != rExcludeCountry ) + aItem.sText = sReplace; + else + aItem.sText.clear(); + } + else + { + aItem.sText = sReplace; + } + } + catch (const sdbc::SQLException&) + { + TOOLS_WARN_EXCEPTION( "sw", ""); + } + } + } + else + { + aItem.sText = sNotAssigned; + } + + } + sAddress.append(aItem.sText); + } + return sAddress.makeStringAndClear(); +} + +SwAddressPreview::SwAddressPreview(std::unique_ptr<weld::ScrolledWindow> xWindow) + : m_pImpl(new SwAddressPreview_Impl()) + , m_xVScrollBar(std::move(xWindow)) +{ + m_xVScrollBar->connect_vadjustment_changed(LINK(this, SwAddressPreview, ScrollHdl)); +} + +SwAddressPreview::~SwAddressPreview() +{ +} + +IMPL_LINK_NOARG(SwAddressPreview, ScrollHdl, weld::ScrolledWindow&, void) +{ + Invalidate(); +} + +void SwAddressPreview::AddAddress(const OUString& rAddress) +{ + m_pImpl->aAddresses.push_back(rAddress); + UpdateScrollBar(); +} + +void SwAddressPreview::SetAddress(const OUString& rAddress) +{ + m_pImpl->aAddresses.clear(); + m_pImpl->aAddresses.push_back(rAddress); + m_xVScrollBar->set_vpolicy(VclPolicyType::NEVER); + Invalidate(); +} + +sal_uInt16 SwAddressPreview::GetSelectedAddress()const +{ + OSL_ENSURE(m_pImpl->nSelectedAddress < m_pImpl->aAddresses.size(), "selection invalid"); + return m_pImpl->nSelectedAddress; +} + +void SwAddressPreview::SelectAddress(sal_uInt16 nSelect) +{ + OSL_ENSURE(m_pImpl->nSelectedAddress < m_pImpl->aAddresses.size(), "selection invalid"); + m_pImpl->nSelectedAddress = nSelect; + // now make it visible... + sal_uInt16 nSelectRow = nSelect / m_pImpl->nColumns; + sal_uInt16 nStartRow = m_xVScrollBar->vadjustment_get_value(); + if( (nSelectRow < nStartRow) || (nSelectRow >= (nStartRow + m_pImpl->nRows) )) + m_xVScrollBar->vadjustment_set_value(nSelectRow); +} + +void SwAddressPreview::Clear() +{ + m_pImpl->aAddresses.clear(); + m_pImpl->nSelectedAddress = 0; + UpdateScrollBar(); +} + +void SwAddressPreview::ReplaceSelectedAddress(const OUString& rNew) +{ + m_pImpl->aAddresses[m_pImpl->nSelectedAddress] = rNew; + Invalidate(); +} + +void SwAddressPreview::RemoveSelectedAddress() +{ + m_pImpl->aAddresses.erase(m_pImpl->aAddresses.begin() + m_pImpl->nSelectedAddress); + if(m_pImpl->nSelectedAddress) + --m_pImpl->nSelectedAddress; + UpdateScrollBar(); + Invalidate(); +} + +void SwAddressPreview::SetLayout(sal_uInt16 nRows, sal_uInt16 nColumns) +{ + m_pImpl->nRows = nRows; + m_pImpl->nColumns = nColumns; + UpdateScrollBar(); +} + +void SwAddressPreview::EnableScrollBar() +{ + m_pImpl->bEnableScrollBar = true; +} + +void SwAddressPreview::UpdateScrollBar() +{ + if (m_pImpl->nColumns) + { + sal_uInt16 nResultingRows = o3tl::narrowing<sal_uInt16>(m_pImpl->aAddresses.size() + m_pImpl->nColumns - 1) / m_pImpl->nColumns; + ++nResultingRows; + auto nValue = m_xVScrollBar->vadjustment_get_value(); + if (nValue > nResultingRows) + nValue = nResultingRows; + m_xVScrollBar->set_vpolicy(m_pImpl->bEnableScrollBar && nResultingRows > m_pImpl->nRows ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); + m_xVScrollBar->vadjustment_configure(nValue, 0, nResultingRows, 1, 10, m_pImpl->nRows); + } +} + +void SwAddressPreview::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetFillColor(rSettings.GetWindowColor()); + rRenderContext.SetLineColor(COL_TRANSPARENT); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), GetOutputSizePixel())); + Color aPaintColor(IsEnabled() ? rSettings.GetWindowTextColor() : rSettings.GetDisableColor()); + rRenderContext.SetLineColor(aPaintColor); + + weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font()); + vcl::Font aFont(rRenderContext.GetFont()); + aFont.SetColor(aPaintColor); + rRenderContext.SetFont(aFont); + + Size aSize(GetOutputSizePixel()); + sal_uInt16 nStartRow = 0; + if (m_xVScrollBar->get_vpolicy() != VclPolicyType::NEVER) + { + aSize.AdjustWidth(-m_xVScrollBar->get_scroll_thickness()); + nStartRow = m_xVScrollBar->vadjustment_get_value(); + } + Size aPartSize(aSize.Width() / m_pImpl->nColumns, + aSize.Height() / m_pImpl->nRows); + aPartSize.AdjustWidth( -2 ); + aPartSize.AdjustHeight( -2 ); + + sal_uInt16 nAddress = nStartRow * m_pImpl->nColumns; + const sal_uInt16 nNumAddresses = o3tl::narrowing<sal_uInt16>(m_pImpl->aAddresses.size()); + for (sal_uInt16 nRow = 0; nRow < m_pImpl->nRows ; ++nRow) + { + for (sal_uInt16 nCol = 0; nCol < m_pImpl->nColumns; ++nCol) + { + if (nAddress >= nNumAddresses) + break; + Point aPos(nCol * aPartSize.Width(), + nRow * aPartSize.Height()); + aPos.Move(1, 1); + bool bIsSelected = nAddress == m_pImpl->nSelectedAddress; + if ((m_pImpl->nColumns * m_pImpl->nRows) == 1) + bIsSelected = false; + OUString adr(m_pImpl->aAddresses[nAddress]); + DrawText_Impl(rRenderContext, adr, aPos, aPartSize, bIsSelected); + ++nAddress; + } + } + rRenderContext.SetClipRegion(); +} + +bool SwAddressPreview::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (rMEvt.IsLeft() && m_pImpl->nRows && m_pImpl->nColumns) + { + //determine the selected address + const Point& rMousePos = rMEvt.GetPosPixel(); + Size aSize(GetOutputSizePixel()); + Size aPartSize( aSize.Width()/m_pImpl->nColumns, aSize.Height()/m_pImpl->nRows ); + sal_uInt32 nRow = rMousePos.Y() / aPartSize.Height() ; + if (m_xVScrollBar->get_vpolicy() != VclPolicyType::NEVER) + { + nRow += m_xVScrollBar->vadjustment_get_value(); + } + sal_uInt32 nCol = rMousePos.X() / aPartSize.Width(); + sal_uInt32 nSelect = nRow * m_pImpl->nColumns + nCol; + + if( nSelect < m_pImpl->aAddresses.size() && + m_pImpl->nSelectedAddress != o3tl::narrowing<sal_uInt16>(nSelect)) + { + m_pImpl->nSelectedAddress = o3tl::narrowing<sal_uInt16>(nSelect); + m_aSelectHdl.Call(nullptr); + } + Invalidate(); + } + return true; +} + +bool SwAddressPreview::KeyInput( const KeyEvent& rKEvt ) +{ + sal_uInt16 nKey = rKEvt.GetKeyCode().GetCode(); + bool bHandled = false; + if (m_pImpl->nRows && m_pImpl->nColumns) + { + sal_uInt32 nSelectedRow = m_pImpl->nSelectedAddress / m_pImpl->nColumns; + sal_uInt32 nSelectedColumn = m_pImpl->nSelectedAddress - (nSelectedRow * m_pImpl->nColumns); + switch(nKey) + { + case KEY_UP: + if(nSelectedRow) + --nSelectedRow; + bHandled = true; + break; + case KEY_DOWN: + if(m_pImpl->aAddresses.size() > o3tl::make_unsigned(m_pImpl->nSelectedAddress + m_pImpl->nColumns)) + ++nSelectedRow; + bHandled = true; + break; + case KEY_LEFT: + if(nSelectedColumn) + --nSelectedColumn; + bHandled = true; + break; + case KEY_RIGHT: + if(nSelectedColumn < o3tl::make_unsigned(m_pImpl->nColumns - 1) && + m_pImpl->aAddresses.size() - 1 > m_pImpl->nSelectedAddress ) + ++nSelectedColumn; + bHandled = true; + break; + } + sal_uInt32 nSelect = nSelectedRow * m_pImpl->nColumns + nSelectedColumn; + if( nSelect < m_pImpl->aAddresses.size() && + m_pImpl->nSelectedAddress != o3tl::narrowing<sal_uInt16>(nSelect)) + { + m_pImpl->nSelectedAddress = o3tl::narrowing<sal_uInt16>(nSelect); + m_aSelectHdl.Call(nullptr); + Invalidate(); + } + } + return bHandled; +} + +void SwAddressPreview::DrawText_Impl(vcl::RenderContext& rRenderContext, std::u16string_view rAddress, + const Point& rTopLeft, const Size& rSize, bool bIsSelected) +{ + rRenderContext.SetClipRegion(vcl::Region(tools::Rectangle(rTopLeft, rSize))); + if (bIsSelected) + { + //selection rectangle + rRenderContext.SetFillColor(COL_TRANSPARENT); + rRenderContext.DrawRect(tools::Rectangle(rTopLeft, rSize)); + } + sal_Int32 nHeight = GetTextHeight(); + Point aStart = rTopLeft; + //put it away from the border + aStart.Move(2, 2); + sal_Int32 nPos = 0; + do + { + rRenderContext.DrawText(aStart, OUString(o3tl::getToken(rAddress, 0, '\n', nPos))); + aStart.AdjustY(nHeight ); + } + while (nPos >= 0); +} + +SwMergeAddressItem SwAddressIterator::Next() +{ + //currently the string may either start with a '<' then it's a column + //otherwise it's simple text maybe containing a return + SwMergeAddressItem aRet; + if(!m_sAddress.isEmpty()) + { + if(m_sAddress[0] == '<') + { + aRet.bIsColumn = true; + sal_Int32 nClose = m_sAddress.indexOf('>'); + OSL_ENSURE(nClose != -1, "closing '>' not found"); + if( nClose != -1 ) + { + aRet.sText = m_sAddress.copy(1, nClose - 1); + m_sAddress = m_sAddress.copy(nClose + 1); + } + else + { + aRet.sText = m_sAddress.copy(1, 1); + m_sAddress = m_sAddress.copy(1); + } + } + else + { + sal_Int32 nOpen = m_sAddress.indexOf('<'); + sal_Int32 nReturn = m_sAddress.indexOf('\n'); + if(nReturn == 0) + { + aRet.bIsReturn = true; + aRet.sText = "\n"; + m_sAddress = m_sAddress.copy(1); + } + else if(-1 == nOpen && -1 == nReturn) + { + aRet.sText = m_sAddress; + m_sAddress.clear(); + } + else + { + if (nOpen == -1) + nOpen = m_sAddress.getLength(); + if (nReturn == -1) + nReturn = m_sAddress.getLength(); + sal_Int32 nTarget = std::min(nOpen, nReturn); + aRet.sText = m_sAddress.copy(0, nTarget); + m_sAddress = m_sAddress.copy(nTarget); + } + } + } + return aRet; + +} + +SwAuthenticator::~SwAuthenticator() +{ +} + +OUString SwAuthenticator::getUserName( ) +{ + return m_aUserName; +} + +OUString SwAuthenticator::getPassword( ) +{ + if(!m_aUserName.isEmpty() && m_aPassword.isEmpty() && m_pParentWindow) + { + SfxPasswordDialog aPasswdDlg(m_pParentWindow); + aPasswdDlg.SetMinLen(0); + if (RET_OK == aPasswdDlg.run()) + m_aPassword = aPasswdDlg.GetPassword(); + } + return m_aPassword; +} + +SwConnectionContext::SwConnectionContext( + OUString aMailServer, sal_Int16 nPort, + OUString aConnectionType) : + m_sMailServer(std::move(aMailServer)), + m_nPort(nPort), + m_sConnectionType(std::move(aConnectionType)) +{ +} + +SwConnectionContext::~SwConnectionContext() +{ +} + +uno::Any SwConnectionContext::getValueByName( const OUString& rName ) +{ + uno::Any aRet; + if( rName == "ServerName" ) + aRet <<= m_sMailServer; + else if( rName == "Port" ) + aRet <<= static_cast<sal_Int32>(m_nPort); + else if( rName == "ConnectionType" ) + aRet <<= m_sConnectionType; + return aRet; +} + +SwConnectionListener::~SwConnectionListener() +{ +} + +void SwConnectionListener::connected(const lang::EventObject& /*aEvent*/) +{ +} + +void SwConnectionListener::disconnected(const lang::EventObject& /*aEvent*/) +{ +} + +void SwConnectionListener::disposing(const lang::EventObject& /*aEvent*/) +{ +} + +SwMailTransferable::SwMailTransferable(OUString aBody, OUString aMimeType) : + m_aMimeType(std::move( aMimeType )), + m_sBody(std::move( aBody )), + m_bIsBody( true ) +{ +} + +SwMailTransferable::SwMailTransferable(OUString aURL, + OUString aName, OUString aMimeType) : + m_aMimeType(std::move( aMimeType )), + m_aURL(std::move(aURL)), + m_aName(std::move( aName )), + m_bIsBody( false ) +{ +} + +SwMailTransferable::~SwMailTransferable() +{ +} + +uno::Any SwMailTransferable::getTransferData( const datatransfer::DataFlavor& /*aFlavor*/ ) +{ + uno::Any aRet; + if( m_bIsBody ) + aRet <<= m_sBody; + else + { + Sequence<sal_Int8> aData; + SfxMedium aMedium( m_aURL, StreamMode::STD_READ ); + SvStream* pStream = aMedium.GetInStream(); + if ( aMedium.GetErrorCode() == ERRCODE_NONE && pStream) + { + aData.realloc(pStream->TellEnd()); + pStream->Seek(0); + sal_Int8 * pData = aData.getArray(); + pStream->ReadBytes( pData, aData.getLength() ); + } + aRet <<= aData; + } + return aRet; +} + +uno::Sequence< datatransfer::DataFlavor > SwMailTransferable::getTransferDataFlavors( ) +{ + datatransfer::DataFlavor aRet; + aRet.MimeType = m_aMimeType; + if( m_bIsBody ) + { + aRet.DataType = cppu::UnoType<OUString>::get(); + } + else + { + aRet.HumanPresentableName = m_aName; + aRet.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get(); + } + return { aRet }; +} + +sal_Bool SwMailTransferable::isDataFlavorSupported( + const datatransfer::DataFlavor& aFlavor ) +{ + return (aFlavor.MimeType == m_aMimeType); +} + +uno::Reference< beans::XPropertySetInfo > SwMailTransferable::getPropertySetInfo( ) +{ + return uno::Reference< beans::XPropertySetInfo >(); +} + +void SwMailTransferable::setPropertyValue( const OUString& , const uno::Any& ) +{ +} + +uno::Any SwMailTransferable::getPropertyValue( const OUString& rPropertyName ) +{ + uno::Any aRet; + if ( rPropertyName == "URL" ) + aRet <<= m_aURL; + return aRet; +} + +void SwMailTransferable::addPropertyChangeListener( + const OUString&, const uno::Reference< beans::XPropertyChangeListener >& ) +{ +} + +void SwMailTransferable::removePropertyChangeListener( + const OUString&, + const uno::Reference< beans::XPropertyChangeListener >& ) +{ +} + +void SwMailTransferable::addVetoableChangeListener( + const OUString&, + const uno::Reference< beans::XVetoableChangeListener >& ) +{ +} + +void SwMailTransferable::removeVetoableChangeListener( + const OUString& , + const uno::Reference< beans::XVetoableChangeListener >& ) +{ +} + +SwMailMessage::SwMailMessage() +{ +} + +SwMailMessage::~SwMailMessage() +{ +} + +OUString SwMailMessage::getSenderName() +{ + return m_sSenderName; +} + +OUString SwMailMessage::getSenderAddress() +{ + return m_sSenderAddress; +} + +OUString SwMailMessage::getReplyToAddress() +{ + return m_sReplyToAddress; +} + +void SwMailMessage::setReplyToAddress( const OUString& _replytoaddress ) +{ + m_sReplyToAddress = _replytoaddress; +} + +OUString SwMailMessage::getSubject() +{ + return m_sSubject; +} + +void SwMailMessage::setSubject( const OUString& _subject ) +{ + m_sSubject = _subject; +} + +uno::Reference< datatransfer::XTransferable > SwMailMessage::getBody() +{ + return m_xBody; +} + +void SwMailMessage::setBody( + const uno::Reference< datatransfer::XTransferable >& rBody ) +{ + m_xBody = rBody; +} + +void SwMailMessage::addRecipient( const OUString& rRecipientAddress ) +{ + m_aRecipients.realloc(m_aRecipients.getLength() + 1); + m_aRecipients.getArray()[m_aRecipients.getLength() - 1] = rRecipientAddress; +} + +void SwMailMessage::addCcRecipient( const OUString& rRecipientAddress ) +{ + m_aCcRecipients.realloc(m_aCcRecipients.getLength() + 1); + m_aCcRecipients.getArray()[m_aCcRecipients.getLength() - 1] = rRecipientAddress; + +} + +void SwMailMessage::addBccRecipient( const OUString& rRecipientAddress ) +{ + m_aBccRecipients.realloc(m_aBccRecipients.getLength() + 1); + m_aBccRecipients.getArray()[m_aBccRecipients.getLength() - 1] = rRecipientAddress; +} + +uno::Sequence< OUString > SwMailMessage::getRecipients( ) +{ + return m_aRecipients; +} + +uno::Sequence< OUString > SwMailMessage::getCcRecipients( ) +{ + return m_aCcRecipients; +} + +uno::Sequence< OUString > SwMailMessage::getBccRecipients( ) +{ + return m_aBccRecipients; +} + +void SwMailMessage::addAttachment( const mail::MailAttachment& rMailAttachment ) +{ + m_aAttachments.realloc(m_aAttachments.getLength() + 1); + m_aAttachments.getArray()[m_aAttachments.getLength() - 1] = rMailAttachment; +} + +uno::Sequence< mail::MailAttachment > SwMailMessage::getAttachments( ) +{ + return m_aAttachments; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx b/sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx new file mode 100644 index 0000000000..a6496a12c1 --- /dev/null +++ b/sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx @@ -0,0 +1,370 @@ +/* -*- 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 <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <strings.hrc> +#include <mmconfigitem.hxx> +#include <swmodule.hxx> +#include <view.hxx> + +using namespace css; + +namespace { + +class CurrentEdit final : public InterimItemWindow +{ +private: + std::unique_ptr<weld::Entry> m_xWidget; + + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); +public: + CurrentEdit(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/swriter/ui/editbox.ui", "EditBox") + , m_xWidget(m_xBuilder->weld_entry("entry")) + { + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_key_press(LINK(this, CurrentEdit, KeyInputHdl)); + SetSizePixel(m_xWidget->get_preferred_size()); + } + + virtual void dispose() override + { + m_xWidget.reset(); + InterimItemWindow::dispose(); + } + + void set_sensitive(bool bSensitive) + { + Enable(bSensitive); + m_xWidget->set_sensitive(bSensitive); + } + + bool get_sensitive() const + { + return m_xWidget->get_sensitive(); + } + + void set_text(const OUString& rText) + { + m_xWidget->set_text(rText); + } + + OUString get_text() const + { + return m_xWidget->get_text(); + } + + void connect_activate(const Link<weld::Entry&, bool>& rLink) + { + m_xWidget->connect_activate(rLink); + } + + virtual ~CurrentEdit() override + { + disposeOnce(); + } +}; + +IMPL_LINK(CurrentEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +/// Controller for .uno:MailMergeCurrentEntry toolbar checkbox: creates the checkbox & handles the value. +typedef cppu::ImplInheritanceHelper< ::svt::ToolboxController, css::lang::XServiceInfo> MMCurrentEntryController_Base; +class MMCurrentEntryController : public MMCurrentEntryController_Base +{ + VclPtr<CurrentEdit> m_xCurrentEdit; + + DECL_LINK(CurrentEditUpdatedHdl, weld::Entry&, bool); + +public: + explicit MMCurrentEntryController(const uno::Reference<uno::XComponentContext>& rContext) + : MMCurrentEntryController_Base(rContext, uno::Reference<frame::XFrame>(), ".uno:MailMergeCurrentEntry") + , m_xCurrentEdit(nullptr) + { + } + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "lo.writer.MMCurrentEntryController"; + } + + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override + { + return cppu::supportsService(this, rServiceName); + } + + virtual uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return { "com.sun.star.frame.ToolbarController" }; + } + + // XComponent + virtual void SAL_CALL dispose() override; + + // XToolbarController + virtual uno::Reference<awt::XWindow> SAL_CALL createItemWindow(const uno::Reference<awt::XWindow>& rParent) override; + + // XStatusListener + virtual void SAL_CALL statusChanged(const frame::FeatureStateEvent& rEvent) override; +}; + +class ExcludeCheckBox final : public InterimItemWindow +{ +private: + std::unique_ptr<weld::CheckButton> m_xWidget; + + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); +public: + ExcludeCheckBox(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/swriter/ui/checkbox.ui", "CheckBox") + , m_xWidget(m_xBuilder->weld_check_button("checkbutton")) + { + InitControlBase(m_xWidget.get()); + + m_xWidget->set_label(SwResId(ST_EXCLUDE)); + m_xWidget->connect_key_press(LINK(this, ExcludeCheckBox, KeyInputHdl)); + SetSizePixel(m_xWidget->get_preferred_size()); + } + + virtual void dispose() override + { + m_xWidget.reset(); + InterimItemWindow::dispose(); + } + + void set_sensitive(bool bSensitive) + { + Enable(bSensitive); + m_xWidget->set_sensitive(bSensitive); + } + + void set_active(bool bActive) + { + m_xWidget->set_active(bActive); + } + + void connect_toggled(const Link<weld::Toggleable&, void>& rLink) + { + m_xWidget->connect_toggled(rLink); + } + + virtual ~ExcludeCheckBox() override + { + disposeOnce(); + } +}; + +IMPL_LINK(ExcludeCheckBox, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +/// Controller for .uno:MailMergeExcludeEntry toolbar checkbox: creates the checkbox & handles the value. +typedef cppu::ImplInheritanceHelper< ::svt::ToolboxController, css::lang::XServiceInfo> MMExcludeEntryController_Base; +class MMExcludeEntryController : public MMExcludeEntryController_Base +{ + VclPtr<ExcludeCheckBox> m_xExcludeCheckbox; + + DECL_STATIC_LINK(MMExcludeEntryController, ExcludeHdl, weld::Toggleable&, void); + +public: + explicit MMExcludeEntryController(const uno::Reference<uno::XComponentContext>& rContext) + : MMExcludeEntryController_Base(rContext, uno::Reference<frame::XFrame>(), ".uno:MailMergeExcludeEntry") + , m_xExcludeCheckbox(nullptr) + { + } + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "lo.writer.MMExcludeEntryController"; + } + + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override + { + return cppu::supportsService(this, rServiceName); + } + + virtual uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return { "com.sun.star.frame.ToolbarController" }; + } + + // XComponent + virtual void SAL_CALL dispose() override; + + // XToolbarController + virtual uno::Reference<awt::XWindow> SAL_CALL createItemWindow(const uno::Reference<awt::XWindow>& rParent) override; + + // XStatusListener + virtual void SAL_CALL statusChanged(const frame::FeatureStateEvent& rEvent) override; +}; + +void MMCurrentEntryController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + svt::ToolboxController::dispose(); + m_xCurrentEdit.disposeAndClear(); +} + +uno::Reference<awt::XWindow> MMCurrentEntryController::createItemWindow(const uno::Reference<awt::XWindow>& rParent) +{ + VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow(rParent); + ToolBox* pToolbar = dynamic_cast<ToolBox*>(pParent.get()); + if (pToolbar) + { + // make it visible + m_xCurrentEdit = VclPtr<CurrentEdit>::Create(pToolbar); + m_xCurrentEdit->connect_activate(LINK(this, MMCurrentEntryController, CurrentEditUpdatedHdl)); + } + + return VCLUnoHelper::GetInterface(m_xCurrentEdit); +} + +IMPL_LINK(MMCurrentEntryController, CurrentEditUpdatedHdl, weld::Entry&, rEdit, bool) +{ + std::shared_ptr<SwMailMergeConfigItem> xConfigItem; + if (SwView* pView = GetActiveView()) + xConfigItem = pView->GetMailMergeConfigItem(); + + if (!xConfigItem) + return true; + + OUString aText(rEdit.get_text()); + sal_Int32 nEntry = aText.toInt32(); + if (!aText.isEmpty() && nEntry != xConfigItem->GetResultSetPosition()) + { + xConfigItem->MoveResultSet(nEntry); + // notify about the change + dispatchCommand(".uno:MailMergeCurrentEntry", uno::Sequence<beans::PropertyValue>()); + } + return true; +}; + +void MMCurrentEntryController::statusChanged(const frame::FeatureStateEvent& rEvent) +{ + if (!m_xCurrentEdit) + return; + + std::shared_ptr<SwMailMergeConfigItem> xConfigItem; + if (SwView* pView = GetActiveView()) + xConfigItem = pView->GetMailMergeConfigItem(); + + if (!xConfigItem || !rEvent.IsEnabled) + { + m_xCurrentEdit->set_sensitive(false); + m_xCurrentEdit->set_text(""); + } + else + { + sal_Int32 nEntry = m_xCurrentEdit->get_text().toInt32(); + if (!m_xCurrentEdit->get_sensitive() || nEntry != xConfigItem->GetResultSetPosition()) + { + m_xCurrentEdit->set_sensitive(true); + m_xCurrentEdit->set_text(OUString::number(xConfigItem->GetResultSetPosition())); + } + } +} + +void MMExcludeEntryController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + svt::ToolboxController::dispose(); + m_xExcludeCheckbox.disposeAndClear(); +} + +uno::Reference<awt::XWindow> MMExcludeEntryController::createItemWindow(const uno::Reference<awt::XWindow>& rParent) +{ + VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow(rParent); + ToolBox* pToolbar = dynamic_cast<ToolBox*>(pParent.get()); + if (pToolbar) + { + // make it visible + m_xExcludeCheckbox = VclPtr<ExcludeCheckBox>::Create(pToolbar); + m_xExcludeCheckbox->connect_toggled(LINK(this, MMExcludeEntryController, ExcludeHdl)); + } + + return VCLUnoHelper::GetInterface(m_xExcludeCheckbox); +} + +IMPL_STATIC_LINK(MMExcludeEntryController, ExcludeHdl, weld::Toggleable&, rCheckbox, void) +{ + std::shared_ptr<SwMailMergeConfigItem> xConfigItem; + if (SwView* pView = GetActiveView()) + xConfigItem = pView->GetMailMergeConfigItem(); + + if (xConfigItem) + xConfigItem->ExcludeRecord(xConfigItem->GetResultSetPosition(), rCheckbox.get_active()); +}; + +void MMExcludeEntryController::statusChanged(const frame::FeatureStateEvent& rEvent) +{ + if (!m_xExcludeCheckbox) + return; + + std::shared_ptr<SwMailMergeConfigItem> xConfigItem; + if (SwView* pView = GetActiveView()) + xConfigItem = pView->GetMailMergeConfigItem(); + + if (!xConfigItem || !rEvent.IsEnabled) + { + m_xExcludeCheckbox->set_sensitive(false); + m_xExcludeCheckbox->set_active(false); + } + else + { + m_xExcludeCheckbox->set_sensitive(true); + m_xExcludeCheckbox->set_active(xConfigItem->IsRecordExcluded(xConfigItem->GetResultSetPosition())); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface * +lo_writer_MMCurrentEntryController_get_implementation( + uno::XComponentContext *context, + uno::Sequence<uno::Any> const &) +{ + return cppu::acquire(new MMCurrentEntryController(context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface * +lo_writer_MMExcludeEntryController_get_implementation( + uno::XComponentContext *context, + uno::Sequence<uno::Any> const &) +{ + return cppu::acquire(new MMExcludeEntryController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/dbui/mmconfigitem.cxx b/sw/source/uibase/dbui/mmconfigitem.cxx new file mode 100644 index 0000000000..c8f89a44d4 --- /dev/null +++ b/sw/source/uibase/dbui/mmconfigitem.cxx @@ -0,0 +1,1708 @@ +/* -*- 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 <mmconfigitem.hxx> +#include <comphelper/propertyvalue.hxx> +#include <vector> +#include <swtypes.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> +#include <com/sun/star/sdb/CommandType.hpp> +#include <comphelper/sequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <unotools/configitem.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <mailmergehelper.hxx> +#include <swunohelper.hxx> +#include <dbmgr.hxx> +#include <view.hxx> +#include <unodispatch.hxx> +#include <wrtsh.hxx> +#include <dbui.hrc> + +using namespace utl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; + +constexpr OUString cAddressDataAssignments = u"AddressDataAssignments"_ustr; +const char cDBColumnAssignments[] = "DBColumnAssignments"; +const char cDataSourceName[] = "DataSource/DataSourceName"; +const char cDataTableName[] = "DataSource/DataTableName" ; +const char cDataCommandType[] = "DataSource/DataCommandType"; + +#define SECURE_PORT 587 +#define DEFAULT_PORT 25 +#define POP_PORT 110 +#define POP_SECURE_PORT 995 +#define IMAP_PORT 143 +#define IMAP_SECURE_PORT 993 + +namespace { + +struct DBAddressDataAssignment +{ + SwDBData aDBData; + Sequence< OUString> aDBColumnAssignments; + //if loaded the name of the node has to be saved + OUString sConfigNodeName; + //all created or changed assignments need to be stored + bool bColumnAssignmentsChanged; + + DBAddressDataAssignment() : + bColumnAssignmentsChanged(false) + {} +}; + +} + +class SwMailMergeConfigItem_Impl : public utl::ConfigItem +{ + friend class SwMailMergeConfigItem; + Reference< XDataSource> m_xSource; + SharedConnection m_xConnection; + Reference< XColumnsSupplier> m_xColumnsSupplier; + Reference< XResultSet> m_xResultSet; + SwDBData m_aDBData; + OUString m_sFilter; + sal_Int32 m_nResultSetCursorPos; + + std::vector<DBAddressDataAssignment> m_aAddressDataAssignments; + std::vector< OUString> m_aAddressBlocks; + sal_Int32 m_nCurrentAddressBlock; + bool m_bIsAddressBlock; + bool m_bIsHideEmptyParagraphs; + + bool m_bIsOutputToLetter; + bool m_bIncludeCountry; + OUString m_sExcludeCountry; + + bool m_bIsGreetingLine; + bool m_bIsIndividualGreetingLine; + std::vector< OUString> m_aFemaleGreetingLines; + sal_Int32 m_nCurrentFemaleGreeting; + std::vector< OUString> m_aMaleGreetingLines; + sal_Int32 m_nCurrentMaleGreeting; + std::vector< OUString> m_aNeutralGreetingLines; + sal_Int32 m_nCurrentNeutralGreeting; + OUString m_sFemaleGenderValue; + uno::Sequence< OUString> m_aSavedDocuments; + + bool m_bIsGreetingLineInMail; + bool m_bIsIndividualGreetingLineInMail; + + //mail settings + OUString m_sMailDisplayName; + OUString m_sMailAddress; + OUString m_sMailReplyTo; + OUString m_sMailServer; + OUString m_sMailUserName; + OUString m_sMailPassword; + + bool m_bIsSMPTAfterPOP; + OUString m_sInServerName; + sal_Int16 m_nInServerPort; + bool m_bInServerPOP; + OUString m_sInServerUserName; + OUString m_sInServerPassword; + + sal_Int16 m_nMailPort; + bool m_bIsMailReplyTo; + bool m_bIsSecureConnection; + bool m_bIsAuthentication; + + bool m_bIsEMailSupported; + + std::vector<std::pair<OUString, int>> m_AddressHeaderSA; + + //these addresses are not stored in the configuration + std::vector< SwDocMergeInfo > m_aMergeInfos; + + //we do overwrite the usersettings in a special case + //then we do remind the usersettings here + bool m_bUserSettingWereOverwritten; + bool m_bIsAddressBlock_LastUserSetting; + bool m_bIsGreetingLineInMail_LastUserSetting; + bool m_bIsGreetingLine_LastUserSetting; + + static const Sequence< OUString>& GetPropertyNames(); + + virtual void ImplCommit() override; + +public: + SwMailMergeConfigItem_Impl(); + + virtual void Notify( const css::uno::Sequence< OUString >& aPropertyNames ) override; + Sequence< OUString> GetAddressBlocks(bool bConvertToConfig = false) const; + void SetAddressBlocks( + const Sequence< OUString>& rBlocks, + bool bConvertFromConfig = false); + uno::Sequence< OUString> + GetGreetings(SwMailMergeConfigItem::Gender eType, + bool bConvertToConfig = false) const; + void SetGreetings(SwMailMergeConfigItem::Gender eType, + const uno::Sequence< OUString>& rBlocks, + bool bConvertFromConfig = false); + + void SetCurrentAddressBlockIndex( sal_Int32 nSet ); + sal_Int32 GetCurrentAddressBlockIndex() const + { return m_nCurrentAddressBlock; } + sal_Int32 GetCurrentGreeting(SwMailMergeConfigItem::Gender eType) const; + void SetCurrentGreeting(SwMailMergeConfigItem::Gender eType, sal_Int32 nIndex); + +}; + +SwMailMergeConfigItem_Impl::SwMailMergeConfigItem_Impl() : + ConfigItem("Office.Writer/MailMergeWizard", ConfigItemMode::NONE), + m_nResultSetCursorPos(-1), + m_nCurrentAddressBlock(0), + m_bIsAddressBlock(true), + m_bIsHideEmptyParagraphs(false), + m_bIsOutputToLetter(true), + m_bIncludeCountry(false), + m_bIsGreetingLine(true), + m_bIsIndividualGreetingLine(false), + m_nCurrentFemaleGreeting(0), + m_nCurrentMaleGreeting(0), + m_nCurrentNeutralGreeting(0), + m_bIsGreetingLineInMail(false), + m_bIsIndividualGreetingLineInMail(false), + m_bIsSMPTAfterPOP(false), + m_nInServerPort( POP_SECURE_PORT ), + m_bInServerPOP( true ), + m_nMailPort(SECURE_PORT), + m_bIsMailReplyTo(false), + m_bIsSecureConnection(true), + m_bIsAuthentication(false), + + m_bIsEMailSupported(false), + m_bUserSettingWereOverwritten(false), + m_bIsAddressBlock_LastUserSetting(false), + m_bIsGreetingLineInMail_LastUserSetting(false), + m_bIsGreetingLine_LastUserSetting(false) +{ + for (auto const& [aName, aID] : SA_ADDRESS_HEADER) + { + m_AddressHeaderSA.emplace_back(SwResId(aName), aID); + } + + const Sequence<OUString>& rNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(rNames); + const Any* pValues = aValues.getConstArray(); + assert(aValues.getLength() == rNames.getLength()); + if(aValues.getLength() == rNames.getLength()) + { + for(int nProp = 0; nProp < rNames.getLength(); nProp++) + { + switch(nProp) + { + case 0: pValues[nProp] >>= m_bIsOutputToLetter; break; + case 1: pValues[nProp] >>= m_bIncludeCountry; break; + case 2: pValues[nProp] >>= m_sExcludeCountry; break; + case 3: + { + Sequence< OUString> aBlocks; + pValues[nProp] >>= aBlocks; + SetAddressBlocks(aBlocks, true); + } + break; + case 4: pValues[nProp] >>= m_bIsAddressBlock; break; + case 5: pValues[nProp] >>= m_bIsGreetingLine; break; + case 6: pValues[nProp] >>= m_bIsIndividualGreetingLine; break; + case 7 : + case 8 : + case 9 : + { + Sequence< OUString> aGreetings; + pValues[nProp] >>= aGreetings; + SetGreetings(SwMailMergeConfigItem::Gender( + SwMailMergeConfigItem::FEMALE + nProp - 7), aGreetings, true); + } + break; + + case 10: pValues[nProp] >>= m_nCurrentFemaleGreeting; break; + case 11: pValues[nProp] >>= m_nCurrentMaleGreeting; break; + case 12: pValues[nProp] >>= m_nCurrentNeutralGreeting; break; + case 13: pValues[nProp] >>= m_sFemaleGenderValue; break; + case 14: pValues[nProp] >>= m_sMailDisplayName; break; + case 15: pValues[nProp] >>= m_sMailAddress; break; + case 16: pValues[nProp] >>= m_bIsMailReplyTo; break; + case 17: pValues[nProp] >>= m_sMailReplyTo; break; + case 18: pValues[nProp] >>= m_sMailServer; break; + case 19: pValues[nProp] >>= m_nMailPort; break; + case 20: pValues[nProp] >>= m_bIsSecureConnection; break; + case 21: pValues[nProp] >>= m_bIsAuthentication; break; + case 22: pValues[nProp] >>= m_sMailUserName; break; + case 23: pValues[nProp] >>= m_sMailPassword; break; + case 24 :pValues[nProp] >>= m_aDBData.sDataSource; break; + case 25 :pValues[nProp] >>= m_aDBData.sCommand; break; + case 26 : + { + short nTemp = 0; + if(pValues[nProp] >>= nTemp) + m_aDBData.nCommandType = nTemp; + } + break; + case 27: pValues[nProp] >>= m_sFilter; break; + case 28: pValues[nProp] >>= m_aSavedDocuments; break; + case 29: + pValues[nProp] >>= m_bIsEMailSupported; + break; + case 30: pValues[nProp] >>= m_bIsGreetingLineInMail; break; + case 31: pValues[nProp] >>= m_bIsIndividualGreetingLineInMail; break; + case 32: pValues[nProp] >>= m_bIsSMPTAfterPOP; break; + case 33: pValues[nProp] >>= m_sInServerName; break; + case 34: pValues[nProp] >>= m_nInServerPort; break; + case 35: pValues[nProp] >>= m_bInServerPOP; break; + case 36: pValues[nProp] >>= m_sInServerUserName; break; + case 37: pValues[nProp] >>= m_sInServerPassword; break; + case 38: pValues[nProp] >>= m_bIsHideEmptyParagraphs; break; + case 39: pValues[nProp] >>= m_nCurrentAddressBlock; break; + } + } + ClearModified(); + } + //read the list of data base assignments + Sequence<OUString> aAssignments = GetNodeNames(cAddressDataAssignments); + if(aAssignments.hasElements()) + { + //create a list of property names to load the URLs of all data bases + const OUString* pAssignments = aAssignments.getConstArray(); + Sequence< OUString > aAssignProperties(4 * aAssignments.getLength()); + OUString* pAssignProperties = aAssignProperties.getArray(); + sal_Int32 nAssign; + for(nAssign = 0; nAssign < aAssignProperties.getLength(); nAssign += 4) + { + OUString sAssignPath = OUString::Concat(cAddressDataAssignments) + + "/" + + pAssignments[nAssign / 4] + + "/"; + pAssignProperties[nAssign] = sAssignPath; + pAssignProperties[nAssign] += cDataSourceName; + pAssignProperties[nAssign + 1] = sAssignPath; + pAssignProperties[nAssign + 1] += cDataTableName; + pAssignProperties[nAssign + 2] = sAssignPath; + pAssignProperties[nAssign + 2] += cDataCommandType; + pAssignProperties[nAssign + 3] = sAssignPath; + pAssignProperties[nAssign + 3] += cDBColumnAssignments; + } + Sequence<Any> aAssignValues = GetProperties(aAssignProperties); + const Any* pAssignValues = aAssignValues.getConstArray(); + for(nAssign = 0; nAssign < aAssignValues.getLength(); nAssign += 4 ) + { + DBAddressDataAssignment aAssignment; + pAssignValues[nAssign] >>= aAssignment.aDBData.sDataSource; + pAssignValues[nAssign + 1] >>= aAssignment.aDBData.sCommand; + pAssignValues[nAssign + 2] >>= aAssignment.aDBData.nCommandType; + pAssignValues[nAssign + 3] >>= aAssignment.aDBColumnAssignments; + aAssignment.sConfigNodeName = pAssignments[nAssign / 4]; + m_aAddressDataAssignments.push_back(aAssignment); + } + } + //check if the saved documents still exist + if(!m_aSavedDocuments.hasElements()) + return; + + uno::Sequence< OUString > aTempDocuments(m_aSavedDocuments.getLength()); + auto begin = aTempDocuments.getArray(); + OUString* pTempDocuments = std::copy_if(std::cbegin(m_aSavedDocuments), std::cend(m_aSavedDocuments), begin, + [](const OUString& rDoc) { return SWUnoHelper::UCB_IsFile( rDoc ); }); + sal_Int32 nIndex = static_cast<sal_Int32>(std::distance(begin, pTempDocuments)); + if(nIndex < m_aSavedDocuments.getLength()) + { + m_aSavedDocuments.swap(aTempDocuments); + m_aSavedDocuments.realloc(nIndex); + } +} + +void SwMailMergeConfigItem_Impl::SetCurrentAddressBlockIndex( sal_Int32 nSet ) +{ + if(m_aAddressBlocks.size() >= sal::static_int_cast<sal_uInt32, sal_Int32>(nSet)) + { + m_nCurrentAddressBlock = nSet; + SetModified(); + } +} + +static OUString lcl_CreateNodeName(Sequence<OUString>& rAssignments ) +{ + sal_Int32 nStart = rAssignments.getLength(); + OUString sNewName; + //search if the name exists + while(true) + { + sNewName = "_" + OUString::number(nStart++); + if(comphelper::findValue(rAssignments, sNewName) == -1) + break; + } + // add the new name to the array of existing names + rAssignments.realloc(rAssignments.getLength() + 1); + rAssignments.getArray()[rAssignments.getLength() - 1] = sNewName; + return sNewName; +} + +static void lcl_ConvertToNumbers(OUString& rBlock, const std::vector<std::pair<OUString, int>>& rHeaders ) +{ + //convert the strings used for UI to numbers used for the configuration + OUString sBlock(rBlock.replaceAll("\n", "\\n")); + for (size_t i = 0; i < rHeaders.size(); ++i) + { + OUString sHeader = "<" + rHeaders[i].first + ">"; + OUString sReplace = "<" + OUStringChar(sal_Unicode('0' + i)) + ">"; + sBlock = sBlock.replaceAll(sHeader, sReplace); + } + rBlock = sBlock; +} + +static void lcl_ConvertFromNumbers(OUString& rBlock, const std::vector<std::pair<OUString, int>>& rHeaders) +{ + //convert the numbers used for the configuration to strings used for UI to numbers + //doesn't use ReplaceAll to prevent expansion of numbers inside of the headers + SwAddressIterator aGreetingIter(rBlock.replaceAll("\\n", "\n")); + OUStringBuffer sBlock; + while(aGreetingIter.HasMore()) + { + SwMergeAddressItem aNext = aGreetingIter.Next(); + if(aNext.bIsColumn) + { + //the text should be 1 characters long + sal_Unicode cChar = aNext.sText[0]; + if(cChar >= '0' && cChar <= 'c') + { + sBlock.append("<"); + sal_uInt16 nHeader = cChar - '0'; + if(nHeader < rHeaders.size()) + sBlock.append(rHeaders[nHeader].first); + sBlock.append(">"); + } + else + { + SAL_WARN("sw.ui", "parse error in address block or greeting line"); + } + } + else + sBlock.append(aNext.sText); + } + rBlock = sBlock.makeStringAndClear(); +} + +const Sequence<OUString>& SwMailMergeConfigItem_Impl::GetPropertyNames() +{ + static Sequence<OUString> aNames { + "OutputToLetter", // 0 + "IncludeCountry", // 1 + "ExcludeCountry", // 2 + "AddressBlockSettings", // 3 + "IsAddressBlock", // 4 + "IsGreetingLine", // 5 + "IsIndividualGreetingLine", // 6 + "FemaleGreetingLines", // 7 + "MaleGreetingLines", // 8 + "NeutralGreetingLines", // 9 + "CurrentFemaleGreeting", // 10 + "CurrentMaleGreeting", // 11 + "CurrentNeutralGreeting", // 12 + "FemaleGenderValue", // 13 + "MailDisplayName", // 14 + "MailAddress", // 15 + "IsMailReplyTo", // 16 + "MailReplyTo", // 17 + "MailServer", // 18 + "MailPort", // 19 + "IsSecureConnection", // 20 + "IsAuthentication", // 21 + "MailUserName", // 22 + "MailPassword", // 23 + "DataSource/DataSourceName", // 24 + "DataSource/DataTableName", // 25 + "DataSource/DataCommandType",// 26 + "Filter", // 27 + "SavedDocuments", // 28 + "EMailSupported", // 29 + "IsEMailGreetingLine", //30 + "IsEMailIndividualGreetingLine", //31 + "IsSMPTAfterPOP", //32 + "InServerName", //33 + "InServerPort", //34 + "InServerIsPOP", //35 + "InServerUserName", //36 + "InServerPassword", //37 + "IsHideEmptyParagraphs", //38 + "CurrentAddressBlock" //39 + }; + return aNames; +} + +void SwMailMergeConfigItem_Impl::Notify( const css::uno::Sequence< OUString >& ) {} + +void SwMailMergeConfigItem_Impl::ImplCommit() +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case 0: pValues[nProp] <<= m_bIsOutputToLetter; break; + case 1: pValues[nProp] <<= m_bIncludeCountry; break; + case 2: pValues[nProp] <<= m_sExcludeCountry; break; + case 3: pValues[nProp] <<= GetAddressBlocks(true); break; + case 4: + { + if( m_bUserSettingWereOverwritten) + pValues[nProp] <<= m_bIsAddressBlock_LastUserSetting; + else + pValues[nProp] <<= m_bIsAddressBlock; + break; + } + case 5: + { + if( m_bUserSettingWereOverwritten) + pValues[nProp] <<= m_bIsGreetingLine_LastUserSetting; + else + pValues[nProp] <<= m_bIsGreetingLine; + break; + } + case 6: pValues[nProp] <<= m_bIsIndividualGreetingLine; break; + case 7: + case 8: + case 9: + pValues[nProp] <<= GetGreetings( + SwMailMergeConfigItem::Gender( + SwMailMergeConfigItem::FEMALE + nProp - 7), true); + break; + case 10: pValues[nProp] <<= m_nCurrentFemaleGreeting; break; + case 11: pValues[nProp] <<= m_nCurrentMaleGreeting; break; + case 12: pValues[nProp] <<= m_nCurrentNeutralGreeting; break; + case 13: pValues[nProp] <<= m_sFemaleGenderValue; break; + case 14: pValues[nProp] <<= m_sMailDisplayName; break; + case 15: pValues[nProp] <<= m_sMailAddress; break; + case 16: pValues[nProp] <<= m_bIsMailReplyTo; break; + case 17: pValues[nProp] <<= m_sMailReplyTo; break; + case 18: pValues[nProp] <<= m_sMailServer; break; + case 19: pValues[nProp] <<= m_nMailPort; break; + case 20: pValues[nProp] <<= m_bIsSecureConnection; break; + case 21: pValues[nProp] <<= m_bIsAuthentication; break; + case 22: pValues[nProp] <<= m_sMailUserName; break; + case 23: pValues[nProp] <<= m_sMailPassword; break; + case 24 :pValues[nProp] <<= m_aDBData.sDataSource; break; + case 25 :pValues[nProp] <<= m_aDBData.sCommand; break; + case 26 :pValues[nProp] <<= m_aDBData.nCommandType; break; + case 27 :pValues[nProp] <<= m_sFilter; break; + case 28 :pValues[nProp] <<= m_aSavedDocuments; break; + case 29: pValues[nProp] <<= m_bIsEMailSupported; break; + case 30: + { + if( m_bUserSettingWereOverwritten) + pValues[nProp] <<= m_bIsGreetingLineInMail_LastUserSetting; + else + pValues[nProp] <<= m_bIsGreetingLineInMail; + break; + } + case 31: pValues[nProp] <<= m_bIsIndividualGreetingLineInMail; break; + case 32: pValues[nProp] <<= m_bIsSMPTAfterPOP; break; + case 33: pValues[nProp] <<= m_sInServerName; break; + case 34: pValues[nProp] <<= m_nInServerPort; break; + case 35: pValues[nProp] <<= m_bInServerPOP; break; + case 36: pValues[nProp] <<= m_sInServerUserName; break; + case 37: pValues[nProp] <<= m_sInServerPassword; break; + case 38: pValues[nProp] <<= m_bIsHideEmptyParagraphs; break; + case 39: pValues[nProp] <<= m_nCurrentAddressBlock; break; + } + } + PutProperties(aNames, aValues); + //store the changed / new assignments + + //load the existing node names to find new names + Sequence<OUString> aAssignments = GetNodeNames(cAddressDataAssignments); + + for(const auto& rAssignment : m_aAddressDataAssignments) + { + if(rAssignment.bColumnAssignmentsChanged) + { + //create a new node name + OUString sNewNode = !rAssignment.sConfigNodeName.isEmpty() ? + rAssignment.sConfigNodeName : + lcl_CreateNodeName(aAssignments); + OUString sSlash = "/"; + OUString sNodePath = cAddressDataAssignments + sSlash + sNewNode + sSlash; + //only one new entry is written + Sequence< PropertyValue > aNewValues + { + comphelper::makePropertyValue(sNodePath + cDataSourceName, rAssignment.aDBData.sDataSource), + comphelper::makePropertyValue(sNodePath + cDataTableName, rAssignment.aDBData.sCommand), + comphelper::makePropertyValue(sNodePath + cDataCommandType, rAssignment.aDBData.nCommandType), + comphelper::makePropertyValue(sNodePath + cDBColumnAssignments, rAssignment.aDBColumnAssignments) + }; + SetSetProperties(cAddressDataAssignments, aNewValues); + } + } + + m_bUserSettingWereOverwritten = false; +} + +Sequence< OUString> SwMailMergeConfigItem_Impl::GetAddressBlocks( + bool bConvertToConfig) const +{ + Sequence< OUString> aRet(m_aAddressBlocks.size()); + std::transform(m_aAddressBlocks.begin(), m_aAddressBlocks.end(), aRet.getArray(), + [this, bConvertToConfig](const OUString& rBlock) -> OUString { + OUString sBlock = rBlock; + if(bConvertToConfig) + lcl_ConvertToNumbers(sBlock, m_AddressHeaderSA); + return sBlock; + }); + return aRet; +} + +void SwMailMergeConfigItem_Impl::SetAddressBlocks( + const Sequence< OUString>& rBlocks, + bool bConvertFromConfig) +{ + m_aAddressBlocks.clear(); + std::transform(rBlocks.begin(), rBlocks.end(), std::back_inserter(m_aAddressBlocks), + [this, bConvertFromConfig](const OUString& rBlock) -> OUString { + OUString sBlock = rBlock; + if(bConvertFromConfig) + lcl_ConvertFromNumbers(sBlock, m_AddressHeaderSA); + return sBlock; + }); + m_nCurrentAddressBlock = 0; + SetModified(); +} + +Sequence< OUString> SwMailMergeConfigItem_Impl::GetGreetings( + SwMailMergeConfigItem::Gender eType, bool bConvertToConfig) const +{ + const std::vector< OUString>& rGreetings = + eType == SwMailMergeConfigItem::FEMALE ? m_aFemaleGreetingLines : + eType == SwMailMergeConfigItem::MALE ? m_aMaleGreetingLines : + m_aNeutralGreetingLines; + Sequence< OUString> aRet(rGreetings.size()); + std::transform(rGreetings.begin(), rGreetings.end(), aRet.getArray(), + [this, bConvertToConfig](const OUString& rGreeting) -> OUString { + OUString sGreeting = rGreeting; + if(bConvertToConfig) + lcl_ConvertToNumbers(sGreeting, m_AddressHeaderSA); + return sGreeting; + }); + return aRet; +} + +void SwMailMergeConfigItem_Impl::SetGreetings( + SwMailMergeConfigItem::Gender eType, + const Sequence< OUString>& rSetGreetings, + bool bConvertFromConfig) +{ + std::vector< OUString>& rGreetings = + eType == SwMailMergeConfigItem::FEMALE ? m_aFemaleGreetingLines : + eType == SwMailMergeConfigItem::MALE ? m_aMaleGreetingLines : + m_aNeutralGreetingLines; + + rGreetings.clear(); + std::transform(rSetGreetings.begin(), rSetGreetings.end(), std::back_inserter(rGreetings), + [this, bConvertFromConfig](const OUString& rGreeting) -> OUString { + OUString sGreeting = rGreeting; + if(bConvertFromConfig) + lcl_ConvertFromNumbers(sGreeting, m_AddressHeaderSA); + return sGreeting; + }); + SetModified(); +} + +sal_Int32 SwMailMergeConfigItem_Impl::GetCurrentGreeting( + SwMailMergeConfigItem::Gender eType) const +{ + sal_Int32 nRet; + switch(eType) + { + case SwMailMergeConfigItem::FEMALE: nRet = m_nCurrentFemaleGreeting ; break; + case SwMailMergeConfigItem::MALE: nRet = m_nCurrentMaleGreeting ; break; + default: nRet = m_nCurrentNeutralGreeting; break; + } + return nRet; +} + +void SwMailMergeConfigItem_Impl::SetCurrentGreeting( + SwMailMergeConfigItem::Gender eType, sal_Int32 nIndex) +{ + bool bChanged = false; + switch(eType) + { + case SwMailMergeConfigItem::FEMALE: + bChanged = m_nCurrentFemaleGreeting != nIndex; + m_nCurrentFemaleGreeting = nIndex; + break; + case SwMailMergeConfigItem::MALE: + bChanged = m_nCurrentMaleGreeting != nIndex; + m_nCurrentMaleGreeting = nIndex; + break; + default: + bChanged = m_nCurrentNeutralGreeting != nIndex; + m_nCurrentNeutralGreeting = nIndex; + } + if(bChanged) + SetModified(); +} + +SwMailMergeConfigItem::SwMailMergeConfigItem() : + m_pImpl(new SwMailMergeConfigItem_Impl), + m_bAddressInserted(false), + m_bGreetingInserted(false), + m_nGreetingMoves(0), + m_nBegin(0), + m_nEnd(0), + m_pSourceView(nullptr), + m_pTargetView(nullptr) +{ +} + +void SwMailMergeConfigItem::stopDBChangeListening() +{ + if (m_xDBChangedListener.is()) + { + uno::Reference<view::XSelectionSupplier> xSupplier = m_pSourceView->GetUNOObject(); + xSupplier->removeSelectionChangeListener(m_xDBChangedListener); + m_xDBChangedListener.clear(); + } +} + +void SwMailMergeConfigItem::updateCurrentDBDataFromDocument() +{ + const SwDBData& rDBData = m_pSourceView->GetWrtShell().GetDBData(); + SetCurrentDBData(rDBData); +} + +SwMailMergeConfigItem::~SwMailMergeConfigItem() +{ + stopDBChangeListening(); +} + +void SwMailMergeConfigItem::Commit() +{ + if(m_pImpl->IsModified()) + m_pImpl->Commit(); +} + +const std::vector<std::pair<OUString, int>>& SwMailMergeConfigItem::GetDefaultAddressHeaders() const +{ + return m_pImpl->m_AddressHeaderSA; +} + +void SwMailMergeConfigItem::SetAddressBlocks( + const Sequence< OUString>& rBlocks) +{ + m_pImpl->SetAddressBlocks(rBlocks); +} + +Sequence< OUString> SwMailMergeConfigItem::GetAddressBlocks() const +{ + return m_pImpl->GetAddressBlocks(); +} + +bool SwMailMergeConfigItem::IsAddressBlock()const +{ + return m_pImpl->m_bIsAddressBlock && IsOutputToLetter(); +} + +void SwMailMergeConfigItem::SetAddressBlock(bool bSet) +{ + m_pImpl->m_bUserSettingWereOverwritten = false; + if(m_pImpl->m_bIsAddressBlock != bSet) + { + m_pImpl->m_bIsAddressBlock = bSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsHideEmptyParagraphs() const +{ + return m_pImpl->m_bIsHideEmptyParagraphs; +} + +void SwMailMergeConfigItem::SetHideEmptyParagraphs(bool bSet) +{ + if(m_pImpl->m_bIsHideEmptyParagraphs != bSet) + { + m_pImpl->m_bIsHideEmptyParagraphs = bSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsIncludeCountry() const +{ + return m_pImpl->m_bIncludeCountry; +} + +OUString& SwMailMergeConfigItem::GetExcludeCountry() const +{ + return m_pImpl->m_sExcludeCountry; +} + +void SwMailMergeConfigItem::SetCountrySettings(bool bSet, const OUString& rCountry) +{ + if(m_pImpl->m_sExcludeCountry != rCountry || + m_pImpl->m_bIncludeCountry != bSet) + { + m_pImpl->m_bIncludeCountry = bSet; + m_pImpl->m_sExcludeCountry = bSet ? rCountry : OUString(); + m_pImpl->SetModified(); + } +} + +void SwMailMergeConfigItem::SetCurrentConnection( + Reference< XDataSource> const & xSource, + const SharedConnection& rConnection, + Reference< XColumnsSupplier> const & xColumnsSupplier, + const SwDBData& rDBData) +{ + m_pImpl->m_xSource = xSource ; + m_pImpl->m_xConnection = rConnection ; + m_pImpl->m_xColumnsSupplier = xColumnsSupplier; + m_pImpl->m_aDBData = rDBData; + m_pImpl->m_xResultSet = nullptr; + m_pImpl->m_nResultSetCursorPos = 0; + m_pImpl->SetModified(); +} + +Reference< XDataSource> const & SwMailMergeConfigItem::GetSource() const +{ + return m_pImpl->m_xSource; +} + +SharedConnection const & SwMailMergeConfigItem::GetConnection() const +{ + return m_pImpl->m_xConnection; +} + +Reference< XColumnsSupplier> const & SwMailMergeConfigItem::GetColumnsSupplier() +{ + if(!m_pImpl->m_xColumnsSupplier.is() && m_pImpl->m_xConnection.is()) + { + m_pImpl->m_xColumnsSupplier = SwDBManager::GetColumnSupplier(m_pImpl->m_xConnection, + m_pImpl->m_aDBData.sCommand, + m_pImpl->m_aDBData.nCommandType == CommandType::TABLE ? + SwDBSelect::TABLE : SwDBSelect::QUERY ); + } + return m_pImpl->m_xColumnsSupplier; +} + +const SwDBData& SwMailMergeConfigItem::GetCurrentDBData() const +{ + return m_pImpl->m_aDBData; +} + +void SwMailMergeConfigItem::SetCurrentDBData( const SwDBData& rDBData) +{ + if(m_pImpl->m_aDBData != rDBData) + { + m_pImpl->m_aDBData = rDBData; + m_pImpl->m_xConnection.clear(); + m_pImpl->m_xSource = nullptr; + m_pImpl->m_xResultSet = nullptr; + m_pImpl->m_xColumnsSupplier = nullptr; + m_pImpl->SetModified(); + } +} + +Reference< XResultSet> const & SwMailMergeConfigItem::GetResultSet() const +{ + if(!m_pImpl->m_xConnection.is() && !m_pImpl->m_aDBData.sDataSource.isEmpty()) + { + m_pImpl->m_xConnection.reset( + SwDBManager::GetConnection(m_pImpl->m_aDBData.sDataSource, m_pImpl->m_xSource, m_pSourceView), + SharedConnection::TakeOwnership + ); + } + if(!m_pImpl->m_xResultSet.is() && m_pImpl->m_xConnection.is()) + { + try + { + Reference< XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() ); + + Reference<XRowSet> xRowSet( xMgr->createInstance("com.sun.star.sdb.RowSet"), UNO_QUERY ); + Reference<XPropertySet> xRowProperties(xRowSet, UNO_QUERY); + xRowProperties->setPropertyValue("DataSourceName", Any(m_pImpl->m_aDBData.sDataSource)); + xRowProperties->setPropertyValue("Command", Any(m_pImpl->m_aDBData.sCommand)); + xRowProperties->setPropertyValue("CommandType", Any(m_pImpl->m_aDBData.nCommandType)); + xRowProperties->setPropertyValue("FetchSize", Any(sal_Int32(10))); + xRowProperties->setPropertyValue("ActiveConnection", Any(m_pImpl->m_xConnection.getTyped())); + try + { + xRowProperties->setPropertyValue("ApplyFilter", Any(!m_pImpl->m_sFilter.isEmpty())); + xRowProperties->setPropertyValue("Filter", Any(m_pImpl->m_sFilter)); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("sw.ui", ""); + } + xRowSet->execute(); + m_pImpl->m_xResultSet = xRowSet.get(); + m_pImpl->m_xResultSet->first(); + m_pImpl->m_nResultSetCursorPos = 1; + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("sw.ui", "SwMailMergeConfigItem::GetResultSet()"); + } + } + return m_pImpl->m_xResultSet; +} + +void SwMailMergeConfigItem::DisposeResultSet() +{ + m_pImpl->m_xConnection.clear(); + if(m_pImpl->m_xResultSet.is()) + { + ::comphelper::disposeComponent( m_pImpl->m_xResultSet ); + } +} + +OUString& SwMailMergeConfigItem::GetFilter() const +{ + return m_pImpl->m_sFilter; +} + +void SwMailMergeConfigItem::SetFilter(OUString const & rFilter) +{ + if(m_pImpl->m_sFilter == rFilter) + return; + + m_pImpl->m_sFilter = rFilter; + m_pImpl->SetModified(); + Reference<XPropertySet> xRowProperties(m_pImpl->m_xResultSet, UNO_QUERY); + if(!xRowProperties.is()) + return; + + try + { + xRowProperties->setPropertyValue("ApplyFilter", Any(!m_pImpl->m_sFilter.isEmpty())); + xRowProperties->setPropertyValue("Filter", Any(m_pImpl->m_sFilter)); + uno::Reference<XRowSet> xRowSet( m_pImpl->m_xResultSet, UNO_QUERY_THROW ); + xRowSet->execute(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("sw.ui", "SwMailMergeConfigItem::SetFilter()"); + } +} + +sal_Int32 SwMailMergeConfigItem::MoveResultSet(sal_Int32 nTarget) +{ + if(!m_pImpl->m_xResultSet.is()) + GetResultSet(); + if(m_pImpl->m_xResultSet.is()) + { + try + { + //no action if the resultset is already at the right position + if(m_pImpl->m_xResultSet->getRow() != nTarget) + { + if(nTarget > 0) + { + bool bMoved = m_pImpl->m_xResultSet->absolute(nTarget); + if(!bMoved) + { + if(nTarget > 1) + m_pImpl->m_xResultSet->last(); + else if(nTarget == 1) + m_pImpl->m_xResultSet->first(); + } + } + else if(nTarget == -1) + m_pImpl->m_xResultSet->last(); + m_pImpl->m_nResultSetCursorPos = m_pImpl->m_xResultSet->getRow(); + } + } + catch (const Exception&) + { + } + } + return m_pImpl->m_nResultSetCursorPos; +} + +bool SwMailMergeConfigItem::IsResultSetFirstLast(bool& bIsFirst, bool& bIsLast) +{ + bool bRet = false; + if(!m_pImpl->m_xResultSet.is()) + GetResultSet(); + if(m_pImpl->m_xResultSet.is()) + { + try + { + bIsFirst = m_pImpl->m_xResultSet->isFirst(); + bIsLast = m_pImpl->m_xResultSet->isLast(); + bRet = true; + } + catch (const Exception&) + { + } + } + return bRet; +} + +sal_Int32 SwMailMergeConfigItem::GetResultSetPosition() const +{ + return m_pImpl->m_nResultSetCursorPos; +} + +bool SwMailMergeConfigItem::IsRecordIncluded(sal_uInt32 nRecord) const + { return nRecord > m_nBegin && nRecord <= m_nEnd; } + +bool SwMailMergeConfigItem::IsRecordExcluded(sal_uInt32 nRecord) const + { return m_aExcludedRecords.find(nRecord) != m_aExcludedRecords.end(); } + +void SwMailMergeConfigItem::ExcludeRecord(sal_Int32 nRecord, bool bExclude) +{ + if(bExclude) + m_aExcludedRecords.insert(nRecord); + else + m_aExcludedRecords.erase(nRecord); +} + +uno::Sequence<uno::Any> SwMailMergeConfigItem::GetSelection() const +{ + if(!m_pImpl->m_xResultSet.is()) + GetResultSet(); + if(!m_pImpl->m_xResultSet.is()) + return {}; + m_pImpl->m_xResultSet->last(); + sal_uInt32 nResultSetSize = m_pImpl->m_xResultSet->getRow()+1; + std::vector<uno::Any> vResult; + for(sal_uInt32 nIdx=1; nIdx<nResultSetSize;++nIdx) + if( !IsRecordExcluded(nIdx) && IsRecordIncluded(nIdx) ) + vResult.push_back(uno::Any(sal_uInt32(nIdx))); + return comphelper::containerToSequence(vResult); +} + + +const uno::Sequence< OUString>& + SwMailMergeConfigItem::GetSavedDocuments() const +{ + return m_pImpl->m_aSavedDocuments; +} + +bool SwMailMergeConfigItem::IsOutputToLetter()const +{ + return m_pImpl->m_bIsOutputToLetter || !IsMailAvailable(); +} + +void SwMailMergeConfigItem::SetOutputToLetter(bool bSet) +{ + if(m_pImpl->m_bIsOutputToLetter != bSet) + { + m_pImpl->m_bIsOutputToLetter = bSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsIndividualGreeting(bool bInEMail) const +{ + return bInEMail ? + m_pImpl->m_bIsIndividualGreetingLineInMail : + m_pImpl->m_bIsIndividualGreetingLine; +} + +void SwMailMergeConfigItem::SetIndividualGreeting( + bool bSet, bool bInEMail) +{ + if(bInEMail) + { + if(m_pImpl->m_bIsIndividualGreetingLineInMail != bSet) + { + m_pImpl->m_bIsIndividualGreetingLineInMail = bSet; + m_pImpl->SetModified(); + } + } + else + { + if(m_pImpl->m_bIsIndividualGreetingLine != bSet) + { + m_pImpl->m_bIsIndividualGreetingLine = bSet; + m_pImpl->SetModified(); + } + } +} + +bool SwMailMergeConfigItem::IsGreetingLine(bool bInEMail) const +{ + return bInEMail ? m_pImpl->m_bIsGreetingLineInMail : m_pImpl->m_bIsGreetingLine; +} + +void SwMailMergeConfigItem::SetGreetingLine(bool bSet, bool bInEMail) +{ + m_pImpl->m_bUserSettingWereOverwritten = false; + if(bInEMail) + { + if(m_pImpl->m_bIsGreetingLineInMail != bSet) + { + m_pImpl->m_bIsGreetingLineInMail = bSet; + m_pImpl->SetModified(); + } + } + else + { + if(m_pImpl->m_bIsGreetingLine != bSet) + { + m_pImpl->m_bIsGreetingLine = bSet; + m_pImpl->SetModified(); + } + } +} + +Sequence< OUString> SwMailMergeConfigItem::GetGreetings( + Gender eType ) const +{ + return m_pImpl->GetGreetings(eType); +} + +void SwMailMergeConfigItem::SetGreetings( + Gender eType, const Sequence< OUString>& rSetGreetings) +{ + m_pImpl->SetGreetings( eType, rSetGreetings); +} + +sal_Int32 SwMailMergeConfigItem::GetCurrentGreeting( + SwMailMergeConfigItem::Gender eType) const +{ + return m_pImpl->GetCurrentGreeting(eType); +} + +void SwMailMergeConfigItem::SetCurrentGreeting(Gender eType, sal_Int32 nIndex) +{ + m_pImpl->SetCurrentGreeting(eType, nIndex); +} + +const OUString& SwMailMergeConfigItem::GetFemaleGenderValue() const +{ + return m_pImpl->m_sFemaleGenderValue; +} + +void SwMailMergeConfigItem::SetFemaleGenderValue(const OUString& rValue) +{ + if( m_pImpl->m_sFemaleGenderValue != rValue ) + { + m_pImpl->m_sFemaleGenderValue = rValue; + m_pImpl->SetModified(); + } +} + +Sequence< OUString> SwMailMergeConfigItem::GetColumnAssignment( + const SwDBData& rDBData ) const +{ + Sequence< OUString> aRet; + auto aAssignIter = std::find_if(m_pImpl->m_aAddressDataAssignments.begin(), m_pImpl->m_aAddressDataAssignments.end(), + [&rDBData](const DBAddressDataAssignment& rAssignment) { return rAssignment.aDBData == rDBData; }); + if (aAssignIter != m_pImpl->m_aAddressDataAssignments.end()) + { + aRet = aAssignIter->aDBColumnAssignments; + } + return aRet; +} + +// returns the name that is assigned as e-mail column of the current data base +OUString SwMailMergeConfigItem::GetAssignedColumn(sal_uInt32 nColumn) const +{ + OUString sRet; + Sequence< OUString> aAssignment = GetColumnAssignment( m_pImpl->m_aDBData ); + if(aAssignment.getLength() > sal::static_int_cast< sal_Int32, sal_uInt32>(nColumn) && !aAssignment[nColumn].isEmpty()) + sRet = aAssignment[nColumn]; + else if(nColumn < m_pImpl->m_AddressHeaderSA.size()) + sRet = m_pImpl->m_AddressHeaderSA[nColumn].first; + return sRet; +} + +void SwMailMergeConfigItem::SetColumnAssignment( const SwDBData& rDBData, + const Sequence< OUString>& rList) +{ + auto aAssignIter = std::find_if(m_pImpl->m_aAddressDataAssignments.begin(), m_pImpl->m_aAddressDataAssignments.end(), + [&rDBData](const DBAddressDataAssignment& rAssignment) { return rAssignment.aDBData == rDBData; }); + if (aAssignIter != m_pImpl->m_aAddressDataAssignments.end()) + { + if(aAssignIter->aDBColumnAssignments != rList) + { + aAssignIter->aDBColumnAssignments = rList; + aAssignIter->bColumnAssignmentsChanged = true; + } + } + else + { + DBAddressDataAssignment aAssignment; + aAssignment.aDBData = rDBData; + aAssignment.aDBColumnAssignments = rList; + aAssignment.bColumnAssignmentsChanged = true; + m_pImpl->m_aAddressDataAssignments.push_back(aAssignment); + } + m_pImpl->SetModified(); +} + +bool SwMailMergeConfigItem::IsAddressFieldsAssigned() const +{ + bool bResult = true; + Reference< XResultSet> xResultSet = GetResultSet(); + uno::Reference< XColumnsSupplier > xColsSupp( xResultSet, UNO_QUERY ); + if(!xColsSupp.is()) + return false; + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + + const std::vector<std::pair<OUString, int>>& rHeaders = GetDefaultAddressHeaders(); + Sequence< OUString> aAssignment = + GetColumnAssignment( GetCurrentDBData() ); + const OUString* pAssignment = aAssignment.getConstArray(); + const Sequence< OUString> aBlocks = GetAddressBlocks(); + + if(aBlocks.getLength() <= m_pImpl->GetCurrentAddressBlockIndex()) + return false; + SwAddressIterator aIter(aBlocks[m_pImpl->GetCurrentAddressBlockIndex()]); + while(aIter.HasMore()) + { + SwMergeAddressItem aItem = aIter.Next(); + if(aItem.bIsColumn) + { + OUString sConvertedColumn = aItem.sText; + auto nSize = std::min(sal_uInt32(rHeaders.size()), sal_uInt32(aAssignment.getLength())); + for(sal_uInt32 nColumn = 0; nColumn < nSize; ++nColumn) + { + if (rHeaders[nColumn].first == aItem.sText && + !pAssignment[nColumn].isEmpty()) + { + sConvertedColumn = pAssignment[nColumn]; + break; + } + } + //find out if the column exists in the data base + if(!xCols->hasByName(sConvertedColumn)) + { + bResult = false; + break; + } + } + } + return bResult; +} + +bool SwMailMergeConfigItem::IsGreetingFieldsAssigned() const +{ + bool bResult = true; + + if(!IsIndividualGreeting(false)) + return true; + + Reference< XResultSet> xResultSet = GetResultSet(); + uno::Reference< XColumnsSupplier > xColsSupp( xResultSet, UNO_QUERY ); + if(!xColsSupp.is()) + return false; + const std::vector<std::pair<OUString, int>>& rHeaders = GetDefaultAddressHeaders(); + uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns(); + + Sequence< OUString> aAssignment = + GetColumnAssignment( GetCurrentDBData() ); + const OUString* pAssignment = aAssignment.getConstArray(); + + const Sequence< OUString> rFemaleEntries = GetGreetings(SwMailMergeConfigItem::FEMALE); + sal_Int32 nCurrentFemale = GetCurrentGreeting(SwMailMergeConfigItem::FEMALE); + const Sequence< OUString> rMaleEntries = GetGreetings(SwMailMergeConfigItem::MALE); + sal_Int32 nCurrentMale = GetCurrentGreeting(SwMailMergeConfigItem::MALE); + OUString sMale, sFemale; + if(rFemaleEntries.getLength() > nCurrentFemale) + sFemale = rFemaleEntries[nCurrentFemale]; + if(rMaleEntries.getLength() > nCurrentMale) + sMale = rMaleEntries[nCurrentMale]; + + OUString sAddress = sFemale + sMale; + SwAddressIterator aIter(sAddress); + while(aIter.HasMore()) + { + SwMergeAddressItem aItem = aIter.Next(); + if(aItem.bIsColumn) + { + OUString sConvertedColumn = aItem.sText; + auto nSize = std::min(sal_uInt32(rHeaders.size()), sal_uInt32(aAssignment.getLength())); + for(sal_uInt32 nColumn = 0; nColumn < nSize; ++nColumn) + { + if (rHeaders[nColumn].first == aItem.sText && + !pAssignment[nColumn].isEmpty()) + { + sConvertedColumn = pAssignment[nColumn]; + break; + } + } + //find out if the column exists in the data base + if(!xCols->hasByName(sConvertedColumn)) + { + bResult = false; + break; + } + } + } + return bResult; +} + +OUString const & SwMailMergeConfigItem::GetMailDisplayName() const +{ + return m_pImpl->m_sMailDisplayName; +} + +void SwMailMergeConfigItem::SetMailDisplayName(const OUString& rName) +{ + if(m_pImpl->m_sMailDisplayName != rName) + { + m_pImpl->m_sMailDisplayName = rName; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetMailAddress() const +{ + return m_pImpl->m_sMailAddress; +} + +void SwMailMergeConfigItem::SetMailAddress(const OUString& rAddress) +{ + if(m_pImpl->m_sMailAddress != rAddress ) + { + m_pImpl->m_sMailAddress = rAddress; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsMailReplyTo() const +{ + return m_pImpl->m_bIsMailReplyTo; +} + +void SwMailMergeConfigItem::SetMailReplyTo(bool bSet) +{ + if(m_pImpl->m_bIsMailReplyTo != bSet) + { + m_pImpl->m_bIsMailReplyTo = bSet; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetMailReplyTo() const +{ + return m_pImpl->m_sMailReplyTo; +} + +void SwMailMergeConfigItem::SetMailReplyTo(const OUString& rReplyTo) +{ + if(m_pImpl->m_sMailReplyTo != rReplyTo) + { + m_pImpl->m_sMailReplyTo = rReplyTo; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetMailServer() const +{ + return m_pImpl->m_sMailServer; +} + +void SwMailMergeConfigItem::SetMailServer(const OUString& rAddress) +{ + if(m_pImpl->m_sMailServer != rAddress) + { + m_pImpl->m_sMailServer = rAddress; + m_pImpl->SetModified(); + } +} + +sal_Int16 SwMailMergeConfigItem::GetMailPort() const +{ + // provide appropriate TCP port, based on SSL/STARTTLS status, if current port is one of the defaults + switch (m_pImpl->m_nMailPort) + { + case SECURE_PORT: + case DEFAULT_PORT: + return m_pImpl->m_bIsSecureConnection ? SECURE_PORT : DEFAULT_PORT; + default: + return m_pImpl->m_nMailPort; + } +} + +void SwMailMergeConfigItem::SetMailPort(sal_Int16 nSet) +{ + if(m_pImpl->m_nMailPort != nSet) + { + m_pImpl->m_nMailPort = nSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsSecureConnection() const +{ + return m_pImpl->m_bIsSecureConnection; +} + +void SwMailMergeConfigItem::SetSecureConnection(bool bSet) +{ + if(m_pImpl->m_bIsSecureConnection != bSet) + { + m_pImpl->m_bIsSecureConnection = bSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsAuthentication() const +{ + return m_pImpl->m_bIsAuthentication; +} + +void SwMailMergeConfigItem::SetAuthentication(bool bSet) +{ + if(m_pImpl->m_bIsAuthentication != bSet) + { + m_pImpl->m_bIsAuthentication = bSet; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetMailUserName() const +{ + return m_pImpl->m_sMailUserName; +} + +void SwMailMergeConfigItem::SetMailUserName(const OUString& rName) +{ + if(m_pImpl->m_sMailUserName != rName) + { + m_pImpl->m_sMailUserName = rName; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetMailPassword() const +{ + return m_pImpl->m_sMailPassword; +} + +void SwMailMergeConfigItem::SetMailPassword(const OUString& rPassword) +{ + if(m_pImpl->m_sMailPassword != rPassword) + { + m_pImpl->m_sMailPassword = rPassword; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsSMTPAfterPOP() const +{ + return m_pImpl->m_bIsSMPTAfterPOP; +} + +void SwMailMergeConfigItem::SetSMTPAfterPOP(bool bSet) +{ + if( m_pImpl->m_bIsSMPTAfterPOP != bSet) + { + m_pImpl->m_bIsSMPTAfterPOP = bSet; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetInServerName() const +{ + return m_pImpl->m_sInServerName; +} + +void SwMailMergeConfigItem::SetInServerName(const OUString& rServer) +{ + if(m_pImpl->m_sInServerName != rServer) + { + m_pImpl->m_sInServerName = rServer; + m_pImpl->SetModified(); + } +} + +sal_Int16 SwMailMergeConfigItem::GetInServerPort() const +{ + // provide appropriate TCP port as user toggles between POP/IMAP if current port is one of the defaults + switch (m_pImpl->m_nInServerPort) + { + case POP_SECURE_PORT: + case POP_PORT: + case IMAP_SECURE_PORT: + case IMAP_PORT: + if ( m_pImpl->m_bInServerPOP ) + return m_pImpl->m_bIsSecureConnection ? POP_SECURE_PORT : POP_PORT; + else + return m_pImpl->m_bIsSecureConnection ? IMAP_SECURE_PORT : IMAP_PORT; + default: + return m_pImpl->m_nInServerPort; + } +} + +void SwMailMergeConfigItem::SetInServerPort(sal_Int16 nSet) +{ + if( m_pImpl->m_nInServerPort != nSet) + { + m_pImpl->m_nInServerPort = nSet; + m_pImpl->SetModified(); + } +} + +bool SwMailMergeConfigItem::IsInServerPOP() const +{ + return m_pImpl->m_bInServerPOP; +} + +void SwMailMergeConfigItem::SetInServerPOP(bool bSet) +{ + if( m_pImpl->m_bInServerPOP != bSet) + { + m_pImpl->m_bInServerPOP = bSet; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetInServerUserName() const +{ + return m_pImpl->m_sInServerUserName; +} + +void SwMailMergeConfigItem::SetInServerUserName(const OUString& rName) +{ + if( m_pImpl->m_sInServerUserName != rName) + { + m_pImpl->m_sInServerUserName = rName; + m_pImpl->SetModified(); + } +} + +OUString const & SwMailMergeConfigItem::GetInServerPassword() const +{ + return m_pImpl->m_sInServerPassword; +} + +void SwMailMergeConfigItem::SetInServerPassword(const OUString& rPassword) +{ + if(m_pImpl->m_sInServerPassword != rPassword) + { + m_pImpl->m_sInServerPassword = rPassword; + m_pImpl->SetModified(); + } +} + +void SwMailMergeConfigItem::DocumentReloaded() +{ + m_bGreetingInserted = false; + m_bAddressInserted = false; +} + +bool SwMailMergeConfigItem::IsMailAvailable() const +{ + return m_pImpl->m_bIsEMailSupported; +} + +void SwMailMergeConfigItem::AddMergedDocument(SwDocMergeInfo const & rInfo) +{ + m_pImpl->m_aMergeInfos.push_back(rInfo); +} + +SwDocMergeInfo& SwMailMergeConfigItem::GetDocumentMergeInfo(sal_uInt32 nDocument) +{ + assert(nDocument < m_pImpl->m_aMergeInfos.size()); + return m_pImpl->m_aMergeInfos[nDocument]; +} + + +sal_uInt32 SwMailMergeConfigItem::GetMergedDocumentCount() +{ + if(m_pTargetView) + return m_pImpl->m_aMergeInfos.size(); + else + { + sal_Int32 nRestore = GetResultSetPosition(); + MoveResultSet(-1); + sal_Int32 nRet = GetResultSetPosition(); + MoveResultSet( nRestore ); + nRet -= m_aExcludedRecords.size(); + return nRet >= 0 ? nRet : 0; + } +} + +static SwView* lcl_ExistsView(SwView* pView) +{ + SfxViewShell* pViewShell = SfxViewShell::GetFirst( false, checkSfxViewShell<SwView> ); + while(pViewShell) + { + if(pViewShell == pView) + return pView; + + pViewShell = SfxViewShell::GetNext( *pViewShell, false, checkSfxViewShell<SwView> ); + } + return nullptr; +} + +SwView* SwMailMergeConfigItem::GetTargetView() +{ + //make sure that the pointer is really valid - the document may have been closed manually + if(m_pTargetView) + { + m_pTargetView = lcl_ExistsView(m_pTargetView); + } + return m_pTargetView; +} + +void SwMailMergeConfigItem::SetTargetView(SwView* pView) +{ + m_pTargetView = pView; + //reset the document merge counter + if(!m_pTargetView) + { + m_pImpl->m_aMergeInfos.clear(); + } +} + +SwView* SwMailMergeConfigItem::GetSourceView() +{ + m_pSourceView = lcl_ExistsView(m_pSourceView); + return m_pSourceView; +} + +namespace { + +//This implements XSelectionChangeListener and XDispatch because the +//broadcaster uses this combo to determine if to send the database-changed +//update. Its probably that listening to statusChanged at some other level is +//equivalent to this. See the other call to SwXDispatch::GetDBChangeURL for +//the broadcaster of the event. +class DBChangeListener : public cppu::WeakImplHelper<css::view::XSelectionChangeListener, css::frame::XDispatch> +{ + SwMailMergeConfigItem& m_rParent; +public: + explicit DBChangeListener(SwMailMergeConfigItem& rParent) + : m_rParent(rParent) + { + } + + virtual void SAL_CALL selectionChanged(const EventObject& /*rEvent*/) override + { + } + + virtual void SAL_CALL disposing(const EventObject&) override + { + m_rParent.stopDBChangeListening(); + } + + virtual void SAL_CALL dispatch(const css::util::URL& rURL, const css::uno::Sequence< css::beans::PropertyValue >& /*rArgs*/) override + { + if (rURL.Complete.equalsAscii(SwXDispatch::GetDBChangeURL())) + m_rParent.updateCurrentDBDataFromDocument(); + } + + virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >&, const css::util::URL&) override + { + } + + virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >&, const css::util::URL&) override + { + } +}; + +} + +void SwMailMergeConfigItem::SetSourceView(SwView* pView) +{ + if (m_xDBChangedListener.is()) + { + uno::Reference<view::XSelectionSupplier> xSupplier = m_pSourceView->GetUNOObject(); + xSupplier->removeSelectionChangeListener(m_xDBChangedListener); + m_xDBChangedListener.clear(); + } + + m_pSourceView = pView; + + if (!m_pSourceView) + return; + + std::vector<OUString> aDBNameList; + std::vector<OUString> aAllDBNames; + m_pSourceView->GetWrtShell().GetAllUsedDB( aDBNameList, &aAllDBNames ); + if(!aDBNameList.empty()) + { + // if fields are available there is usually no need of an addressblock and greeting + if(!m_pImpl->m_bUserSettingWereOverwritten) + { + if( m_pImpl->m_bIsAddressBlock + || m_pImpl->m_bIsGreetingLineInMail + || m_pImpl->m_bIsGreetingLine ) + { + //store user settings + m_pImpl->m_bUserSettingWereOverwritten = true; + m_pImpl->m_bIsAddressBlock_LastUserSetting = m_pImpl->m_bIsAddressBlock; + m_pImpl->m_bIsGreetingLineInMail_LastUserSetting = m_pImpl->m_bIsGreetingLineInMail; + m_pImpl->m_bIsGreetingLine_LastUserSetting = m_pImpl->m_bIsGreetingLine; + + //set all to false + m_pImpl->m_bIsAddressBlock = false; + m_pImpl->m_bIsGreetingLineInMail = false; + m_pImpl->m_bIsGreetingLine = false; + + m_pImpl->SetModified(); + } + } + } + else if( m_pImpl->m_bUserSettingWereOverwritten ) + { + //restore last user settings: + m_pImpl->m_bIsAddressBlock = m_pImpl->m_bIsAddressBlock_LastUserSetting; + m_pImpl->m_bIsGreetingLineInMail = m_pImpl->m_bIsGreetingLineInMail_LastUserSetting; + m_pImpl->m_bIsGreetingLine = m_pImpl->m_bIsGreetingLine_LastUserSetting; + + m_pImpl->m_bUserSettingWereOverwritten = false; + } + + if (!m_xDBChangedListener.is()) + { + m_xDBChangedListener.set(new DBChangeListener(*this)); + } + + uno::Reference<view::XSelectionSupplier> xSupplier = m_pSourceView->GetUNOObject(); + xSupplier->addSelectionChangeListener(m_xDBChangedListener); +} + +void SwMailMergeConfigItem::SetCurrentAddressBlockIndex( sal_Int32 nSet ) +{ + m_pImpl->SetCurrentAddressBlockIndex( nSet ); +} + +sal_Int32 SwMailMergeConfigItem::GetCurrentAddressBlockIndex() const +{ + return m_pImpl->GetCurrentAddressBlockIndex(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |