1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
24 #include <osl/diagnose.h>
25 #include <swtypes.hxx>
26 #include "createaddresslistdialog.hxx"
27 #include "customizeaddresslistdialog.hxx"
28 #include <mmconfigitem.hxx>
30 #include <vcl/svapp.hxx>
31 #include <sfx2/filedlghelper.hxx>
32 #include <sfx2/docfile.hxx>
33 #include <rtl/textenc.h>
34 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
35 #include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
36 #include <tools/urlobj.hxx>
37 #include <o3tl/string_view.hxx>
38 #include <strings.hrc>
41 using namespace ::com::sun::star
;
42 using namespace ::com::sun::star::ui::dialogs
;
46 struct SwAddressFragment
48 std::unique_ptr
<weld::Builder
> m_xBuilder
;
49 std::unique_ptr
<weld::Label
> m_xLabel
;
50 std::unique_ptr
<weld::Entry
> m_xEntry
;
51 weld::Container
* m_pGrid
;
53 SwAddressFragment(weld::Container
* pGrid
, int nLine
)
54 : m_xBuilder(Application::CreateBuilder(pGrid
, "modules/swriter/ui/addressfragment.ui"))
55 , m_xLabel(m_xBuilder
->weld_label("label"))
56 , m_xEntry(m_xBuilder
->weld_entry("entry"))
59 m_xLabel
->set_grid_left_attach(0);
60 m_xLabel
->set_grid_top_attach(nLine
);
62 m_xEntry
->set_grid_left_attach(1);
63 m_xEntry
->set_grid_top_attach(nLine
);
68 m_pGrid
->move(m_xEntry
.get(), nullptr);
69 m_pGrid
->move(m_xLabel
.get(), nullptr);
75 class SwAddressControl_Impl
77 std::map
<weld::Entry
*, sal_Int32
> m_aEditLines
;
80 sal_uInt32 m_nCurrentDataSet
;
84 std::unique_ptr
<weld::ScrolledWindow
> m_xScrollBar
;
85 std::unique_ptr
<weld::Container
> m_xWindow
;
86 std::vector
<std::unique_ptr
<SwAddressFragment
>> m_aLines
;
88 DECL_LINK(GotFocusHdl_Impl
, weld::Widget
&, void);
89 DECL_LINK(EditModifyHdl_Impl
, weld::Entry
&, void);
91 void MakeVisible(const tools::Rectangle
& aRect
);
94 SwAddressControl_Impl(weld::Builder
& rBuilder
);
96 void SetData(SwCSVData
& rDBData
);
98 void SetCurrentDataSet(sal_uInt32 nSet
);
99 void CurrentDataSetInvalidated() { m_nCurrentDataSet
= std::numeric_limits
<sal_uInt32
>::max(); }
100 sal_uInt32
GetCurrentDataSet() const { return m_nCurrentDataSet
; }
101 void SetCursorTo(std::size_t nElement
);
104 SwAddressControl_Impl::SwAddressControl_Impl(weld::Builder
& rBuilder
)
106 , m_nCurrentDataSet(0)
108 , m_xScrollBar(rBuilder
.weld_scrolled_window("scrollwin"))
109 , m_xWindow(rBuilder
.weld_container("CONTAINER"))
113 void SwAddressControl_Impl::SetData(SwCSVData
& rDBData
)
116 //when the address data is updated then remove the controls and build again
117 if (!m_aLines
.empty())
123 Link
<weld::Widget
&,void> aFocusLink
= LINK(this, SwAddressControl_Impl
, GotFocusHdl_Impl
);
124 Link
<weld::Entry
&,void> aEditModifyLink
= LINK(this, SwAddressControl_Impl
, EditModifyHdl_Impl
);
125 sal_Int32 nLines
= 0;
126 for (const auto& rHeader
: m_pData
->aDBColumnHeaders
)
128 m_aLines
.emplace_back(new SwAddressFragment(m_xWindow
.get(), nLines
));
130 // when we have one line, measure it to get the line height to use as
131 // the basis for overall size request
134 auto nLineHeight
= m_xWindow
->get_preferred_size().Height();
135 m_xScrollBar
->set_size_request(m_xScrollBar
->get_approximate_digit_width() * 65,
139 weld::Label
* pNewFT
= m_aLines
.back()->m_xLabel
.get();
140 weld::Entry
* pNewED
= m_aLines
.back()->m_xEntry
.get();
141 //set nLines a position identifier - used in the ModifyHdl
142 m_aEditLines
[pNewED
] = nLines
;
143 pNewED
->connect_focus_in(aFocusLink
);
144 pNewED
->connect_changed(aEditModifyLink
);
146 pNewFT
->set_label(rHeader
);
152 void SwAddressControl_Impl::SetCurrentDataSet(sal_uInt32 nSet
)
154 if(!(m_bNoDataSet
|| m_nCurrentDataSet
!= nSet
))
157 m_bNoDataSet
= false;
158 m_nCurrentDataSet
= nSet
;
159 OSL_ENSURE(m_pData
->aDBData
.size() > m_nCurrentDataSet
, "wrong data set index");
160 if(m_pData
->aDBData
.size() > m_nCurrentDataSet
)
162 sal_uInt32 nIndex
= 0;
163 for(auto& rLine
: m_aLines
)
165 OSL_ENSURE(nIndex
< m_pData
->aDBData
[m_nCurrentDataSet
].size(),
166 "number of columns doesn't match number of Edits");
167 rLine
->m_xEntry
->set_text(m_pData
->aDBData
[m_nCurrentDataSet
][nIndex
]);
173 IMPL_LINK(SwAddressControl_Impl
, GotFocusHdl_Impl
, weld::Widget
&, rEdit
, void)
175 int x
, y
, width
, height
;
176 rEdit
.get_extents_relative_to(*m_xWindow
, x
, y
, width
, height
);
177 // the container has a border of 3 in the .ui
178 tools::Rectangle
aRect(Point(x
- 3, y
- 3), Size(width
+ 6, height
+ 6));
182 void SwAddressControl_Impl::MakeVisible(const tools::Rectangle
& rRect
)
184 //determine range of visible positions
185 auto nMinVisiblePos
= m_xScrollBar
->vadjustment_get_value();
186 auto nMaxVisiblePos
= nMinVisiblePos
+ m_xScrollBar
->vadjustment_get_page_size();
187 if (rRect
.Top() < nMinVisiblePos
|| rRect
.Bottom() > nMaxVisiblePos
)
188 m_xScrollBar
->vadjustment_set_value(rRect
.Top());
191 // copy data changes into database
192 IMPL_LINK(SwAddressControl_Impl
, EditModifyHdl_Impl
, weld::Entry
&, rEdit
, void)
194 //get the data element number of the current set
195 sal_Int32 nIndex
= m_aEditLines
[&rEdit
];
196 //get the index of the set
197 OSL_ENSURE(m_pData
->aDBData
.size() > m_nCurrentDataSet
, "wrong data set index" );
198 if (m_pData
->aDBData
.size() > m_nCurrentDataSet
)
200 m_pData
->aDBData
[m_nCurrentDataSet
][nIndex
] = rEdit
.get_text();
204 void SwAddressControl_Impl::SetCursorTo(std::size_t nElement
)
206 if (nElement
< m_aLines
.size())
208 weld::Entry
* pEdit
= m_aLines
[nElement
]->m_xEntry
.get();
214 SwCreateAddressListDialog::SwCreateAddressListDialog(
215 weld::Window
* pParent
, OUString aURL
, SwMailMergeConfigItem
const & rConfig
)
216 : SfxDialogController(pParent
, "modules/swriter/ui/createaddresslist.ui", "CreateAddressList")
217 , m_sAddressListFilterName(SwResId(ST_FILTERNAME
))
218 , m_sURL(std::move(aURL
))
219 , m_pCSVData(new SwCSVData
)
220 , m_xAddressControl(new SwAddressControl_Impl(*m_xBuilder
))
221 , m_xNewPB(m_xBuilder
->weld_button("NEW"))
222 , m_xDeletePB(m_xBuilder
->weld_button("DELETE"))
223 , m_xFindPB(m_xBuilder
->weld_button("FIND"))
224 , m_xCustomizePB(m_xBuilder
->weld_button("CUSTOMIZE"))
225 , m_xStartPB(m_xBuilder
->weld_button("START"))
226 , m_xPrevPB(m_xBuilder
->weld_button("PREV"))
227 , m_xSetNoED(m_xBuilder
->weld_entry("SETNOED"))
228 , m_xSetNoNF(m_xBuilder
->weld_spin_button("SETNOSB"))
229 , m_xNextPB(m_xBuilder
->weld_button("NEXT"))
230 , m_xEndPB(m_xBuilder
->weld_button("END"))
231 , m_xOK(m_xBuilder
->weld_button("ok"))
233 m_xSetNoNF
->set_min(1);
235 m_xNewPB
->connect_clicked(LINK(this, SwCreateAddressListDialog
, NewHdl_Impl
));
236 m_xDeletePB
->connect_clicked(LINK(this, SwCreateAddressListDialog
, DeleteHdl_Impl
));
237 m_xFindPB
->connect_clicked(LINK(this, SwCreateAddressListDialog
, FindHdl_Impl
));
238 m_xCustomizePB
->connect_clicked(LINK(this, SwCreateAddressListDialog
, CustomizeHdl_Impl
));
239 m_xOK
->connect_clicked(LINK(this, SwCreateAddressListDialog
, OkHdl_Impl
));
241 Link
<weld::Button
&,void> aLk
= LINK(this, SwCreateAddressListDialog
, DBCursorHdl_Impl
);
242 m_xStartPB
->connect_clicked(aLk
);
243 m_xPrevPB
->connect_clicked(aLk
);
244 m_xSetNoED
->connect_changed(LINK(this, SwCreateAddressListDialog
, DBNumCursorHdl_Impl
));
245 m_xSetNoED
->connect_focus_out(LINK(this, SwCreateAddressListDialog
, RefreshNum_Impl
));
246 m_xNextPB
->connect_clicked(aLk
);
247 m_xEndPB
->connect_clicked(aLk
);
249 if (!m_sURL
.isEmpty())
251 //file exists, has to be loaded here
252 SfxMedium
aMedium( m_sURL
, StreamMode::READ
);
253 SvStream
* pStream
= aMedium
.GetInStream();
256 pStream
->SetLineDelimiter( LINEEND_LF
);
257 pStream
->SetStreamCharSet(RTL_TEXTENCODING_UTF8
);
260 bool bRead
= pStream
->ReadByteStringLine( sLine
, RTL_TEXTENCODING_UTF8
);
262 if(bRead
&& !sLine
.isEmpty())
264 sal_Int32 nIndex
= 0;
267 const std::u16string_view sHeader
= o3tl::getToken(sLine
, 0, '\t', nIndex
);
268 OSL_ENSURE(sHeader
.size() > 2 &&
269 o3tl::starts_with(sHeader
, u
"\"") && o3tl::ends_with(sHeader
, u
"\""),
270 "Wrong format of header");
271 if(sHeader
.size() > 2)
273 m_pCSVData
->aDBColumnHeaders
.push_back( OUString(sHeader
.substr(1, sHeader
.size() -2)));
278 while(pStream
->ReadByteStringLine( sLine
, RTL_TEXTENCODING_UTF8
))
280 std::vector
<OUString
> aNewData
;
282 sal_Int32 nIndex
= { sLine
.isEmpty() ? -1 : 0 };
285 const OUString sData
= sLine
.getToken( 0, '\t', nIndex
);
286 OSL_ENSURE( sData
.startsWith("\"") && sData
.endsWith("\""),
287 "Wrong format of line");
288 if(sData
.getLength() >= 2)
289 aNewData
.push_back(sData
.copy(1, sData
.getLength() - 2));
291 aNewData
.push_back(sData
);
293 m_pCSVData
->aDBData
.push_back( aNewData
);
299 //database has to be created
300 const std::vector
<std::pair
<OUString
, int>>& rAddressHeader
= rConfig
.GetDefaultAddressHeaders();
301 const sal_uInt32 nCount
= rAddressHeader
.size();
302 for(sal_uInt32 nHeader
= 0; nHeader
< nCount
; ++nHeader
)
303 m_pCSVData
->aDBColumnHeaders
.push_back(rAddressHeader
[nHeader
].first
);
304 std::vector
<OUString
> aNewData
;
305 aNewData
.insert(aNewData
.begin(), nCount
, OUString());
306 m_pCSVData
->aDBData
.push_back(aNewData
);
308 //now fill the address control
309 m_xAddressControl
->SetData(*m_pCSVData
);
310 m_xAddressControl
->SetCurrentDataSet(0);
311 m_xSetNoNF
->set_max(m_pCSVData
->aDBData
.size());
313 m_xSetNoNF
->set_value(1);
314 RefreshNum_Impl(*m_xSetNoED
);
319 SwCreateAddressListDialog::~SwCreateAddressListDialog()
323 IMPL_LINK_NOARG(SwCreateAddressListDialog
, NewHdl_Impl
, weld::Button
&, void)
325 sal_uInt32 nCurrent
= m_xAddressControl
->GetCurrentDataSet();
326 std::vector
<OUString
> aNewData
;
327 aNewData
.insert(aNewData
.begin(), m_pCSVData
->aDBColumnHeaders
.size(), OUString());
328 m_pCSVData
->aDBData
.insert(m_pCSVData
->aDBData
.begin() + ++nCurrent
, aNewData
);
329 m_xSetNoNF
->set_max(m_pCSVData
->aDBData
.size());
330 //the NumericField start at 1
331 m_xSetNoNF
->set_value(nCurrent
+ 1);
332 RefreshNum_Impl(*m_xSetNoED
);
333 //the address control starts at 0
334 m_xAddressControl
->SetCurrentDataSet(nCurrent
);
338 IMPL_LINK_NOARG(SwCreateAddressListDialog
, DeleteHdl_Impl
, weld::Button
&, void)
340 sal_uInt32 nCurrent
= m_xAddressControl
->GetCurrentDataSet();
341 if (m_pCSVData
->aDBData
.size() > 1)
343 m_pCSVData
->aDBData
.erase(m_pCSVData
->aDBData
.begin() + nCurrent
);
349 // if only one set is available then clear the data
350 m_pCSVData
->aDBData
[0].assign(m_pCSVData
->aDBData
[0].size(), OUString());
351 m_xDeletePB
->set_sensitive(false);
353 m_xAddressControl
->CurrentDataSetInvalidated();
354 m_xAddressControl
->SetCurrentDataSet(nCurrent
);
355 m_xSetNoNF
->set_max(m_pCSVData
->aDBData
.size());
359 IMPL_LINK_NOARG(SwCreateAddressListDialog
, FindHdl_Impl
, weld::Button
&, void)
363 m_xFindDlg
.reset(new SwFindEntryDialog(this));
364 weld::ComboBox
& rColumnBox
= m_xFindDlg
->GetFieldsListBox();
365 for(const auto& rHeader
: m_pCSVData
->aDBColumnHeaders
)
366 rColumnBox
.append_text(rHeader
);
367 rColumnBox
.set_active(0);
371 m_xFindDlg
->set_visible(!m_xFindDlg
->get_visible());
374 IMPL_LINK_NOARG(SwCreateAddressListDialog
, CustomizeHdl_Impl
, weld::Button
&, void)
376 SwCustomizeAddressListDialog
aDlg(m_xDialog
.get(), *m_pCSVData
);
377 if (aDlg
.run() == RET_OK
)
379 m_pCSVData
= aDlg
.ReleaseNewData();
380 m_xAddressControl
->SetData(*m_pCSVData
);
381 m_xAddressControl
->SetCurrentDataSet(m_xAddressControl
->GetCurrentDataSet());
387 weld::ComboBox
& rColumnBox
= m_xFindDlg
->GetFieldsListBox();
389 for(const auto& rHeader
: m_pCSVData
->aDBColumnHeaders
)
390 rColumnBox
.append_text(rHeader
);
397 void lcl_WriteValues(const std::vector
<OUString
> *pFields
, SvStream
* pStream
)
399 OUStringBuffer sLine
;
400 const std::vector
< OUString
>::const_iterator aBegin
= pFields
->begin();
401 const std::vector
< OUString
>::const_iterator aEnd
= pFields
->end();
402 for(std::vector
< OUString
>::const_iterator aIter
= aBegin
; aIter
!= aEnd
; ++aIter
)
406 sLine
.append("\"" + *aIter
+ "\"");
410 sLine
.append("\t\"" + *aIter
+ "\"");
413 pStream
->WriteByteStringLine( sLine
, RTL_TEXTENCODING_UTF8
);
418 IMPL_LINK_NOARG(SwCreateAddressListDialog
, OkHdl_Impl
, weld::Button
&, void)
422 sfx2::FileDialogHelper
aDlgHelper(TemplateDescription::FILESAVE_SIMPLE
,
423 FileDialogFlags::NONE
, m_xDialog
.get());
424 aDlgHelper
.SetContext(sfx2::FileDialogHelper::WriterCreateAddressList
);
425 uno::Reference
< XFilePicker3
> xFP
= aDlgHelper
.GetFilePicker();
426 xFP
->appendFilter( m_sAddressListFilterName
, "*.csv" );
427 xFP
->setCurrentFilter( m_sAddressListFilterName
) ;
429 if( ERRCODE_NONE
== aDlgHelper
.Execute() )
431 m_sURL
= xFP
->getSelectedFiles().getConstArray()[0];
432 INetURLObject
aResult( m_sURL
);
433 aResult
.setExtension(u
"csv");
434 m_sURL
= aResult
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
440 SfxMedium
aMedium( m_sURL
, StreamMode::READWRITE
|StreamMode::TRUNC
);
441 SvStream
* pStream
= aMedium
.GetOutStream();
442 pStream
->SetLineDelimiter( LINEEND_LF
);
443 pStream
->SetStreamCharSet(RTL_TEXTENCODING_UTF8
);
445 lcl_WriteValues(&(m_pCSVData
->aDBColumnHeaders
), pStream
);
447 for(const auto& rData
: m_pCSVData
->aDBData
)
449 lcl_WriteValues(&rData
, pStream
);
452 m_xDialog
->response(RET_OK
);
455 IMPL_LINK(SwCreateAddressListDialog
, DBCursorHdl_Impl
, weld::Button
&, rButton
, void)
457 int nValue
= m_xSetNoNF
->get_value();
459 if (&rButton
== m_xStartPB
.get())
461 else if (&rButton
== m_xPrevPB
.get())
466 else if (&rButton
== m_xNextPB
.get())
468 if (nValue
< m_xSetNoNF
->get_max())
472 nValue
= m_xSetNoNF
->get_max();
473 if (nValue
!= m_xSetNoNF
->get_value())
475 m_xSetNoNF
->set_value(nValue
);
476 RefreshNum_Impl(*m_xSetNoED
);
481 IMPL_LINK_NOARG(SwCreateAddressListDialog
, DBNumCursorHdl_Impl
, weld::Entry
&, void)
483 m_xSetNoNF
->set_text(m_xSetNoED
->get_text());
487 IMPL_LINK_NOARG(SwCreateAddressListDialog
, RefreshNum_Impl
, weld::Widget
&, void)
489 m_xSetNoED
->set_text(OUString::number(m_xSetNoNF
->get_value()));
492 void SwCreateAddressListDialog::DBNumCursor()
494 m_xAddressControl
->SetCurrentDataSet(m_xSetNoNF
->get_value() - 1);
498 void SwCreateAddressListDialog::UpdateButtons()
500 sal_uInt32 nCurrent
= static_cast< sal_uInt32
>(m_xSetNoNF
->get_value() );
501 sal_uInt32 nSize
= static_cast<sal_uInt32
>(m_pCSVData
->aDBData
.size());
502 m_xStartPB
->set_sensitive(nCurrent
!= 1);
503 m_xPrevPB
->set_sensitive(nCurrent
!= 1);
504 m_xNextPB
->set_sensitive(nCurrent
!= nSize
);
505 m_xEndPB
->set_sensitive(nCurrent
!= nSize
);
506 m_xDeletePB
->set_sensitive(nSize
> 0);
509 void SwCreateAddressListDialog::Find(const OUString
& rSearch
, sal_Int32 nColumn
)
511 const OUString sSearch
= rSearch
.toAsciiLowerCase();
512 sal_uInt32 nCurrent
= m_xAddressControl
->GetCurrentDataSet();
515 sal_uInt32 nStart
= nCurrent
+ 1;
516 sal_uInt32 nEnd
= m_pCSVData
->aDBData
.size();
517 std::size_t nElement
= 0;
519 for(short nTemp
= 0; nTemp
< 2 && !bFound
; nTemp
++)
521 for(nPos
= nStart
; nPos
< nEnd
; ++nPos
)
523 std::vector
< OUString
> const & aData
= m_pCSVData
->aDBData
[nPos
];
525 bFound
= -1 != aData
[static_cast<sal_uInt32
>(nColumn
)].toAsciiLowerCase().indexOf(sSearch
);
528 for( nElement
= 0; nElement
< aData
.size(); ++nElement
)
530 bFound
= -1 != aData
[nElement
].toAsciiLowerCase().indexOf(sSearch
);
533 nColumn
= nElement
; //TODO: std::size_t -> sal_Int32!
546 m_xAddressControl
->SetCurrentDataSet(nPos
);
547 m_xSetNoNF
->set_value( nPos
+ 1 );
548 RefreshNum_Impl(*m_xSetNoED
);
550 m_xAddressControl
->SetCursorTo(nElement
);
554 SwFindEntryDialog::SwFindEntryDialog(SwCreateAddressListDialog
* pParent
)
555 : GenericDialogController(pParent
->getDialog(), "modules/swriter/ui/findentrydialog.ui", "FindEntryDialog")
557 , m_xFindED(m_xBuilder
->weld_entry("entry"))
558 , m_xFindOnlyCB(m_xBuilder
->weld_check_button("findin"))
559 , m_xFindOnlyLB(m_xBuilder
->weld_combo_box("area"))
560 , m_xFindPB(m_xBuilder
->weld_button("find"))
561 , m_xCancel(m_xBuilder
->weld_button("cancel"))
563 m_xFindPB
->connect_clicked(LINK(this, SwFindEntryDialog
, FindHdl_Impl
));
564 m_xFindED
->connect_changed(LINK(this, SwFindEntryDialog
, FindEnableHdl_Impl
));
565 m_xCancel
->connect_clicked(LINK(this, SwFindEntryDialog
, CloseHdl_Impl
));
568 SwFindEntryDialog::~SwFindEntryDialog()
572 IMPL_LINK_NOARG(SwFindEntryDialog
, FindHdl_Impl
, weld::Button
&, void)
574 sal_Int32 nColumn
= -1;
575 if (m_xFindOnlyCB
->get_active())
576 nColumn
= m_xFindOnlyLB
->get_active();
577 m_pParent
->Find(m_xFindED
->get_text(), nColumn
);
580 IMPL_LINK_NOARG(SwFindEntryDialog
, FindEnableHdl_Impl
, weld::Entry
&, void)
582 m_xFindPB
->set_sensitive(!m_xFindED
->get_text().isEmpty());
585 IMPL_LINK_NOARG(SwFindEntryDialog
, CloseHdl_Impl
, weld::Button
&, void)
590 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */