xref: /core/sc/source/ui/dbgui/scuiasciiopt.cxx (revision 2f16890dafd2d5d90f5211c1f24c062dbc997851)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #undef SC_DLLIMPLEMENTATION
21 
22 #include <svx/txencbox.hxx>
23 
24 #include <global.hxx>
25 #include <scresid.hxx>
26 #include <impex.hxx>
27 #include <scuiasciiopt.hxx>
28 #include <strings.hrc>
29 #include <strings.hxx>
30 #include <csvtablebox.hxx>
31 #include <osl/thread.h>
32 #include <unotools/transliterationwrapper.hxx>
33 
34 #include <optutil.hxx>
35 #include <com/sun/star/uno/Any.hxx>
36 #include <com/sun/star/uno/Sequence.hxx>
37 #include <miscuno.hxx>
38 #include <osl/diagnose.h>
39 #include <vcl/svapp.hxx>
40 #include <comphelper/lok.hxx>
41 #include <comphelper/sequence.hxx>
42 #include <o3tl/string_view.hxx>
43 
44 #include <unicode/ucsdet.h>
45 #include <sfx2/objsh.hxx>
46 #include <svx/txenctab.hxx>
47 #include <unotools/filteroptions_settings.hxx>
48 #include <unotools/viewoptions.hxx>
49 
50 //! TODO make dynamic
51 const SCSIZE ASCIIDLG_MAXROWS                = MAXROWCOUNT;
52 
53 // Maximum number of source lines to concatenate while generating the preview
54 // for one logical line. This may result in a wrong preview if the actual
55 // number of embedded line feeds is greater, but a number too high would take
56 // too much time (loop excessively if unlimited and large data) if none of the
57 // selected separators are actually used in data but a field at start of line
58 // is quoted.
59 constexpr sal_uInt32 kMaxEmbeddedLinefeeds = 500;
60 
61 using namespace com::sun::star::uno;
62 
63 namespace {
64 // The values of this enum are stored in config's "SeparatorType" node
65 enum SeparatorType
66 {
67     FIXED = 0,
68     SEPARATOR = 1,
69     DETECT_SEPARATOR = 2,
70 };
71 
72 }
73 
74 // Config items for all three paths are defined in
75 // officecfg/registry/schema/org/openoffice/Office/Calc.xcs
76 // If not, options are neither loaded nor saved.
77 constexpr OUString CSVIO_MergeDelimiters = u"MergeDelimiters"_ustr;
78 constexpr OUString CSVIO_Separators = u"Separators"_ustr;
79 constexpr OUString CSVIO_TextSeparators = u"TextSeparators"_ustr;
80 constexpr OUString CSVIO_RemoveSpace = u"RemoveSpace"_ustr;
81 constexpr OUString CSVIO_EvaluateFormulas = u"EvaluateFormulas"_ustr;
82 constexpr OUString CSVIO_SeparatorType = u"SeparatorType"_ustr;
83 constexpr OUString CSVIO_FromRow = u"FromRow"_ustr;
84 constexpr OUString CSVIO_Encoding = u"Encoding"_ustr;
85 constexpr OUString CSVIO_QuotedAsText = u"QuotedFieldAsText"_ustr;
86 constexpr OUString CSVIO_DetectSpecialNum = u"DetectSpecialNumbers"_ustr;
87 constexpr OUString CSVIO_DetectScientificNum = u"DetectScientificNumbers"_ustr;
88 constexpr OUString CSVIO_Language = u"Language"_ustr;
89 constexpr OUString CSVIO_SkipEmptyCells = u"SkipEmptyCells"_ustr;
90 
lcl_FillCombo(weld::ComboBox & rCombo,std::u16string_view rList,sal_Unicode cSelect)91 static void lcl_FillCombo(weld::ComboBox& rCombo, std::u16string_view rList, sal_Unicode cSelect)
92 {
93     OUString aStr;
94     if (!rList.empty())
95     {
96         sal_Int32 nIdx {0};
97         do
98         {
99             const OUString sEntry {o3tl::getToken(rList, 0, '\t', nIdx)};
100             rCombo.append_text(sEntry);
101             if (nIdx>0 && static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nIdx))) == cSelect)
102                 aStr = sEntry;
103         }
104         while (nIdx>0);
105     }
106 
107     if ( cSelect )
108     {
109         if (aStr.isEmpty())
110             aStr = OUString(cSelect);         // Ascii
111 
112         rCombo.set_entry_text(aStr);
113     }
114 }
115 
lcl_CharFromCombo(const weld::ComboBox & rCombo,std::u16string_view rList)116 static sal_Unicode lcl_CharFromCombo(const weld::ComboBox& rCombo, std::u16string_view rList)
117 {
118     sal_Unicode c = 0;
119     OUString aStr = rCombo.get_active_text();
120     if ( !aStr.isEmpty() && !rList.empty() )
121     {
122         sal_Int32 nIdx {0};
123         OUString sToken {o3tl::getToken(rList, 0, '\t', nIdx)};
124         while (nIdx>0)
125         {
126             if ( ScGlobal::GetTransliteration().isEqual( aStr, sToken ) )
127             {
128                 sal_Int32 nTmpIdx {nIdx};
129                 c = static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nTmpIdx)));
130             }
131             // Skip to next token at even position
132             sToken = o3tl::getToken(rList, 1, '\t', nIdx);
133         }
134         if (!c)
135         {
136             sal_Unicode cFirst = aStr[0];
137             // #i24235# first try the first character of the string directly
138             if( (aStr.getLength() == 1) || (cFirst < '0') || (cFirst > '9') )
139                 c = cFirst;
140             else    // keep old behaviour for compatibility (i.e. "39" -> "'")
141                 c = static_cast<sal_Unicode>(aStr.toInt32());       // Ascii
142         }
143     }
144     return c;
145 }
146 
lcl_GetConfigPath(ScImportAsciiCall eCall)147 static OUString lcl_GetConfigPath(ScImportAsciiCall eCall)
148 {
149     switch(eCall)
150     {
151         case SC_IMPORTFILE:
152             return u"Office.Calc/Dialogs/CSVImport"_ustr;
153         case SC_PASTETEXT:
154             return u"Office.Calc/Dialogs/ClipboardTextImport"_ustr;
155         case SC_TEXTTOCOLUMNS:
156         default:
157             return u"Office.Calc/Dialogs/TextToColumnsImport"_ustr;
158     }
159 }
160 
lcl_LoadSeparators(ScImportAsciiCall eCall,OUString & rFieldSeparators,OUString & rTextSeparators,bool & rMergeDelimiters,bool & rQuotedAsText,bool & rDetectSpecialNum,bool & rDetectScientificNum,SeparatorType & rSepType,sal_Int32 & rFromRow,rtl_TextEncoding & rEncoding,sal_Int32 & rLanguage,bool & rSkipEmptyCells,bool & rRemoveSpace,bool & rEvaluateFormulas)161 static void lcl_LoadSeparators(ScImportAsciiCall eCall, OUString& rFieldSeparators, OUString& rTextSeparators,
162                              bool& rMergeDelimiters, bool& rQuotedAsText, bool& rDetectSpecialNum, bool& rDetectScientificNum,
163                              SeparatorType& rSepType, sal_Int32& rFromRow, rtl_TextEncoding& rEncoding,
164                              sal_Int32& rLanguage, bool& rSkipEmptyCells, bool& rRemoveSpace,
165                              bool& rEvaluateFormulas)
166 {
167     ScLinkConfigItem aItem(lcl_GetConfigPath(eCall));
168     const Sequence<OUString> aNames = aItem.GetNodeNames({});
169     const Sequence<Any> aValues = aItem.GetProperties(aNames);
170 
171     rEncoding = RTL_TEXTENCODING_DONTKNOW;
172 
173     for (sal_Int32 i = 0; i < aNames.getLength(); ++i)
174     {
175         const OUString& name = aNames[i];
176         const Any& value = aValues[i];
177         if (!value.hasValue())
178             continue;
179         if (name == CSVIO_MergeDelimiters)
180             rMergeDelimiters = ScUnoHelpFunctions::GetBoolFromAny(value);
181         else if (name == CSVIO_RemoveSpace)
182             rRemoveSpace = ScUnoHelpFunctions::GetBoolFromAny(value);
183         else if (name == CSVIO_Separators)
184             value >>= rFieldSeparators;
185         else if (name == CSVIO_TextSeparators)
186             value >>= rTextSeparators;
187         else if (name == CSVIO_SeparatorType)
188             rSepType = static_cast<SeparatorType>(ScUnoHelpFunctions::GetInt16FromAny(value));
189         else if (name == CSVIO_EvaluateFormulas)
190             rEvaluateFormulas = ScUnoHelpFunctions::GetBoolFromAny(value);
191         else if (name == CSVIO_FromRow)
192             value >>= rFromRow;
193         else if (name == CSVIO_Encoding)
194             value >>= rEncoding;
195         else if (name == CSVIO_QuotedAsText)
196             value >>= rQuotedAsText;
197         else if (name == CSVIO_DetectSpecialNum)
198             value >>= rDetectSpecialNum;
199         else if (name == CSVIO_DetectScientificNum)
200             value >>= rDetectScientificNum;
201         else if (name == CSVIO_Language)
202             value >>= rLanguage;
203         else if (name == CSVIO_SkipEmptyCells)
204             rSkipEmptyCells = ScUnoHelpFunctions::GetBoolFromAny(value);
205     }
206 }
207 
lcl_SaveSeparators(ScImportAsciiCall eCall,const OUString & sFieldSeparators,const OUString & sTextSeparators,bool bMergeDelimiters,bool bQuotedAsText,bool bDetectSpecialNum,bool bDetectScientificNum,SeparatorType rSepType,sal_Int32 nFromRow,rtl_TextEncoding eEncoding,sal_Int32 nLanguage,bool bSkipEmptyCells,bool bRemoveSpace,bool bEvaluateFormulas)208 static void lcl_SaveSeparators(ScImportAsciiCall eCall,
209     const OUString& sFieldSeparators, const OUString& sTextSeparators, bool bMergeDelimiters, bool bQuotedAsText,
210     bool bDetectSpecialNum, bool bDetectScientificNum, SeparatorType rSepType, sal_Int32 nFromRow,
211     rtl_TextEncoding eEncoding, sal_Int32 nLanguage, bool bSkipEmptyCells, bool bRemoveSpace, bool bEvaluateFormulas)
212 {
213     ScLinkConfigItem aItem(lcl_GetConfigPath(eCall));
214     std::unordered_map<OUString, Any> properties;
215 
216     for (const OUString& name : aItem.GetNodeNames({}))
217     {
218         if (name == CSVIO_MergeDelimiters)
219             properties[name] <<= bMergeDelimiters;
220         else if (name == CSVIO_RemoveSpace)
221             properties[name] <<= bRemoveSpace;
222         else if (name == CSVIO_Separators)
223             properties[name] <<= sFieldSeparators;
224         else if (name == CSVIO_TextSeparators)
225             properties[name] <<= sTextSeparators;
226         else if (name == CSVIO_EvaluateFormulas)
227             properties[name] <<= bEvaluateFormulas;
228         else if (name == CSVIO_SeparatorType)
229             properties[name] <<= static_cast<sal_Int16>(rSepType);
230         else if (name == CSVIO_FromRow)
231             properties[name] <<= nFromRow;
232         else if (name == CSVIO_Encoding)
233             properties[name] <<= eEncoding;
234         else if (name == CSVIO_QuotedAsText)
235             properties[name] <<= bQuotedAsText;
236         else if (name == CSVIO_DetectSpecialNum)
237             properties[name] <<= bDetectSpecialNum;
238         else if (name == CSVIO_DetectScientificNum)
239             properties[name] <<= bDetectScientificNum;
240         else if (name == CSVIO_Language)
241             properties[name] <<= nLanguage;
242         else if (name == CSVIO_SkipEmptyCells)
243             properties[name] <<= bSkipEmptyCells;
244     }
245 
246     aItem.PutProperties(comphelper::mapKeysToSequence(properties),
247                         comphelper::mapValuesToSequence(properties));
248 }
249 
ScImportAsciiDlg(weld::Window * pParent,std::u16string_view aDatName,SvStream * pInStream,ScImportAsciiCall eCall)250 ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aDatName,
251                                    SvStream* pInStream, ScImportAsciiCall eCall)
252     : GenericDialogController(pParent, u"modules/scalc/ui/textimportcsv.ui"_ustr, u"TextImportCsvDialog"_ustr)
253     , mpDatStream(pInStream)
254     , mnStreamPos(pInStream ? pInStream->Tell() : 0)
255     , mnStreamInitPos(mnStreamPos)
256     , mnRowPosCount(0)
257     , mcTextSep(ScAsciiOptions::cDefaultTextSep)
258     , meDetectedCharSet(RTL_TEXTENCODING_DONTKNOW)
259     , mbCharSetDetect(true)
260     , meCall(eCall)
261     , mxFtCharSet(m_xBuilder->weld_label(u"textcharset"_ustr))
262     , mxLbCharSet(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"charset"_ustr)))
263     , mxFtDetectedCharSet(m_xBuilder->weld_label(u"textdetectedcharset"_ustr))
264     , mxFtCustomLang(m_xBuilder->weld_label(u"textlanguage"_ustr))
265     , mxLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"language"_ustr)))
266     , mxFtRow(m_xBuilder->weld_label(u"textfromrow"_ustr))
267     , mxNfRow(m_xBuilder->weld_spin_button(u"fromrow"_ustr))
268     , mxRbDetectSep(m_xBuilder->weld_radio_button(u"todetectseparator"_ustr))
269     , mxRbFixed(m_xBuilder->weld_radio_button(u"tofixedwidth"_ustr))
270     , mxRbSeparated(m_xBuilder->weld_radio_button(u"toseparatedby"_ustr))
271     , mxCkbTab(m_xBuilder->weld_check_button(u"tab"_ustr))
272     , mxCkbSemicolon(m_xBuilder->weld_check_button(u"semicolon"_ustr))
273     , mxCkbComma(m_xBuilder->weld_check_button(u"comma"_ustr))
274     , mxCkbRemoveSpace(m_xBuilder->weld_check_button(u"removespace"_ustr))
275     , mxCkbSpace(m_xBuilder->weld_check_button(u"space"_ustr))
276     , mxCkbOther(m_xBuilder->weld_check_button(u"other"_ustr))
277     , mxEdOther(m_xBuilder->weld_entry(u"inputother"_ustr))
278     , mxCkbMergeDelimiters(m_xBuilder->weld_check_button(u"mergedelimiters"_ustr))
279     , mxFtTextSep(m_xBuilder->weld_label(u"texttextdelimiter"_ustr))
280     , mxCbTextSep(m_xBuilder->weld_combo_box(u"textdelimiter"_ustr))
281     , mxCkbQuotedAsText(m_xBuilder->weld_check_button(u"quotedfieldastext"_ustr))
282     , mxCkbDetectNumber(m_xBuilder->weld_check_button(u"detectspecialnumbers"_ustr))
283     , mxCkbDetectScientificNumber(m_xBuilder->weld_check_button(u"detectscientificnumbers"_ustr))
284     , mxCkbEvaluateFormulas(m_xBuilder->weld_check_button(u"evaluateformulas"_ustr))
285     , mxCkbSkipEmptyCells(m_xBuilder->weld_check_button(u"skipemptycells"_ustr))
286     , mxLbType(m_xBuilder->weld_combo_box(u"columntype"_ustr))
287     , mxAltTitle(m_xBuilder->weld_label(u"textalttitle"_ustr))
288     , mxTableBox(new ScCsvTableBox(*m_xBuilder))
289     , mxCkbAlwaysShowOnImport(m_xBuilder->weld_check_button(u"alwaysshowonimport"_ustr))
290 {
291     SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog");
292     if (aDlgOpt.Exists())
293         m_xDialog->set_window_state(aDlgOpt.GetWindowState());
294 
295     OUString aName = m_xDialog->get_title();
296     switch (meCall)
297     {
298         case SC_TEXTTOCOLUMNS:
299             m_xDialog->set_title(mxAltTitle->get_label());
300             break;
301         case SC_IMPORTFILE:
302             if (!comphelper::LibreOfficeKit::isActive())
303             {
304                 aName += OUString::Concat(" - [") + aDatName + "]";
305                 m_xDialog->set_title(aName);
306             }
307             mxCkbAlwaysShowOnImport->show();
308             mxCkbAlwaysShowOnImport->set_active(
309                 utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME));
310             break;
311         default:
312             break;
313     }
314 
315     // To be able to prefill the correct values based on the file extension
316     bool bIsTSV = (o3tl::endsWithIgnoreAsciiCase(aDatName, ".tsv") || o3tl::endsWithIgnoreAsciiCase(aDatName, ".tab"));
317 
318     // Default options are set in officecfg/registry/schema/org/openoffice/Office/Calc.xcs
319     OUString sFieldSeparators(u",;\t"_ustr);
320     OUString sTextSeparators(mcTextSep);
321     bool bMergeDelimiters = false;
322     SeparatorType eSepType = DETECT_SEPARATOR;
323     bool bQuotedFieldAsText = false;
324     bool bDetectSpecialNum = true;
325     bool bDetectScientificNum = true;
326     bool bEvaluateFormulas = (meCall != SC_IMPORTFILE);
327     bool bSkipEmptyCells = true;
328     bool bRemoveSpace = false;
329     sal_Int32 nFromRow = 1;
330     rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
331     sal_Int32 nLanguage = 0;
332 
333     lcl_LoadSeparators ( meCall, sFieldSeparators, sTextSeparators, bMergeDelimiters,
334                          bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, eSepType, nFromRow,
335                          eEncoding, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas);
336 
337     maFieldSeparators = sFieldSeparators;
338 
339     if( bMergeDelimiters && !bIsTSV )
340         mxCkbMergeDelimiters->set_active(true);
341     if (bQuotedFieldAsText)
342         mxCkbQuotedAsText->set_active(true);
343     if (bRemoveSpace)
344         mxCkbRemoveSpace->set_active(true);
345     if (bDetectSpecialNum)
346     {
347         mxCkbDetectNumber->set_active(true);
348         bDetectScientificNum = true;
349         mxCkbDetectScientificNumber->set_sensitive(false);
350     }
351     if (bDetectScientificNum)
352         mxCkbDetectScientificNumber->set_active(true);
353     if (bEvaluateFormulas)
354         mxCkbEvaluateFormulas->set_active(true);
355     if (bSkipEmptyCells)
356         mxCkbSkipEmptyCells->set_active(true);
357     if (eSepType == SeparatorType::FIXED)
358     {
359         if (bIsTSV)
360         {
361             eSepType = SeparatorType::SEPARATOR;
362             mxRbSeparated->set_active(true);
363         }
364         else
365             mxRbFixed->set_active(true);
366     }
367     else if (eSepType == SeparatorType::SEPARATOR)
368         mxRbSeparated->set_active(true);
369     else
370         mxRbDetectSep->set_active(true);
371 
372     // Detect character set only once and then use it for "Detect" option.
373     SvStreamEndian eEndian;
374     SfxObjectShell::DetectCharSet(*mpDatStream, meDetectedCharSet, eEndian);
375     if (meDetectedCharSet == RTL_TEXTENCODING_UNICODE)
376         mpDatStream->SetEndian(eEndian);
377     else if ( meDetectedCharSet == RTL_TEXTENCODING_DONTKNOW )
378     {
379         meDetectedCharSet = osl_getThreadTextEncoding();
380         // Prefer UTF-8, as UTF-16 would have already been detected from the stream.
381         // This gives a better chance that the file is going to be opened correctly.
382         if ( meDetectedCharSet == RTL_TEXTENCODING_UNICODE && mpDatStream )
383             meDetectedCharSet = RTL_TEXTENCODING_UTF8;
384     }
385 
386     if (bIsTSV)
387         SetSeparators('\t');
388     else
389         SetSeparators(0);
390 
391     // Get Separators from the dialog (empty are set from default)
392     maFieldSeparators = GetActiveSeparators();
393 
394     // *** Separator characters ***
395     lcl_FillCombo( *mxCbTextSep, SCSTR_TEXTSEP, mcTextSep );
396     mxCbTextSep->set_entry_text(sTextSeparators);
397     // tdf#69207 - use selected text delimiter to parse the provided data
398     mcTextSep = lcl_CharFromCombo(*mxCbTextSep, SCSTR_TEXTSEP);
399 
400     Link<weld::Toggleable&,void> aSeparatorClickHdl =LINK( this, ScImportAsciiDlg, SeparatorClickHdl );
401     Link<weld::Toggleable&,void> aOtherOptionsClickHdl =LINK( this, ScImportAsciiDlg, OtherOptionsClickHdl );
402     mxCbTextSep->connect_changed( LINK( this, ScImportAsciiDlg, SeparatorComboBoxHdl ) );
403     mxCkbTab->connect_toggled( aSeparatorClickHdl );
404     mxCkbSemicolon->connect_toggled( aSeparatorClickHdl );
405     mxCkbComma->connect_toggled( aSeparatorClickHdl );
406     mxCkbMergeDelimiters->connect_toggled( aSeparatorClickHdl );
407     mxCkbSpace->connect_toggled( aSeparatorClickHdl );
408     mxCkbRemoveSpace->connect_toggled( aSeparatorClickHdl );
409     mxCkbOther->connect_toggled( aSeparatorClickHdl );
410     mxEdOther->connect_changed(LINK(this, ScImportAsciiDlg, SeparatorEditHdl));
411     mxCkbQuotedAsText->connect_toggled( aOtherOptionsClickHdl );
412     mxCkbDetectNumber->connect_toggled( aOtherOptionsClickHdl );
413     mxCkbDetectScientificNumber->connect_toggled( aOtherOptionsClickHdl );
414     mxCkbEvaluateFormulas->connect_toggled( aOtherOptionsClickHdl );
415     mxCkbSkipEmptyCells->connect_toggled( aOtherOptionsClickHdl );
416 
417     // *** text encoding ListBox ***
418     // all encodings allowed, including Unicode, but subsets are excluded
419     mxLbCharSet->FillFromTextEncodingTable( true );
420     // Insert one "SYSTEM" entry for compatibility in AsciiOptions and system
421     // independent document linkage.
422     mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_DONTKNOW, ScResId( SCSTR_CHARSET_USER ) );
423     // Insert one for detecting charset.
424     mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_USER_DETECTED, "- " + ScResId( SCSTR_AUTOMATIC ) + " -" );
425 
426     // Clipboard is always Unicode, and TextToColumns doesn't use encoding.
427     if (meCall != SC_IMPORTFILE)
428         eEncoding = RTL_TEXTENCODING_UNICODE;
429     else if (eEncoding == RTL_TEXTENCODING_DONTKNOW)
430         eEncoding = RTL_TEXTENCODING_USER_DETECTED;
431 
432     mxLbCharSet->SelectTextEncoding(eEncoding);
433 
434     SetSelectedCharSet();
435     mxLbCharSet->connect_changed( LINK( this, ScImportAsciiDlg, CharSetHdl ) );
436 
437     mxLbCustomLang->SetLanguageList(
438         SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false, false);
439     mxLbCustomLang->InsertLanguage(LANGUAGE_SYSTEM);
440     mxLbCustomLang->set_active_id(static_cast<LanguageType>(nLanguage));
441 
442     // *** column type ListBox ***
443     OUString aColumnUser( ScResId( SCSTR_COLUMN_USER ) );
444     for (sal_Int32 nIdx {0}; nIdx>=0; )
445     {
446         mxLbType->append_text(aColumnUser.getToken(0, ';', nIdx));
447     }
448 
449     mxLbType->connect_changed( LINK( this, ScImportAsciiDlg, LbColTypeHdl ) );
450     mxLbType->set_sensitive(false);
451 
452     // *** table box preview ***
453     mxTableBox->Init();
454     mxTableBox->SetUpdateTextHdl( LINK( this, ScImportAsciiDlg, UpdateTextHdl ) );
455     mxTableBox->InitTypes( *mxLbType );
456     mxTableBox->SetColTypeHdl( LINK( this, ScImportAsciiDlg, ColTypeHdl ) );
457 
458     mxRbDetectSep->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
459     mxRbSeparated->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
460     mxRbFixed->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
461 
462     RbSepFix();
463 
464     UpdateVertical();
465 
466     mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
467 
468     if (nFromRow != 1)
469     {
470         mxNfRow->set_value(nFromRow);
471         // tdf#163638 - show visual indicator for from rows
472         mxTableBox->GetGrid().Execute(CSVCMD_SETFIRSTIMPORTLINE, nFromRow - 1);
473     }
474     mxNfRow->connect_value_changed(LINK(this, ScImportAsciiDlg, FirstRowHdl));
475 
476     if (meCall == SC_TEXTTOCOLUMNS)
477     {
478         m_xBuilder->weld_frame(u"frame1"_ustr)->hide(); // the whole "Import" section
479 
480         mxFtCharSet->set_sensitive(false);
481         mxLbCharSet->set_sensitive(false);
482         mxFtCustomLang->set_sensitive(false);
483         mxLbCustomLang->set_active_id(LANGUAGE_SYSTEM);
484         mxLbCustomLang->set_sensitive(false);
485         mxFtRow->set_sensitive(false);
486         mxNfRow->set_sensitive(false);
487 
488         // Quoted field as text option is not used for text-to-columns mode.
489         mxCkbQuotedAsText->set_active(false);
490         mxCkbQuotedAsText->set_sensitive(false);
491 
492         // Always detect special numbers for text-to-columns mode.
493         mxCkbDetectNumber->set_active(true);
494         mxCkbDetectNumber->set_sensitive(false);
495         mxCkbDetectScientificNumber->set_active(true);
496         mxCkbDetectScientificNumber->set_sensitive(false);
497     }
498     if (meCall == SC_IMPORTFILE)
499     {
500         //Empty cells in imported file are empty
501         mxCkbSkipEmptyCells->set_active(false);
502         mxCkbSkipEmptyCells->hide();
503     }
504 
505     if (comphelper::LibreOfficeKit::isActive())
506         m_xBuilder->weld_button(u"cancel"_ustr)->hide();
507     m_xDialog->SetInstallLOKNotifierHdl(LINK(this, ScImportAsciiDlg, InstallLOKNotifierHdl));
508 }
509 
IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg,InstallLOKNotifierHdl,void *,vcl::ILibreOfficeKitNotifier *)510 IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*)
511 {
512     return GetpApp();
513 }
514 
~ScImportAsciiDlg()515 ScImportAsciiDlg::~ScImportAsciiDlg()
516 {
517     SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog");
518     aDlgOpt.SetWindowState(m_xDialog->get_window_state(vcl::WindowDataMask::PosSize));
519 }
520 
GetLine(sal_uLong nLine,OUString & rText,sal_Unicode & rcDetectSep)521 bool ScImportAsciiDlg::GetLine( sal_uLong nLine, OUString &rText, sal_Unicode& rcDetectSep )
522 {
523     if (nLine >= ASCIIDLG_MAXROWS || !mpDatStream)
524         return false;
525 
526     bool bRet = true;
527     bool bFixed = mxRbFixed->get_active();
528 
529     if (!mpRowPosArray)
530         mpRowPosArray.reset( new sal_uLong[ASCIIDLG_MAXROWS + 2] );
531 
532     if (!mnRowPosCount) // complete re-fresh
533     {
534         memset( mpRowPosArray.get(), 0, sizeof(mpRowPosArray[0]) * (ASCIIDLG_MAXROWS+2));
535 
536         Seek(0);
537         mpDatStream->StartReadingUnicodeText( mpDatStream->GetStreamCharSet() );
538 
539         mnStreamPos = mpDatStream->Tell();
540         mpRowPosArray[mnRowPosCount] = mnStreamPos;
541     }
542 
543     if (nLine >= mnRowPosCount)
544     {
545         // need to work out some more line information
546         do
547         {
548             if (!Seek(mpRowPosArray[mnRowPosCount]) || !mpDatStream->good())
549             {
550                 bRet = false;
551                 break;
552             }
553             rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators,
554                     mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds);
555             mnStreamPos = mpDatStream->Tell();
556             mpRowPosArray[++mnRowPosCount] = mnStreamPos;
557         } while (nLine >= mnRowPosCount && mpDatStream->good());
558         if (mpDatStream->eof() && mnRowPosCount &&
559                 mnStreamPos == mpRowPosArray[mnRowPosCount-1])
560         {
561             // the very end, not even an empty line read
562             bRet = false;
563             --mnRowPosCount;
564         }
565     }
566     else
567     {
568         Seek( mpRowPosArray[nLine]);
569         rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds);
570         mnStreamPos = mpDatStream->Tell();
571     }
572 
573     //  If the file content isn't unicode, ReadUniStringLine
574     //  may try to seek beyond the file's end and cause a CANTSEEK error
575     //  (depending on the stream type). The error code has to be cleared,
576     //  or further read operations (including non-unicode) will fail.
577     if ( mpDatStream->GetError() == ERRCODE_IO_CANTSEEK )
578         mpDatStream->ResetError();
579 
580     ScImportExport::EmbeddedNullTreatment( rText);
581 
582     return bRet;
583 }
584 
GetOptions(ScAsciiOptions & rOpt)585 void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt )
586 {
587     rOpt.SetCharSet( meCharSet );
588     rOpt.SetCharSetSystem( mbCharSetSystem );
589     rOpt.SetLanguage(mxLbCustomLang->get_active_id());
590     rOpt.SetFixedLen( mxRbFixed->get_active() );
591     rOpt.SetStartRow( mxNfRow->get_value() );
592     mxTableBox->FillColumnData( rOpt );
593     if( mxRbSeparated->get_active() || mxRbDetectSep->get_active())
594     {
595         rOpt.SetFieldSeps( GetActiveSeparators() );
596         rOpt.SetMergeSeps( mxCkbMergeDelimiters->get_active() );
597         rOpt.SetRemoveSpace( mxCkbRemoveSpace->get_active() );
598         rOpt.SetTextSep( lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ) );
599     }
600 
601     rOpt.SetQuotedAsText(mxCkbQuotedAsText->get_active());
602     rOpt.SetDetectSpecialNumber(mxCkbDetectNumber->get_active());
603     rOpt.SetDetectScientificNumber(mxCkbDetectScientificNumber->get_active());
604     rOpt.SetEvaluateFormulas(mxCkbEvaluateFormulas->get_active());
605     rOpt.SetSkipEmptyCells(mxCkbSkipEmptyCells->get_active());
606 }
607 
SaveParameters()608 void ScImportAsciiDlg::SaveParameters()
609 {
610     if (mxCkbAlwaysShowOnImport->get_visible())
611     {
612         bool value(mxCkbAlwaysShowOnImport->get_active());
613         if (value != utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME))
614         {
615             auto pChange(comphelper::ConfigurationChanges::create());
616             auto xFilterDialogSettings(
617                 utl::getSettingsForFilterOptions(SC_TEXT_CSV_FILTER_NAME, pChange));
618             xFilterDialogSettings->setPropertyValue(u"show"_ustr, Any(value));
619             pChange->commit();
620         }
621     }
622     lcl_SaveSeparators(meCall, GetSeparators(), mxCbTextSep->get_active_text(), mxCkbMergeDelimiters->get_active(),
623                      mxCkbQuotedAsText->get_active(), mxCkbDetectNumber->get_active(), mxCkbDetectScientificNumber->get_active(),
624                      mxRbFixed->get_active() ? FIXED : (mxRbDetectSep->get_active() ? DETECT_SEPARATOR : SEPARATOR),
625                      mxNfRow->get_value(),
626                      mxLbCharSet->GetSelectTextEncoding(),
627                      static_cast<sal_uInt16>(mxLbCustomLang->get_active_id()),
628                      mxCkbSkipEmptyCells->get_active(), mxCkbRemoveSpace->get_active(),
629                      mxCkbEvaluateFormulas->get_active());
630 }
631 
SetSeparators(sal_Unicode cSep)632 void ScImportAsciiDlg::SetSeparators( sal_Unicode cSep )
633 {
634     if (cSep)
635     {
636         // Exclusively set a separator, maFieldSeparators needs not be
637         // modified, it's obtained by GetSeparators() after this call.
638         static constexpr sal_Unicode aSeps[] = { '\t', ';', ',', ' ' };
639         for (const sal_Unicode c : aSeps)
640         {
641             const bool bSet = (c == cSep);
642             switch (c)
643             {
644                 case '\t':  mxCkbTab->set_active(bSet);        break;
645                 case ';':   mxCkbSemicolon->set_active(bSet);  break;
646                 case ',':   mxCkbComma->set_active(bSet);      break;
647                 case ' ':   mxCkbSpace->set_active(bSet);      break;
648             }
649             if (bSet)
650                 cSep = 0;
651         }
652         if (cSep)
653         {
654             mxCkbOther->set_active(true);
655             mxEdOther->set_text(OUStringChar(cSep));
656         }
657     }
658     else
659     {
660         for (sal_Int32 i = 0; i < maFieldSeparators.getLength(); ++i)
661         {
662             switch (maFieldSeparators[i])
663             {
664                 case '\t':  mxCkbTab->set_active(true);        break;
665                 case ';':   mxCkbSemicolon->set_active(true);  break;
666                 case ',':   mxCkbComma->set_active(true);      break;
667                 case ' ':   mxCkbSpace->set_active(true);      break;
668                 default:
669                             mxCkbOther->set_active(true);
670                             mxEdOther->set_text(mxEdOther->get_text() + OUStringChar(maFieldSeparators[i]));
671             }
672         }
673     }
674 }
675 
SetSelectedCharSet()676 void ScImportAsciiDlg::SetSelectedCharSet()
677 {
678     rtl_TextEncoding eOldCharSet = meCharSet;
679     meCharSet = mxLbCharSet->GetSelectTextEncoding();
680     mbCharSetDetect = (meCharSet == RTL_TEXTENCODING_USER_DETECTED);
681     mbCharSetSystem = (meCharSet == RTL_TEXTENCODING_DONTKNOW);
682     if (mbCharSetDetect)
683     {
684         meCharSet = meDetectedCharSet;
685         mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
686     }
687     else if( mbCharSetSystem )
688     {
689         meCharSet = osl_getThreadTextEncoding();
690         mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
691     }
692     else
693         mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
694 
695     if (eOldCharSet != meCharSet)
696         DetectCsvSeparators();
697 
698     RbSepFix();
699 }
700 
GetSeparators() const701 OUString ScImportAsciiDlg::GetSeparators() const
702 {
703     OUString aSepChars;
704     if( mxCkbTab->get_active() )
705         aSepChars += "\t";
706     if( mxCkbSemicolon->get_active() )
707         aSepChars += ";";
708     if( mxCkbComma->get_active() )
709         aSepChars += ",";
710     if( mxCkbSpace->get_active() )
711         aSepChars += " ";
712     if( mxCkbOther->get_active() )
713         aSepChars += mxEdOther->get_text();
714     return aSepChars;
715 }
716 
GetActiveSeparators() const717 OUString ScImportAsciiDlg::GetActiveSeparators() const
718 {
719     if (mxRbSeparated->get_active())
720         return GetSeparators();
721 
722     if (mxRbDetectSep->get_active())
723         return maDetectedFieldSeps;
724 
725     return OUString();
726 }
727 
SetupSeparatorCtrls()728 void ScImportAsciiDlg::SetupSeparatorCtrls()
729 {
730     bool bEnable = mxRbSeparated->get_active();
731     mxCkbTab->set_sensitive( bEnable );
732     mxCkbSemicolon->set_sensitive( bEnable );
733     mxCkbComma->set_sensitive( bEnable );
734     mxCkbSpace->set_sensitive( bEnable );
735     mxCkbOther->set_sensitive( bEnable );
736     mxEdOther->set_sensitive( bEnable );
737 
738     bEnable = bEnable || mxRbDetectSep->get_active();
739     mxCkbRemoveSpace->set_sensitive( bEnable );
740     mxCkbMergeDelimiters->set_sensitive( bEnable );
741     mxFtTextSep->set_sensitive( bEnable );
742     mxCbTextSep->set_sensitive( bEnable );
743 
744     OUString aSepName;
745     if (maDetectedFieldSeps.isEmpty())
746         aSepName += ScResId(SCSTR_NONE);
747     else
748     {
749         for (int idx = 0; idx < maDetectedFieldSeps.getLength(); idx ++)
750         {
751             if (idx > 0)
752                 aSepName += u" ";
753 
754             if (maDetectedFieldSeps[idx] == u' ')
755                 aSepName += ScResId(SCSTR_FIELDSEP_SPACE);
756             else if (maDetectedFieldSeps[idx] == u'\t')
757                 aSepName += ScResId(SCSTR_FIELDSEP_TAB);
758             else
759                 aSepName += OUStringChar(maDetectedFieldSeps[idx]);
760         }
761     }
762     mxRbDetectSep->set_label(ScResId(SCSTR_DETECTED).replaceFirst( "%1", aSepName));
763 }
764 
DetectCsvSeparators()765 void ScImportAsciiDlg::DetectCsvSeparators()
766 {
767     mpDatStream->Seek(mnStreamInitPos);
768     SfxObjectShell::DetectCsvSeparators(*mpDatStream, meCharSet, maDetectedFieldSeps, mcTextSep);
769     mpDatStream->Seek(mnStreamPos);
770 }
771 
UpdateVertical()772 void ScImportAsciiDlg::UpdateVertical()
773 {
774     mnRowPosCount = 0;
775     if (mpDatStream)
776         mpDatStream->SetStreamCharSet(meCharSet);
777 }
778 
RbSepFix()779 void ScImportAsciiDlg::RbSepFix()
780 {
781     weld::WaitObject aWaitObj(m_xDialog.get());
782     if (mxRbSeparated->get_active() || mxRbDetectSep->get_active())
783     {
784         maFieldSeparators = GetActiveSeparators();
785         if (mxTableBox->IsFixedWidthMode())
786             mxTableBox->SetSeparatorsMode();
787         else
788             mxTableBox->Refresh();
789     }
790     else
791         mxTableBox->SetFixedWidthMode();
792 
793     SetupSeparatorCtrls();
794 }
795 
IMPL_LINK(ScImportAsciiDlg,RbSepFixHdl,weld::Toggleable &,rButton,void)796 IMPL_LINK(ScImportAsciiDlg, RbSepFixHdl, weld::Toggleable&, rButton, void)
797 {
798     if (!rButton.get_active())
799         return;
800     RbSepFix();
801 }
802 
IMPL_LINK(ScImportAsciiDlg,SeparatorClickHdl,weld::Toggleable &,rCtrl,void)803 IMPL_LINK(ScImportAsciiDlg, SeparatorClickHdl, weld::Toggleable&, rCtrl, void)
804 {
805     SeparatorHdl(&rCtrl);
806 }
807 
IMPL_LINK(ScImportAsciiDlg,SeparatorComboBoxHdl,weld::ComboBox &,rCtrl,void)808 IMPL_LINK( ScImportAsciiDlg, SeparatorComboBoxHdl, weld::ComboBox&, rCtrl, void )
809 {
810     SeparatorHdl(&rCtrl);
811 }
812 
IMPL_LINK(ScImportAsciiDlg,SeparatorEditHdl,weld::Entry &,rEdit,void)813 IMPL_LINK( ScImportAsciiDlg, SeparatorEditHdl, weld::Entry&, rEdit, void )
814 {
815     SeparatorHdl(&rEdit);
816 }
817 
IMPL_LINK(ScImportAsciiDlg,OtherOptionsClickHdl,weld::Toggleable &,rCtrl,void)818 IMPL_LINK(ScImportAsciiDlg, OtherOptionsClickHdl, weld::Toggleable&, rCtrl, void)
819 {
820     if (&rCtrl == mxCkbDetectNumber.get())
821     {
822         if (mxCkbDetectNumber->get_active())
823         {
824             mxCkbDetectScientificNumber->set_active(true);
825             mxCkbDetectScientificNumber->set_sensitive(false);
826         }
827         else
828             mxCkbDetectScientificNumber->set_sensitive(true);
829         return;
830     }
831 }
832 
SeparatorHdl(const weld::Widget * pCtrl)833 void ScImportAsciiDlg::SeparatorHdl(const weld::Widget* pCtrl)
834 {
835     OSL_ENSURE( pCtrl, "ScImportAsciiDlg::SeparatorHdl - missing sender" );
836     OSL_ENSURE( !mxRbFixed->get_active(), "ScImportAsciiDlg::SeparatorHdl - not allowed in fixed width" );
837 
838     /*  #i41550# First update state of the controls. The GetSeparators()
839         function needs final state of the check boxes. */
840     if (pCtrl == mxCkbOther.get() && mxCkbOther->get_active())
841         mxEdOther->grab_focus();
842     else if (pCtrl == mxEdOther.get())
843         mxCkbOther->set_active(!mxEdOther->get_text().isEmpty());
844 
845     OUString aOldFldSeps( maFieldSeparators);
846     sal_Unicode cOldSep = mcTextSep;
847     mcTextSep = lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP );
848     // Any separator changed may result in completely different lines due to
849     // embedded line breaks.
850     if (cOldSep != mcTextSep)
851     {
852         DetectCsvSeparators();
853 
854         SetupSeparatorCtrls();
855 
856         maFieldSeparators = GetActiveSeparators();
857         if (aOldFldSeps != maFieldSeparators)
858         {
859             UpdateVertical();
860             mxTableBox->Refresh();
861             return;
862         }
863     }
864     else
865         maFieldSeparators = GetActiveSeparators();
866 
867     mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
868 }
869 
IMPL_LINK_NOARG(ScImportAsciiDlg,CharSetHdl,weld::ComboBox &,void)870 IMPL_LINK_NOARG(ScImportAsciiDlg, CharSetHdl, weld::ComboBox&, void)
871 {
872     if (mxLbCharSet->get_active() != -1)
873     {
874         weld::WaitObject aWaitObj(m_xDialog.get());
875         rtl_TextEncoding eOldCharSet = meCharSet;
876         SetSelectedCharSet();
877         // switching char-set invalidates 8bit -> String conversions
878         if (eOldCharSet != meCharSet)
879             UpdateVertical();
880 
881         mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
882     }
883 }
884 
IMPL_LINK(ScImportAsciiDlg,FirstRowHdl,weld::SpinButton &,rNumField,void)885 IMPL_LINK(ScImportAsciiDlg, FirstRowHdl, weld::SpinButton&, rNumField, void)
886 {
887     mxTableBox->GetGrid().Execute( CSVCMD_SETFIRSTIMPORTLINE, rNumField.get_value() - 1);
888 }
889 
IMPL_LINK(ScImportAsciiDlg,LbColTypeHdl,weld::ComboBox &,rListBox,void)890 IMPL_LINK(ScImportAsciiDlg, LbColTypeHdl, weld::ComboBox&, rListBox, void)
891 {
892     if (&rListBox == mxLbType.get())
893         mxTableBox->GetGrid().Execute(CSVCMD_SETCOLUMNTYPE, rListBox.get_active());
894 }
895 
IMPL_LINK_NOARG(ScImportAsciiDlg,UpdateTextHdl,ScCsvTableBox &,void)896 IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void)
897 {
898     sal_Unicode cDetectSep = 0xffff;
899 
900     sal_Int32 nBaseLine = mxTableBox->GetGrid().GetFirstVisLine();
901     sal_Int32 nRead = mxTableBox->GetGrid().GetVisLineCount();
902     // If mnRowPosCount==0, this is an initializing call, read ahead for row
903     // count and resulting scroll bar size and position to be able to scroll at
904     // all. When adding lines, read only the amount of next lines to be
905     // displayed.
906     if (!mnRowPosCount || nRead > CSV_PREVIEW_LINES)
907         nRead = CSV_PREVIEW_LINES;
908 
909     sal_Int32 i;
910     for (i = 0; i < nRead; i++)
911     {
912         if (!GetLine( nBaseLine + i, maPreviewLine[i], cDetectSep))
913             break;
914     }
915     for (; i < CSV_PREVIEW_LINES; i++)
916         maPreviewLine[i].clear();
917 
918 
919     mxTableBox->GetGrid().Execute( CSVCMD_SETLINECOUNT, mnRowPosCount);
920     bool bMergeSep = mxCkbMergeDelimiters->get_active();
921     bool bRemoveSpace = mxCkbRemoveSpace->get_active();
922     mxTableBox->SetUniStrings( maPreviewLine, maFieldSeparators, mcTextSep, bMergeSep, bRemoveSpace );
923 }
924 
IMPL_LINK(ScImportAsciiDlg,ColTypeHdl,ScCsvTableBox &,rTableBox,void)925 IMPL_LINK( ScImportAsciiDlg, ColTypeHdl, ScCsvTableBox&, rTableBox, void )
926 {
927     sal_Int32 nType = rTableBox.GetSelColumnType();
928     sal_Int32 nTypeCount = mxLbType->get_count();
929     bool bEmpty = (nType == CSV_TYPE_MULTI);
930     bool bEnable = ((0 <= nType) && (nType < nTypeCount)) || bEmpty;
931 
932     mxLbType->set_sensitive( bEnable );
933 
934     if (bEmpty)
935         mxLbType->set_active(-1);
936     else if (bEnable)
937         mxLbType->set_active(nType);
938 }
939 
940 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
941