summaryrefslogtreecommitdiffstats
path: root/sw/source/uibase/dbui
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/uibase/dbui')
-rw-r--r--sw/source/uibase/dbui/README48
-rw-r--r--sw/source/uibase/dbui/dbmgr.cxx3283
-rw-r--r--sw/source/uibase/dbui/dbtree.cxx454
-rw-r--r--sw/source/uibase/dbui/dbui.cxx87
-rw-r--r--sw/source/uibase/dbui/maildispatcher.cxx248
-rw-r--r--sw/source/uibase/dbui/mailmergehelper.cxx834
-rw-r--r--sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx408
-rw-r--r--sw/source/uibase/dbui/mmconfigitem.cxx1708
8 files changed, 7070 insertions, 0 deletions
diff --git a/sw/source/uibase/dbui/README b/sw/source/uibase/dbui/README
new file mode 100644
index 000000000..ba9745c77
--- /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 000000000..c5aa73a0a
--- /dev/null
+++ b/sw/source/uibase/dbui/dbmgr.cxx
@@ -0,0 +1,3283 @@
+/* -*- 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 <tools/diagnose_ex.h>
+
+#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, 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;
+ SwDoc *pWorkDoc = nullptr;
+ 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::unique_ptr<SwWait> pWait;
+
+ {
+ sal_uLong i = 0;
+ do {
+
+ ImportDBEntry(pSh);
+ if( 10 == ++i )
+ pWait.reset(new SwWait( *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::TempFile 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;
+
+ const OUString sExt( ".odt" );
+ OUString basename = OUString::createFromAscii( name );
+ if (no > 0)
+ basename += OUString::number(no) + "-";
+ // aTempFile is not deleted, but that seems to be intentional
+ utl::TempFile aTempFile( basename, true, &sExt, &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->GetError());
+ 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( pDstMed->GetItemSet() )
+ {
+ 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->GetError());
+ if( bAnyError )
+ {
+ // error message ??
+ ErrorHandler::HandleError( xObjectShell->GetError() );
+ }
+ 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, 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() );
+ 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 ));
+ sBody.append("\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";
+
+ 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;
+ SwDoc* pTargetDoc = nullptr;
+ SfxObjectShellRef xTargetDocShell;
+
+ std::unique_ptr< utl::TempFile > 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();
+ 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;
+ SwDoc* pWorkDoc = nullptr;
+ 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::TempFile(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 )
+ {
+ 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(
+ appendedDocStart, "", IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
+ ::sw::mark::InsertMode::New);
+ aMergeInfo.nDBRow = nStartRow;
+ rMergeDescriptor.pMailMergeConfigItem->AddMergedDocument( aMergeInfo );
+ }
+ }
+ else
+ {
+ assert( bNeedsTempFiles );
+ assert( pWorkShell->IsExpFieldsLocked() );
+
+ // 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 )
+ {
+ pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
+ 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 )
+ {
+ pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
+ 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_uLong SwDBManager::GetColumnFormat( const OUString& rDBName,
+ const OUString& rTableName,
+ const OUString& rColNm,
+ SvNumberFormatter* pNFormatr,
+ LanguageType nLanguage )
+{
+ sal_uLong 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_uLong 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_uLong 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 const sOutputExt = ".odb";
+ OUString sHomePath(SvtPathOptions().GetWorkPath());
+ utl::TempFile aTempFile(sNewName, true, &sOutputExt, pDestDir ? pDestDir : &sHomePath);
+ const OUString& sTmpName = aTempFile.GetURL();
+ 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));
+ OUString sFilterSXB(SwResId(STR_FILTER_SXB));
+ OUString sFilterSXC(SwResId(STR_FILTER_SXC));
+ OUString sFilterSXW(SwResId(STR_FILTER_SXW));
+ OUString sFilterDBF(SwResId(STR_FILTER_DBF));
+ OUString sFilterXLS(SwResId(STR_FILTER_XLS));
+ OUString sFilterDOC(SwResId(STR_FILTER_DOC));
+ OUString sFilterTXT(SwResId(STR_FILTER_TXT));
+ OUString sFilterCSV(SwResId(STR_FILTER_CSV));
+#ifdef _WIN32
+ OUString sFilterMDB(SwResId(STR_FILTER_MDB));
+ OUString sFilterACCDB(SwResId(STR_FILTER_ACCDB));
+#endif
+ xFP->appendFilter( sFilterAll, "*" );
+ xFP->appendFilter( sFilterAllData, "*.ods;*.sxc;*.odt;*.sxw;*.dbf;*.xls;*.xlsx;*.doc;*.docx;*.txt;*.csv");
+
+ xFP->appendFilter( sFilterSXB, "*.odb" );
+ xFP->appendFilter( sFilterSXC, "*.ods;*.sxc" );
+ xFP->appendFilter( sFilterSXW, "*.odt;*.sxw" );
+ xFP->appendFilter( sFilterDBF, "*.dbf" );
+ xFP->appendFilter( sFilterXLS, "*.xls;*.xlsx" );
+ xFP->appendFilter( sFilterDOC, "*.doc;*.docx" );
+ xFP->appendFilter( sFilterTXT, "*.txt" );
+ xFP->appendFilter( sFilterCSV, "*.csv" );
+#ifdef _WIN32
+ xFP->appendFilter(sFilterMDB, "*.mdb;*.mde");
+ xFP->appendFilter(sFilterACCDB, "*.accdb;*.accde");
+#endif
+
+ xFP->setCurrentFilter( sFilterAll ) ;
+ OUString sFind;
+ if( ERRCODE_NONE == aDlgHelper.Execute() )
+ {
+ uno::Reference< beans::XPropertySet > aSettings;
+ const INetURLObject aURL( xFP->getSelectedFiles().getConstArray()[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 000000000..432a0a28e
--- /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 000000000..a2fb02345
--- /dev/null
+++ b/sw/source/uibase/dbui/dbui.cxx
@@ -0,0 +1,87 @@
+/* -*- 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_xDocName(m_xBuilder->weld_label("docname"))
+ , 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 000000000..4bde20e97
--- /dev/null
+++ b/sw/source/uibase/dbui/maildispatcher.cxx
@@ -0,0 +1,248 @@
+/* -*- 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 <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> const & message) :
+ message_(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> const & xMailDispatcher,
+ uno::Reference<mail::XMailMessage> const & message,
+ const OUString& error_message) :
+ m_mail_dispatcher(xMailDispatcher),
+ m_message(message),
+ m_error_message(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> const & mailserver) :
+ m_xMailserver( 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 000000000..5eadf3dc9
--- /dev/null
+++ b/sw/source/uibase/dbui/mailmergehelper.cxx
@@ -0,0 +1,834 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weldutils.hxx>
+#include <tools/diagnose_ex.h>
+#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( const OUString& rMailAddress )
+{
+ const sal_Int32 nPosAt = rMailAddress.indexOf('@');
+ if (nPosAt<0 || rMailAddress.lastIndexOf('@')!=nPosAt)
+ return false;
+ const sal_Int32 nPosDot = rMailAddress.indexOf('.', nPosAt);
+ return !(nPosDot<0 || nPosDot-nPosAt<2 || rMailAddress.getLength()-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(
+ const OUString& rMailServer, sal_Int16 nPort,
+ const OUString& rConnectionType) :
+ m_sMailServer(rMailServer),
+ m_nPort(nPort),
+ m_sConnectionType(rConnectionType)
+{
+}
+
+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(const OUString& rBody, const OUString& rMimeType) :
+ cppu::WeakComponentImplHelper< datatransfer::XTransferable, beans::XPropertySet >(m_aMutex),
+ m_aMimeType( rMimeType ),
+ m_sBody( rBody ),
+ m_bIsBody( true )
+{
+}
+
+SwMailTransferable::SwMailTransferable(const OUString& rURL,
+ const OUString& rName, const OUString& rMimeType) :
+ cppu::WeakComponentImplHelper< datatransfer::XTransferable, beans::XPropertySet >(m_aMutex),
+ m_aMimeType( rMimeType ),
+ m_aURL(rURL),
+ m_aName( rName ),
+ 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() :
+ cppu::WeakComponentImplHelper< mail::XMailMessage>(m_aMutex)
+{
+}
+
+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 000000000..db3d38f1e
--- /dev/null
+++ b/sw/source/uibase/dbui/mailmergetoolbarcontrols.cxx
@@ -0,0 +1,408 @@
+/* -*- 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.
+class MMCurrentEntryController : public svt::ToolboxController, public lang::XServiceInfo
+{
+ VclPtr<CurrentEdit> m_xCurrentEdit;
+
+ DECL_LINK(CurrentEditUpdatedHdl, weld::Entry&, bool);
+
+public:
+ explicit MMCurrentEntryController(const uno::Reference<uno::XComponentContext>& rContext)
+ : svt::ToolboxController(rContext, uno::Reference<frame::XFrame>(), ".uno:MailMergeCurrentEntry")
+ , m_xCurrentEdit(nullptr)
+ {
+ }
+
+ // XInterface
+ virtual uno::Any SAL_CALL queryInterface(const uno::Type& aType) override
+ {
+ uno::Any a(ToolboxController::queryInterface(aType));
+ if (a.hasValue())
+ return a;
+
+ return ::cppu::queryInterface(aType, static_cast<lang::XServiceInfo*>(this));
+ }
+
+ void SAL_CALL acquire() noexcept override
+ {
+ ToolboxController::acquire();
+ }
+
+ void SAL_CALL release() noexcept override
+ {
+ ToolboxController::release();
+ }
+
+ // 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.
+class MMExcludeEntryController : public svt::ToolboxController, public lang::XServiceInfo
+{
+ VclPtr<ExcludeCheckBox> m_xExcludeCheckbox;
+
+ DECL_STATIC_LINK(MMExcludeEntryController, ExcludeHdl, weld::Toggleable&, void);
+
+public:
+ explicit MMExcludeEntryController(const uno::Reference<uno::XComponentContext>& rContext)
+ : svt::ToolboxController(rContext, uno::Reference<frame::XFrame>(), ".uno:MailMergeExcludeEntry")
+ , m_xExcludeCheckbox(nullptr)
+ {
+ }
+
+ // XInterface
+ virtual uno::Any SAL_CALL queryInterface(const uno::Type& aType) override
+ {
+ uno::Any a(ToolboxController::queryInterface(aType));
+ if (a.hasValue())
+ return a;
+
+ return ::cppu::queryInterface(aType, static_cast<lang::XServiceInfo*>(this));
+ }
+
+ void SAL_CALL acquire() noexcept override
+ {
+ ToolboxController::acquire();
+ }
+
+ void SAL_CALL release() noexcept override
+ {
+ ToolboxController::release();
+ }
+
+ // 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 000000000..86c1a73f3
--- /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 <tools/diagnose_ex.h>
+#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 OUStringLiteral cAddressDataAssignments = u"AddressDataAssignments";
+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 (size_t i = 0; i < SAL_N_ELEMENTS(SA_ADDRESS_HEADER); ++i)
+ {
+ m_AddressHeaderSA.emplace_back(SwResId(SA_ADDRESS_HEADER[i].first), SA_ADDRESS_HEADER[i].second);
+ }
+
+ 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: */