1 /* This file is part of the KDE project
2    Copyright (C) 2012 Oleg Kukharchuk <oleg.kuh@gmail.org>
3    Copyright (C) 2005-2018 Jarosław Staniek <staniek@kde.org>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kexicsvexportwizard.h"
22 #include "kexicsvwidgets.h"
23 #include <core/KexiMainWindowIface.h>
24 #include <core/kexiproject.h>
25 #include <core/kexipartinfo.h>
26 #include <core/kexipartmanager.h>
27 #include <core/kexiguimsghandler.h>
28 #include <kexiutils/utils.h>
29 #include <widget/kexicharencodingcombobox.h>
30 #include <widget/KexiFileWidgetInterface.h>
31 #include <KexiIcon.h>
32 
33 #include <KDbConnection>
34 #include <KDbCursor>
35 #include <KDbQuerySchema>
36 #include <KDbTableOrQuerySchema>
37 #include <KDbUtils>
38 
39 #include <KSharedConfig>
40 #include <KLocalizedString>
41 
42 #include <QCheckBox>
43 #include <QGroupBox>
44 #include <QClipboard>
45 #include <QGridLayout>
46 #include <QFileInfo>
47 #include <QHBoxLayout>
48 #include <QLabel>
49 #include <QMimeDatabase>
50 #include <QPushButton>
51 #include <QDialog>
52 #include <QDebug>
53 
54 namespace {
55     const QString DEFAULT_EXTENSION("csv");
56 
57     //! Adds extension if missing
58     //! @todo Move to KexiFileWidgetInterface or so
addExtensionIfNeeded(QString * fileName)59     void addExtensionIfNeeded(QString *fileName) {
60         QMimeDatabase db;
61         const QMimeType currentMimeType(db.mimeTypeForFile(*fileName, QMimeDatabase::MatchExtension));
62         qDebug() << currentMimeType.name();
63         if (!fileName->isEmpty() && currentMimeType.isDefault()) { // no known extension, add
64             fileName->append('.' + DEFAULT_EXTENSION);
65         }
66     }
67 }
68 
KexiCSVExportWizard(const KexiCSVExport::Options & options,QWidget * parent)69 KexiCSVExportWizard::KexiCSVExportWizard(const KexiCSVExport::Options& options,
70         QWidget * parent)
71         : KAssistantDialog(parent)
72         , m_options(options)
73         , m_importExportGroup(KSharedConfig::openConfig()->group("ImportExport"))
74 {
75     KexiMainWindowIface::global()->setReasonableDialogSize(this);
76     if (m_options.mode == KexiCSVExport::Clipboard) {
77         //! @todo KEXI3 ?
78         finishButton()->setText(xi18n("Copy"));
79     } else {
80         finishButton()->setText(xi18n("Export"));
81     }
82 
83     QString infoLblFromText;
84     QString captionOrName;
85     KexiGUIMessageHandler msgh(this);
86     KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection();
87     if (m_options.useTempQuery) {
88         m_tableOrQuery = new KDbTableOrQuerySchema(KexiMainWindowIface::global()->unsavedQuery(options.itemId));
89         captionOrName = conn->querySchema(m_options.itemId)->captionOrName();
90     } else {
91         m_tableOrQuery = new KDbTableOrQuerySchema(conn, m_options.itemId);
92         captionOrName = m_tableOrQuery->captionOrName();
93     }
94     if (m_tableOrQuery->table()) {
95         if (m_options.mode == KexiCSVExport::Clipboard) {
96             setWindowTitle(xi18nc("@title:window", "Copy Data From Table to Clipboard"));
97             infoLblFromText = xi18n("Copying data from table:");
98         } else {
99             setWindowTitle(xi18nc("@title:window", "Export Data From Table to CSV File"));
100             infoLblFromText = xi18n("Exporting data from table:");
101         }
102     } else if (m_tableOrQuery->query()) {
103         if (m_options.mode == KexiCSVExport::Clipboard) {
104             setWindowTitle(xi18nc("@title:window", "Copy Data From Query to Clipboard"));
105             infoLblFromText = xi18n("Copying data from table:");
106         } else {
107             setWindowTitle(xi18nc("@title:window", "Export Data From Query to CSV File"));
108             infoLblFromText = xi18n("Exporting data from query:");
109         }
110     } else {
111         msgh.showErrorMessage(conn->result(), KDbMessageHandler::Error,
112                               xi18n("Could not open data for exporting."));
113         m_canceled = true;
114         return;
115     }
116 
117     QString text = "\n" + captionOrName;
118     int m_recordCount = conn->recordCount(m_tableOrQuery);
119     int columns = m_tableOrQuery->fieldCount(conn);
120     text += "\n";
121     if (m_recordCount > 0)
122         text += xi18n("(rows: %1, columns: %2)", m_recordCount, columns);
123     else
124         text += xi18n("(columns: %1)", columns);
125     infoLblFromText.append(text);
126 
127     // OK, source data found.
128 
129     // Setup pages
130 
131     // 1. File Save Page
132     if (m_options.mode == KexiCSVExport::File) {
133         QString defaultFileName(KDbUtils::stringToFileName(captionOrName));
134         addExtensionIfNeeded(&defaultFileName);
135         m_fileIface = KexiFileWidgetInterface::createWidget(
136             QUrl("kfiledialog:///CSVImportExport"), KexiFileFilters::CustomSavingFileBasedDB,
137             defaultFileName, this);
138         m_fileIface->setAdditionalMimeTypes(csvMimeTypes());
139         m_fileIface->setDefaultExtension(DEFAULT_EXTENSION);
140         m_fileSavePage = new KPageWidgetItem(m_fileIface->widget(),
141                                              xi18n("Enter Name of File You Want to Save Data To"));
142         addPage(m_fileSavePage);
143         connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)),
144                 this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*)));
145     }
146 
147     /* 2. Export options
148         m_exportOptionsPage
149         exportOptionsLyr
150             m_infoLblFrom
151             m_infoLblTo
152             m_showOptionsButton
153             m_exportOptionsSection
154             exportOptionsSectionLyr
155     */
156     m_exportOptionsWidget = new QWidget(this);
157     m_exportOptionsWidget->setObjectName("m_exportOptionsPage");
158 
159     QGridLayout *exportOptionsLyr = new QGridLayout(m_exportOptionsWidget);
160     exportOptionsLyr->setObjectName("exportOptionsLyr");
161 
162     m_infoLblFrom = new KexiCSVInfoLabel(infoLblFromText, m_exportOptionsWidget, true/*showFnameLine*/);
163     KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId(
164             QString("org.kexi-project.%1").arg(m_tableOrQuery->table() ? "table" : "query"));
165     if (partInfo) {
166         m_infoLblFrom->setIcon(partInfo->iconName());
167     }
168     m_infoLblFrom->separator()->hide();
169     m_infoLblFrom->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
170     exportOptionsLyr->addWidget(m_infoLblFrom, 0, 0, 1, 2);
171 
172     m_infoLblTo = new KexiCSVInfoLabel(
173         (m_options.mode == KexiCSVExport::File) ? xi18n("To CSV file:") : xi18n("To clipboard."),
174         m_exportOptionsWidget, true/*showFnameLine*/);
175 
176     if (m_options.mode == KexiCSVExport::Clipboard)
177         m_infoLblTo->setIcon(koIconName("edit-paste"));
178 
179     m_infoLblTo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
180     exportOptionsLyr->addWidget(m_infoLblTo, 1, 0, 1, 2);
181     exportOptionsLyr->setRowStretch(2, 1);
182     m_showOptionsButton = new QPushButton(xi18n("Show Options &gt;&gt;"));
183     m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
184     connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked()));
185     exportOptionsLyr->addWidget(m_showOptionsButton, 3, 1, Qt::AlignRight);
186 
187     // -<options section>
188     m_exportOptionsSection = new QGroupBox(""/*xi18n("Options")*/);
189     m_exportOptionsSection->setObjectName("m_exportOptionsSection");
190     m_exportOptionsSection->setAlignment(Qt::Vertical);
191     m_exportOptionsSection->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
192     exportOptionsLyr->addWidget(m_exportOptionsSection, 4, 0, 1, 2);
193 
194     QGridLayout *exportOptionsSectionLyr = new QGridLayout;
195     exportOptionsLyr->setObjectName("exportOptionsLyr");
196     m_exportOptionsSection->setLayout(exportOptionsSectionLyr);
197 
198     // -delimiter
199     QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:"));
200     exportOptionsSectionLyr->addWidget(delimiterLabel, 0, 0);
201 
202     m_delimiterWidget = new KexiCSVDelimiterWidget(false /* !lineEditOnBottom*/);
203     m_delimiterWidget->setDelimiter(defaultDelimiter());
204     delimiterLabel->setBuddy(m_delimiterWidget);
205     exportOptionsSectionLyr->addWidget(m_delimiterWidget, 0, 1);
206 
207     // -text quote
208     QLabel *textQuoteLabel = new QLabel(xi18n("Text quote:"));
209     exportOptionsSectionLyr->addWidget(textQuoteLabel, 1, 0);
210 
211     QWidget *textQuoteWidget = new QWidget;
212     QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget);
213 
214     m_textQuote = new KexiCSVTextQuoteComboBox(textQuoteWidget);
215     m_textQuote->setTextQuote(defaultTextQuote());
216     textQuoteLabel->setBuddy(m_textQuote);
217     textQuoteLyr->addWidget(m_textQuote);
218     textQuoteLyr->addStretch(0);
219 
220     exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1);
221 
222     // - character encoding
223     QLabel *characterEncodingLabel = new QLabel(xi18n("Text encoding:"));
224     exportOptionsSectionLyr->addWidget(characterEncodingLabel, 2, 0);
225 
226     m_characterEncodingCombo = new KexiCharacterEncodingComboBox();
227     m_characterEncodingCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
228     characterEncodingLabel->setBuddy(m_characterEncodingCombo);
229     exportOptionsSectionLyr->addWidget(m_characterEncodingCombo, 2, 1);
230 
231     // - checkboxes
232     m_addColumnNamesCheckBox = new QCheckBox(xi18n("Add column names as the first row"));
233     m_addColumnNamesCheckBox->setChecked(true);
234     exportOptionsSectionLyr->addWidget(m_addColumnNamesCheckBox, 3, 1);
235 
236     m_defaultsBtn = new QPushButton(xi18n("Defaults"), this);
237     connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked()));
238     exportOptionsLyr->addWidget(m_defaultsBtn, 5, 0);
239     exportOptionsLyr->setColumnStretch(1, 1);
240     m_alwaysUseCheckBox = new QCheckBox(
241         m_options.mode == KexiCSVExport::Clipboard ?
242           xi18n("Always use above options for copying")
243         : xi18n("Always use above options for exporting"));
244     exportOptionsLyr->addWidget(m_alwaysUseCheckBox, 5, 1, Qt::AlignRight);
245 
246     m_exportOptionsSection->hide();
247     m_defaultsBtn->hide();
248     m_alwaysUseCheckBox->hide();
249     // -</options section>
250 
251     m_exportOptionsPage = new KPageWidgetItem(m_exportOptionsWidget, m_options.mode == KexiCSVExport::Clipboard ? xi18n("Copying") : xi18n("Exporting"));
252     addPage(m_exportOptionsPage);
253 
254     // load settings
255     if (m_options.mode != KexiCSVExport::Clipboard
256             && readBoolEntry("ShowOptionsInCSVExportDialog", false)) {
257         show();
258         slotShowOptionsButtonClicked();
259     }
260     if (readBoolEntry("StoreOptionsForCSVExportDialog", false)) {
261         // load defaults:
262         m_alwaysUseCheckBox->setChecked(true);
263         QString s = readEntry("DefaultDelimiterForExportingCSVFiles", defaultDelimiter());
264         if (!s.isEmpty())
265             m_delimiterWidget->setDelimiter(s);
266         s = readEntry("DefaultTextQuoteForExportingCSVFiles", defaultTextQuote());
267         m_textQuote->setTextQuote(s); //will be invaliudated here, so not a problem
268         s = readEntry("DefaultEncodingForExportingCSVFiles");
269         if (!s.isEmpty())
270             m_characterEncodingCombo->setSelectedEncoding(s);
271         m_addColumnNamesCheckBox->setChecked(
272             readBoolEntry("AddColumnNamesForExportingCSVFiles", true));
273     }
274 
275     // -keep widths equal on page #2:
276     int width = qMax(m_infoLblFrom->leftLabel()->sizeHint().width(),
277                      m_infoLblTo->leftLabel()->sizeHint().width());
278     m_infoLblFrom->leftLabel()->setFixedWidth(width);
279     m_infoLblTo->leftLabel()->setFixedWidth(width);
280 
281     updateGeometry();
282 }
283 
~KexiCSVExportWizard()284 KexiCSVExportWizard::~KexiCSVExportWizard()
285 {
286     delete m_tableOrQuery;
287 }
288 
canceled() const289 bool KexiCSVExportWizard::canceled() const
290 {
291     return m_canceled;
292 }
293 
slotCurrentPageChanged(KPageWidgetItem * page,KPageWidgetItem * prev)294 void KexiCSVExportWizard::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev)
295 {
296     Q_UNUSED(prev)
297 
298     if (page == m_fileSavePage) {
299         m_fileIface->widget()->setFocus();
300     } else if (page == m_exportOptionsPage) {
301         if (m_options.mode == KexiCSVExport::File)
302             m_infoLblTo->setFileName(selectedFile());
303     }
304 }
305 
selectedFile() const306 QString KexiCSVExportWizard::selectedFile() const
307 {
308     return m_fileIface->selectedFile();
309 }
310 
next()311 void KexiCSVExportWizard::next()
312 {
313     if (currentPage() == m_fileSavePage) {
314         const QString selectedFile(this->selectedFile());
315         QString newSelectedFile(selectedFile);
316         addExtensionIfNeeded(&newSelectedFile);
317         if (selectedFile != newSelectedFile) {
318             m_fileIface->setSelectedFile(newSelectedFile);
319         }
320         if (!m_fileIface->checkSelectedFile()) {
321             return;
322         }
323         KAssistantDialog::next();
324         return;
325     }
326     KAssistantDialog::next();
327 }
328 
done(int result)329 void KexiCSVExportWizard::done(int result)
330 {
331     KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection();
332     if (QDialog::Accepted == result) {
333         if (m_fileSavePage) {
334             //qDebug() << selectedFile();
335             m_options.fileName = selectedFile();
336         }
337         m_options.delimiter = m_delimiterWidget->delimiter();
338         m_options.textQuote = m_textQuote->textQuote();
339         m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked();
340         if (!KexiCSVExport::exportData(conn, m_tableOrQuery, m_options))
341             return;
342 
343         //store options
344         if (m_options.mode != KexiCSVExport::Clipboard)
345             writeEntry("ShowOptionsInCSVExportDialog", m_exportOptionsSection->isVisible());
346         const bool store = m_alwaysUseCheckBox->isChecked();
347         writeEntry("StoreOptionsForCSVExportDialog", store);
348         // only save if an option differs from default
349 
350         if (store && m_delimiterWidget->delimiter() != defaultDelimiter())
351             writeEntry("DefaultDelimiterForExportingCSVFiles", m_delimiterWidget->delimiter());
352         else
353             deleteEntry("DefaultDelimiterForExportingCSVFiles");
354         if (store && m_textQuote->textQuote() != defaultTextQuote())
355             writeEntry("DefaultTextQuoteForExportingCSVFiles", m_textQuote->textQuote());
356         else
357             deleteEntry("DefaultTextQuoteForExportingCSVFiles");
358         if (store && !m_characterEncodingCombo->defaultEncodingSelected())
359             writeEntry(
360                 "DefaultEncodingForExportingCSVFiles", m_characterEncodingCombo->selectedEncoding());
361         else
362             deleteEntry("DefaultEncodingForExportingCSVFiles");
363         if (store && !m_addColumnNamesCheckBox->isChecked())
364             writeEntry(
365                 "AddColumnNamesForExportingCSVFiles", m_addColumnNamesCheckBox->isChecked());
366         else
367             deleteEntry("AddColumnNamesForExportingCSVFiles");
368     }
369     else if (QDialog::Rejected == result) {
370         //nothing to do
371     }
372 
373     KAssistantDialog::done(result);
374 }
375 
slotShowOptionsButtonClicked()376 void KexiCSVExportWizard::slotShowOptionsButtonClicked()
377 {
378     if (m_exportOptionsSection->isVisible()) {
379         m_showOptionsButton->setText(xi18n("Show Options &gt;&gt;"));
380         m_exportOptionsSection->hide();
381         m_alwaysUseCheckBox->hide();
382         m_defaultsBtn->hide();
383     } else {
384         m_showOptionsButton->setText(xi18n("Hide Options &lt;&lt;"));
385         m_exportOptionsSection->show();
386         m_alwaysUseCheckBox->show();
387         m_defaultsBtn->show();
388     }
389 }
390 
slotDefaultsButtonClicked()391 void KexiCSVExportWizard::slotDefaultsButtonClicked()
392 {
393     m_delimiterWidget->setDelimiter(defaultDelimiter());
394     m_textQuote->setTextQuote(defaultTextQuote());
395     m_addColumnNamesCheckBox->setChecked(true);
396     m_characterEncodingCombo->selectDefaultEncoding();
397 }
398 
399 
convertKey(const char * key,KexiCSVExport::Mode mode)400 static QString convertKey(const char *key, KexiCSVExport::Mode mode)
401 {
402     QString _key(QString::fromLatin1(key));
403     if (mode == KexiCSVExport::Clipboard) {
404         _key.replace("Exporting", "Copying");
405         _key.replace("Export", "Copy");
406         _key.replace("CSVFiles", "CSVToClipboard");
407     }
408     return _key;
409 }
410 
readBoolEntry(const char * key,bool defaultValue)411 bool KexiCSVExportWizard::readBoolEntry(const char *key, bool defaultValue)
412 {
413     return m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue);
414 }
415 
readEntry(const char * key,const QString & defaultValue)416 QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue)
417 {
418     return m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue);
419 }
420 
writeEntry(const char * key,const QString & value)421 void KexiCSVExportWizard::writeEntry(const char *key, const QString& value)
422 {
423     m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value);
424 }
425 
writeEntry(const char * key,bool value)426 void KexiCSVExportWizard::writeEntry(const char *key, bool value)
427 {
428     m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value);
429 }
430 
deleteEntry(const char * key)431 void KexiCSVExportWizard::deleteEntry(const char *key)
432 {
433     m_importExportGroup.deleteEntry(convertKey(key, m_options.mode));
434 }
435 
defaultDelimiter() const436 QString KexiCSVExportWizard::defaultDelimiter() const
437 {
438     if (m_options.mode == KexiCSVExport::Clipboard) {
439         if (!m_options.forceDelimiter.isEmpty())
440             return m_options.forceDelimiter;
441         else
442             return KEXICSV_DEFAULT_CLIPBOARD_DELIMITER;
443     }
444     return KEXICSV_DEFAULT_FILE_DELIMITER;
445 }
446 
defaultTextQuote() const447 QString KexiCSVExportWizard::defaultTextQuote() const
448 {
449     if (m_options.mode == KexiCSVExport::Clipboard)
450         return KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE;
451     return KEXICSV_DEFAULT_FILE_TEXT_QUOTE;
452 }
453