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 >>"));
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 >>"));
380 m_exportOptionsSection->hide();
381 m_alwaysUseCheckBox->hide();
382 m_defaultsBtn->hide();
383 } else {
384 m_showOptionsButton->setText(xi18n("Hide Options <<"));
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