From 8362bf63dea22bbf6736609b0f49c152f975eb63 Mon Sep 17 00:00:00 2001 From: tpearson Date: Wed, 20 Jan 2010 01:29:50 +0000 Subject: Added old abandoned KDE3 version of koffice git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kexi/plugins/importexport/Makefile.am | 1 + kexi/plugins/importexport/csv/Makefile.am | 21 + .../csv/kexicsv_importexporthandler.desktop | 51 + .../importexport/csv/kexicsv_importexportpart.cpp | 87 + .../importexport/csv/kexicsv_importexportpart.h | 44 + kexi/plugins/importexport/csv/kexicsvexport.cpp | 271 ++++ kexi/plugins/importexport/csv/kexicsvexport.h | 58 + .../importexport/csv/kexicsvexportwizard.cpp | 431 +++++ .../plugins/importexport/csv/kexicsvexportwizard.h | 113 ++ .../importexport/csv/kexicsvimportdialog.cpp | 1662 ++++++++++++++++++++ .../plugins/importexport/csv/kexicsvimportdialog.h | 231 +++ .../importexport/csv/kexicsvimportoptionsdlg.cpp | 140 ++ .../importexport/csv/kexicsvimportoptionsdlg.h | 62 + kexi/plugins/importexport/csv/kexicsvwidgets.cpp | 233 +++ kexi/plugins/importexport/csv/kexicsvwidgets.h | 116 ++ 15 files changed, 3521 insertions(+) create mode 100644 kexi/plugins/importexport/Makefile.am create mode 100644 kexi/plugins/importexport/csv/Makefile.am create mode 100644 kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop create mode 100644 kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsv_importexportpart.h create mode 100644 kexi/plugins/importexport/csv/kexicsvexport.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsvexport.h create mode 100644 kexi/plugins/importexport/csv/kexicsvexportwizard.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsvexportwizard.h create mode 100644 kexi/plugins/importexport/csv/kexicsvimportdialog.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsvimportdialog.h create mode 100644 kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h create mode 100644 kexi/plugins/importexport/csv/kexicsvwidgets.cpp create mode 100644 kexi/plugins/importexport/csv/kexicsvwidgets.h (limited to 'kexi/plugins/importexport') diff --git a/kexi/plugins/importexport/Makefile.am b/kexi/plugins/importexport/Makefile.am new file mode 100644 index 00000000..02d8b733 --- /dev/null +++ b/kexi/plugins/importexport/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = csv diff --git a/kexi/plugins/importexport/csv/Makefile.am b/kexi/plugins/importexport/csv/Makefile.am new file mode 100644 index 00000000..7ad16495 --- /dev/null +++ b/kexi/plugins/importexport/csv/Makefile.am @@ -0,0 +1,21 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_csv_importexport.la + +kexihandler_csv_importexport_la_SOURCES = kexicsv_importexportpart.cpp kexicsvimportdialog.cpp \ + kexicsvimportoptionsdlg.cpp kexicsvwidgets.cpp kexicsvexport.cpp kexicsvexportwizard.cpp + +kexihandler_csv_importexport_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_csv_importexport_la_LIBADD = ../../../core/libkexicore.la \ + ../../../migration/libkeximigrate.la + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/migration \ + -I$(top_srcdir)/kexi/kexiDB $(all_includes) + +METASOURCES = AUTO + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexicsv_importexporthandler.desktop + +include ../../Makefile.common diff --git a/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop new file mode 100644 index 00000000..1c2a383a --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +Name=Kexi CSV Data Import/Export Plugin +Name[bg]=Приставка за импортиране/експортиране от CSV в Kexi +Name[ca]=Connector d'importació/Exportació de dades CVS per a Kexi +Name[da]=Kexi CSV data-import/eksport plugin +Name[de]=Kexi CSV-Daten Import-/Export-Modul +Name[el]=Πρόσθετο εισαγωγής/εξαγωγής CSV δεδομένων του Kexi +Name[eo]=Kexi CSV-datuma import-eksport-kromaĵo +Name[es]=Complemento de Kexi para importar y exportar datos CSV +Name[et]=Kexi CSV-andmete impordi/ekspordifilter +Name[fa]=وصلۀ واردات/صادرات دادۀ Kexi CSV +Name[fr]=Module d'importation / exportation de données CSV de Kexi +Name[fy]=Kexi ymport/eksport Plugin foar CSV-gegevens +Name[gl]=Importación de Datos CSV de Kexi +Name[he]=תוסף של Kexi ליבוא/יצוא של מידע מסוג CSV +Name[hu]=Kexi CSV adatimportáló és -exportáló modul +Name[is]=Kexi CSV gagna inn/útflutnings íforrit +Name[it]=Importazione ed esportazione di dati CSV di Kexi +Name[ja]=Kexi CSV データ インポート/エクスポートプラグイン +Name[km]=កម្មវិធី​ជំនួយ​ក្នុង​ការ​នាំចេញ និង​នាំចូល​ទិន្នន័យ CSV សម្រាប់ Kexi +Name[lv]=Kexi CSV datu importa/eksporta spraudnis +Name[nb]=CSV-data import/eksportfilter for Kexi +Name[nds]=CSV-Datenimport-/exportmoduul för Kexi +Name[ne]=केक्सी CSV डेटा आयात/निर्यात प्लगइन +Name[nl]=Kexi import/exportplugin voor CSV-gegevens +Name[pl]=Wtyczka importu/eksportu danych CSV dla Kexi +Name[pt]=Importação de Dados CSV do Kexi +Name[pt_BR]=Plugin de Importação/Exportação de Dados CSV do Kexi +Name[ru]=Модуль импорта/экспорта CSV (значения через запятую) для Kexi +Name[se]=Kexi CSV-dáhta sisa-/olggosfievrridan lassemoduvla +Name[sk]=Modul Kexi pre import a export CSV dát +Name[sl]=Vstavek za uvoz/izvoz podatkov CVS za Kexi +Name[sr]=Kexi-јев прикључак за увоз и извоз из CSV-а +Name[sr@Latn]=Kexi-jev priključak za uvoz i izvoz iz CSV-a +Name[sv]=Kexi insticksprogram för import/export av CSV-data +Name[uk]=Втулок імпорту/експорту CSV-даних для Kexi +Name[uz]=Kexi CSV maʼlumot import/eksport plagini +Name[uz@cyrillic]=Kexi CSV маълумот импорт/экспорт плагини +Name[zh_CN]=Kexi CSV 数据导入/导出插件 +Name[zh_TW]=Kexi CSV 資料匯入/匯出外掛程式 + +X-KDE-Library=kexihandler_csv_importexport +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=csv_importexport +X-Kexi-GroupIcon=csv_importexport +X-Kexi-ItemIcon=csv_importexport +X-Kexi-NoObject=true diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp new file mode 100644 index 00000000..caa8640d --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicsv_importexportpart.h" +#include "kexicsvimportdialog.h" +#include "kexicsvexportwizard.h" +#include +#include +#include + +#include + +KexiCSVImportExportPart::KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args) + : KexiInternalPart(parent, name, args) +{ +} + +KexiCSVImportExportPart::~KexiCSVImportExportPart() +{ +} + +QWidget *KexiCSVImportExportPart::createWidget(const char* widgetClass, KexiMainWindow* mainWin, + QWidget *parent, const char *objName, QMap* args ) +{ + if (0==qstrcmp(widgetClass, "KexiCSVImportDialog")) { + KexiCSVImportDialog::Mode mode = (args && (*args)["sourceType"]=="file") + ? KexiCSVImportDialog::File : KexiCSVImportDialog::Clipboard; + KexiCSVImportDialog *dlg = new KexiCSVImportDialog( mode, mainWin, parent, objName ); + m_cancelled = dlg->cancelled(); + if (m_cancelled) { + delete dlg; + return 0; + } + return dlg; + } + else if (0==qstrcmp(widgetClass, "KexiCSVExportWizard")) { + if (!args) + return 0; + KexiCSVExport::Options options; + if (!options.assign( *args )) + return 0; + KexiCSVExportWizard *dlg = new KexiCSVExportWizard( options, mainWin, parent, objName); + m_cancelled = dlg->cancelled(); + if (m_cancelled) { + delete dlg; + return 0; + } + return dlg; + } + return 0; +} + +bool KexiCSVImportExportPart::executeCommand(KexiMainWindow* mainWin, const char* commandName, + QMap* args) +{ + if (0==qstrcmp(commandName, "KexiCSVExport")) { + KexiCSVExport::Options options; + if (!options.assign( *args )) + return false; + KexiDB::TableOrQuerySchema tableOrQuery( + mainWin->project()->dbConnection(), options.itemId); + QTextStream *stream = 0; + if (args->contains("textStream")) + stream = KexiUtils::stringToPtr( (*args)["textStream"] ); + return KexiCSVExport::exportData(tableOrQuery, options, -1, stream); + } + return false; +} + +K_EXPORT_COMPONENT_FACTORY( kexihandler_csv_importexport, + KGenericFactory("kexihandler_csv_importexport") ) diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.h b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h new file mode 100644 index 00000000..8ee8e8cd --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_MIGRATION_PART_H +#define KEXI_MIGRATION_PART_H + +#include +#include "kexicsvexportwizard.h" + +/*! Internal part for CSV data import/export dialogs. */ +class KexiCSVImportExportPart : public KexiInternalPart +{ + public: + KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiCSVImportExportPart(); + + /*! Reimplemented to return wizard object. */ + virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin, + QWidget *parent, const char *objName = 0, QMap* args = 0); + + /*! Reimplemented to execute a command \a commandName (nonvisual). The result are put into the \a args. */ + virtual bool executeCommand(KexiMainWindow* mainWin, const char* commandName, + QMap* args = 0); + + protected: +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvexport.cpp b/kexi/plugins/importexport/csv/kexicsvexport.cpp new file mode 100644 index 00000000..b83f85a7 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexport.cpp @@ -0,0 +1,271 @@ +/* This file is part of the KDE project + Copyright (C) 2005,2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicsvexport.h" +#include "kexicsvwidgets.h" +#include
+#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace KexiCSVExport; + +Options::Options() + : mode(File), itemId(0), addColumnNames(true) +{ +} + +bool Options::assign( QMap& args ) +{ + mode = (args["destinationType"]=="file") + ? KexiCSVExport::File : KexiCSVExport::Clipboard; + + if (args.contains("delimiter")) + delimiter = args["delimiter"]; + else + delimiter = (mode==File) ? KEXICSV_DEFAULT_FILE_DELIMITER : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; + + if (args.contains("textQuote")) + textQuote = args["textQuote"]; + else + textQuote = (mode==File) ? KEXICSV_DEFAULT_FILE_TEXT_QUOTE : KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; + + bool ok; + itemId = args["itemId"].toInt(&ok); + if (!ok || itemId<=0) + return false; + if (args.contains("forceDelimiter")) + forceDelimiter = args["forceDelimiter"]; + if (args.contains("addColumnNames")) + addColumnNames = (args["addColumnNames"]=="1"); + return true; +} + +//------------------------------------ + +bool KexiCSVExport::exportData(KexiDB::TableOrQuerySchema& tableOrQuery, + const Options& options, int rowCount, QTextStream *predefinedTextStream) +{ + KexiDB::Connection* conn = tableOrQuery.connection(); + if (!conn) + return false; + + if (rowCount == -1) + rowCount = KexiDB::rowCount(tableOrQuery); + if (rowCount == -1) + return false; + +//! @todo move this to non-GUI location so it can be also used via command line +//! @todo add a "finish" page with a progressbar. +//! @todo look at rowCount whether the data is really large; +//! if so: avoid copying to clipboard (or ask user) because of system memory + +//! @todo OPTIMIZATION: use fieldsExpanded(true /*UNIQUE*/) +//! @todo OPTIMIZATION? (avoid multiple data retrieving) look for already fetched data within KexiProject.. + + KexiDB::QuerySchema* query = tableOrQuery.query(); + if (!query) + query = tableOrQuery.table()->query(); + + KexiDB::QueryColumnInfo::Vector fields( query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields ) ); + QString buffer; + + KSaveFile *kSaveFile = 0; + QTextStream *stream = 0; + + const bool copyToClipboard = options.mode==Clipboard; + if (copyToClipboard) { +//! @todo (during exporting): enlarge bufSize by factor of 2 when it became too small + uint bufSize = QMIN((rowCount<0 ? 10 : rowCount) * fields.count() * 20, 128000); + buffer.reserve( bufSize ); + if (buffer.capacity() < bufSize) { + kdWarning() << "KexiCSVExportWizard::exportData() cannot allocate memory for " << bufSize + << " characters" << endl; + return false; + } + } + else { + if (predefinedTextStream) { + stream = predefinedTextStream; + } + else { + if (options.fileName.isEmpty()) {//sanity + kdWarning() << "KexiCSVExportWizard::exportData(): fname is empty" << endl; + return false; + } + kSaveFile = new KSaveFile(options.fileName); + if (0 == kSaveFile->status()) + stream = kSaveFile->textStream(); + if (0 != kSaveFile->status() || !stream) {//sanity + kdWarning() << "KexiCSVExportWizard::exportData(): status != 0 or stream == 0" << endl; + delete kSaveFile; + return false; + } + } + } + +//! @todo escape strings + +#define _ERR \ + delete [] isText; \ + if (kSaveFile) { kSaveFile->abort(); delete kSaveFile; } \ + return false + +#define APPEND(what) \ + if (copyToClipboard) buffer.append(what); else (*stream) << (what) + +// line endings should be as in RFC 4180 +#define CSV_EOLN "\r\n" + + // 0. Cache information + const uint fieldsCount = query->fieldsExpanded().count(); //real fields count without internals + const QCString delimiter( options.delimiter.left(1).latin1() ); + const bool hasTextQuote = !options.textQuote.isEmpty(); + const QString textQuote( options.textQuote.left(1) ); + const QCString escapedTextQuote( (textQuote + textQuote).latin1() ); //ok? + //cache for faster checks + bool *isText = new bool[fieldsCount]; + bool *isDateTime = new bool[fieldsCount]; + bool *isTime = new bool[fieldsCount]; + bool *isBLOB = new bool[fieldsCount]; + uint *visibleFieldIndex = new uint[fieldsCount]; +// bool isInteger[fieldsCount]; //cache for faster checks +// bool isFloatingPoint[fieldsCount]; //cache for faster checks + for (uint i=0; iindexForVisibleLookupValue(); + if (-1 != indexForVisibleLookupValue) { + ci = query->expandedOrInternalField( indexForVisibleLookupValue ); + visibleFieldIndex[i] = indexForVisibleLookupValue; + } + else { + ci = fields[i]; + visibleFieldIndex[i] = i; + } + + isText[i] = ci->field->isTextType(); + isDateTime[i] = ci->field->type()==KexiDB::Field::DateTime; + isTime[i] = ci->field->type()==KexiDB::Field::Time; + isBLOB[i] = ci->field->type()==KexiDB::Field::BLOB; +// isInteger[i] = fields[i]->field->isIntegerType() +// || fields[i]->field->type()==KexiDB::Field::Boolean; +// isFloatingPoint[i] = fields[i]->field->isFPNumericType(); + } + + // 1. Output column names + if (options.addColumnNames) { + for (uint i=0; i0) + APPEND( delimiter ); + if (hasTextQuote){ + APPEND( textQuote + fields[i]->captionOrAliasOrName().replace(textQuote, escapedTextQuote) + textQuote ); + } + else { + APPEND( fields[i]->captionOrAliasOrName() ); + } + } + APPEND(CSV_EOLN); + } + + KexiGUIMessageHandler handler; + KexiDB::Cursor *cursor = conn->executeQuery(*query); + if (!cursor) { + handler.showErrorMessage(conn); + _ERR; + } + for (cursor->moveFirst(); !cursor->eof() && !cursor->error(); cursor->moveNext()) { + const uint realFieldCount = QMIN(cursor->fieldCount(), fieldsCount); + for (uint i=0; i0) + APPEND( delimiter ); + if (cursor->value(real_i).isNull()) + continue; + if (isText[real_i]) { + if (hasTextQuote) + APPEND( textQuote + QString(cursor->value(real_i).toString()).replace(textQuote, escapedTextQuote) + textQuote ); + else + APPEND( cursor->value(real_i).toString() ); + } + else if (isDateTime[real_i]) { //avoid "T" in ISO DateTime + APPEND( cursor->value(real_i).toDateTime().date().toString(Qt::ISODate)+" " + + cursor->value(real_i).toDateTime().time().toString(Qt::ISODate) ); + } + else if (isTime[real_i]) { //time is temporarily stored as null date + time... + APPEND( cursor->value(real_i).toTime().toString(Qt::ISODate) ); + } + else if (isBLOB[real_i]) { //BLOB is escaped in a special way + if (hasTextQuote) +//! @todo add options to suppport other types from KexiDB::BLOBEscapingType enum... + APPEND( textQuote + KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) + textQuote ); + else + APPEND( KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) ); + } + else {//other types + APPEND( cursor->value(real_i).toString() ); + } + } + APPEND(CSV_EOLN); + } + + if (copyToClipboard) + buffer.squeeze(); + + if (!conn->deleteCursor(cursor)) { + handler.showErrorMessage(conn); + _ERR; + } + + if (copyToClipboard) + kapp->clipboard()->setText(buffer, QClipboard::Clipboard); + + delete [] isText; + delete [] isDateTime; + delete [] isTime; + delete [] isBLOB; + delete [] visibleFieldIndex; + + if (kSaveFile) { + if (!kSaveFile->close()) { + kdWarning() << "KexiCSVExportWizard::exportData(): error close(); status == " + << kSaveFile->status() << endl; + } + delete kSaveFile; + } + return true; +} diff --git a/kexi/plugins/importexport/csv/kexicsvexport.h b/kexi/plugins/importexport/csv/kexicsvexport.h new file mode 100644 index 00000000..7c8138fe --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexport.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2005,2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_CSVEXPORT_H +#define KEXI_CSVEXPORT_H + +#include + +namespace KexiCSVExport +{ + +//! Exporting mode: a file or clipboard +enum Mode { Clipboard, File }; + +//! Options used in KexiCSVExportWizard contructor. +class Options { + public: + Options(); + + //! Assigns \a args. \return false on failure. + bool assign( QMap& args ); + + Mode mode; + int itemId; //!< Table or query ID + QString fileName; + QString delimiter; + QString forceDelimiter; //!< Used for "clipboard" mode + QString textQuote; + bool addColumnNames : 1; +}; + +/*! Exports data. \return false on failure. + @param options options for the export + @param rowCount row count of the input data or -1 if the row cound has not yet been computed + @param predefinedTextStream text stream that should be used instead of writing to a file +*/ +bool exportData(KexiDB::TableOrQuerySchema& tableOrQuery, const Options& options, int rowCount = -1, + QTextStream *predefinedTextStream = 0); + +} + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp new file mode 100644 index 00000000..11c0cff0 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp @@ -0,0 +1,431 @@ +/* This file is part of the KDE project + Copyright (C) 2005,2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicsvexportwizard.h" +#include "kexicsvwidgets.h" +#include
+#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +KexiCSVExportWizard::KexiCSVExportWizard( const KexiCSVExport::Options& options, + KexiMainWindow* mainWin, QWidget * parent, const char * name ) + : KWizard(parent, name) + , m_options(options) +// , m_mode(mode) +// , m_itemId(itemId) + , m_mainWin(mainWin) + , m_fileSavePage(0) + , m_defaultsBtn(0) + , m_rowCount(-1) + , m_rowCountDetermined(false) + , m_cancelled(false) +{ + if (m_options.mode==KexiCSVExport::Clipboard) { + finishButton()->setText(i18n("Copy")); + backButton()->hide(); + } + else { + finishButton()->setText(i18n("Export")); + } + helpButton()->hide(); + + QString infoLblFromText; + KexiGUIMessageHandler msgh(this); + m_tableOrQuery = new KexiDB::TableOrQuerySchema( + m_mainWin->project()->dbConnection(), m_options.itemId); + if (m_tableOrQuery->table()) { + if (m_options.mode==KexiCSVExport::Clipboard) { + setCaption(i18n("Copy Data From Table to Clipboard")); + infoLblFromText = i18n("Copying data from table:"); + } + else { + setCaption(i18n("Export Data From Table to CSV File")); + infoLblFromText = i18n("Exporting data from table:"); + } + } + else if (m_tableOrQuery->query()) { + if (m_options.mode==KexiCSVExport::Clipboard) { + setCaption(i18n("Copy Data From Query to Clipboard")); + infoLblFromText = i18n("Copying data from table:"); + } + else { + setCaption(i18n("Export Data From Query to CSV File")); + infoLblFromText = i18n("Exporting data from query:"); + } + } + else { + msgh.showErrorMessage(m_mainWin->project()->dbConnection(), + i18n("Could not open data for exporting.")); + m_cancelled = true; + return; + } + // OK, source data found. + + // Setup pages + + // 1. File Save Page + if (m_options.mode==KexiCSVExport::File) { + m_fileSavePage = new KexiStartupFileDialog( + ":CSVImportExport", //startDir + KexiStartupFileDialog::Custom | KexiStartupFileDialog::SavingFileBasedDB, + this, "m_fileSavePage"); + m_fileSavePage->setMinimumHeight(kapp->desktop()->height()/2); + m_fileSavePage->setAdditionalFilters( csvMimeTypes() ); + m_fileSavePage->setDefaultExtension("csv"); + m_fileSavePage->setLocationText( KexiUtils::stringToFileName(m_tableOrQuery->captionOrName()) ); + connect(m_fileSavePage, SIGNAL(rejected()), this, SLOT(reject())); + addPage(m_fileSavePage, i18n("Enter Name of File You Want to Save Data To")); + } + + // 2. Export options + m_exportOptionsPage = new QWidget(this, "m_exportOptionsPage"); + QGridLayout *exportOptionsLyr = new QGridLayout( m_exportOptionsPage, 6, 3, + KDialogBase::marginHint(), KDialogBase::spacingHint(), "exportOptionsLyr"); + m_infoLblFrom = new KexiCSVInfoLabel( infoLblFromText, m_exportOptionsPage ); + KexiPart::Info *partInfo = Kexi::partManager().infoForMimeType( + m_tableOrQuery->table() ? "kexi/table" : "kexi/query"); + if (partInfo) + m_infoLblFrom->setIcon(partInfo->itemIcon()); + m_infoLblFrom->separator()->hide(); + exportOptionsLyr->addMultiCellWidget(m_infoLblFrom, 0, 0, 0, 2); + + m_infoLblTo = new KexiCSVInfoLabel( + (m_options.mode==KexiCSVExport::File) ? i18n("To CSV file:") : i18n("To clipboard:"), + m_exportOptionsPage + ); + if (m_options.mode==KexiCSVExport::Clipboard) + m_infoLblTo->setIcon("editpaste"); + exportOptionsLyr->addMultiCellWidget(m_infoLblTo, 1, 1, 0, 2); + + m_showOptionsButton = new KPushButton(KGuiItem(i18n("Show Options >>"), "configure"), + m_exportOptionsPage); + connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked())); + exportOptionsLyr->addMultiCellWidget(m_showOptionsButton, 2, 2, 0, 0); + m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + // - + m_exportOptionsSection = new QGroupBox(1, Vertical, i18n("Options"), m_exportOptionsPage, + "m_exportOptionsSection"); + m_exportOptionsSection->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + exportOptionsLyr->addMultiCellWidget(m_exportOptionsSection, 3, 3, 0, 1); + QWidget *exportOptionsSectionWidget + = new QWidget(m_exportOptionsSection, "exportOptionsSectionWidget"); + QGridLayout *exportOptionsSectionLyr = new QGridLayout( exportOptionsSectionWidget, 5, 2, + 0, KDialogBase::spacingHint(), "exportOptionsLyr"); + + // -delimiter + m_delimiterWidget = new KexiCSVDelimiterWidget(false, //!lineEditOnBottom + exportOptionsSectionWidget); + m_delimiterWidget->setDelimiter(defaultDelimiter()); + exportOptionsSectionLyr->addWidget( m_delimiterWidget, 0, 1 ); + QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( delimiterLabel, 0, 0 ); + + // -text quote + QWidget *textQuoteWidget = new QWidget(exportOptionsSectionWidget); + QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget); + exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1); + m_textQuote = new KexiCSVTextQuoteComboBox( textQuoteWidget ); + m_textQuote->setTextQuote(defaultTextQuote()); + textQuoteLyr->addWidget( m_textQuote ); + textQuoteLyr->addStretch(0); + QLabel *textQuoteLabel = new QLabel(m_textQuote, i18n("Text quote:"), exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( textQuoteLabel, 1, 0 ); + + // - character encoding + m_characterEncodingCombo = new KexiCharacterEncodingComboBox( exportOptionsSectionWidget ); + m_characterEncodingCombo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + exportOptionsSectionLyr->addWidget( m_characterEncodingCombo, 2, 1 ); + QLabel *characterEncodingLabel = new QLabel(m_characterEncodingCombo, i18n("Text encoding:"), + exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( characterEncodingLabel, 2, 0 ); + + // - checkboxes + m_addColumnNamesCheckBox = new QCheckBox(i18n("Add column names as the first row"), + exportOptionsSectionWidget); + m_addColumnNamesCheckBox->setChecked(true); + exportOptionsSectionLyr->addWidget( m_addColumnNamesCheckBox, 3, 1 ); +//! @todo 1.1: for copying use "Always use above options for copying" string + m_alwaysUseCheckBox = new QCheckBox(i18n("Always use above options for exporting"), + m_exportOptionsPage); + exportOptionsLyr->addMultiCellWidget(m_alwaysUseCheckBox, 4, 4, 0, 1); +// exportOptionsSectionLyr->addWidget( m_alwaysUseCheckBox, 4, 1 ); + m_exportOptionsSection->hide(); + m_alwaysUseCheckBox->hide(); + // - + +// exportOptionsLyr->setColStretch(3, 1); + exportOptionsLyr->addMultiCell( + new QSpacerItem( 0, 0, QSizePolicy::Preferred, QSizePolicy::MinimumExpanding), 5, 5, 0, 1 ); + +// addPage(m_exportOptionsPage, i18n("Set Export Options")); + addPage(m_exportOptionsPage, m_options.mode==KexiCSVExport::Clipboard ? i18n("Copying") : i18n("Exporting")); + setFinishEnabled(m_exportOptionsPage, true); + + // load settings + kapp->config()->setGroup("ImportExport"); + if (m_options.mode!=KexiCSVExport::Clipboard && readBoolEntry("ShowOptionsInCSVExportDialog", false)) { + show(); + slotShowOptionsButtonClicked(); + } + if (readBoolEntry("StoreOptionsForCSVExportDialog", false)) { + // load defaults: + m_alwaysUseCheckBox->setChecked(true); + QString s = readEntry("DefaultDelimiterForExportingCSVFiles", defaultDelimiter()); + if (!s.isEmpty()) + m_delimiterWidget->setDelimiter(s); + s = readEntry("DefaultTextQuoteForExportingCSVFiles", defaultTextQuote()); + m_textQuote->setTextQuote(s); //will be invaliudated here, so not a problem + s = readEntry("DefaultEncodingForExportingCSVFiles"); + if (!s.isEmpty()) + m_characterEncodingCombo->setSelectedEncoding(s); + m_addColumnNamesCheckBox->setChecked( + readBoolEntry("AddColumnNamesForExportingCSVFiles", true) ); + } + + updateGeometry(); + + // -keep widths equal on page #2: + int width = QMAX( m_infoLblFrom->leftLabel()->sizeHint().width(), + m_infoLblTo->leftLabel()->sizeHint().width()); + m_infoLblFrom->leftLabel()->setFixedWidth(width); + m_infoLblTo->leftLabel()->setFixedWidth(width); +} + +KexiCSVExportWizard::~KexiCSVExportWizard() +{ + delete m_tableOrQuery; +} + +bool KexiCSVExportWizard::cancelled() const +{ + return m_cancelled; +} + +void KexiCSVExportWizard::showPage ( QWidget * page ) +{ + if (page == m_fileSavePage) { + m_fileSavePage->setFocus(); + } + else if (page==m_exportOptionsPage) { + if (m_options.mode==KexiCSVExport::File) + m_infoLblTo->setFileName( m_fileSavePage->currentFileName() ); + QString text = m_tableOrQuery->captionOrName(); + if (!m_rowCountDetermined) { + //do this costly operation only once + m_rowCount = KexiDB::rowCount(*m_tableOrQuery); + m_rowCountDetermined = true; + } + int columns = KexiDB::fieldCount(*m_tableOrQuery); + text += "\n"; + if (m_rowCount>0) + text += i18n("(rows: %1, columns: %2)").arg(m_rowCount).arg(columns); + else + text += i18n("(columns: %1)").arg(columns); + m_infoLblFrom->setLabelText(text); + QFontMetrics fm(m_infoLblFrom->fileNameLabel()->font()); + m_infoLblFrom->fileNameLabel()->setFixedHeight( fm.height() * 2 + fm.lineSpacing() ); + if (m_defaultsBtn) + m_defaultsBtn->show(); + } + + if (page!=m_exportOptionsPage) { + if (m_defaultsBtn) + m_defaultsBtn->hide(); + } + + KWizard::showPage(page); +} + +void KexiCSVExportWizard::next() +{ + if (currentPage() == m_fileSavePage) { + if (!m_fileSavePage->checkFileName()) + return; + KWizard::next(); + finishButton()->setFocus(); + return; + } + KWizard::next(); +} + +void KexiCSVExportWizard::done(int result) +{ + if (QDialog::Accepted == result) { + if (m_fileSavePage) + m_options.fileName = m_fileSavePage->currentFileName(); + m_options.delimiter = m_delimiterWidget->delimiter(); + m_options.textQuote = m_textQuote->textQuote(); + m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked(); + if (!KexiCSVExport::exportData(*m_tableOrQuery, m_options)) + return; + } + else if (QDialog::Rejected == result) { + //nothing to do + } + + //store options + kapp->config()->setGroup("ImportExport"); + if (m_options.mode!=KexiCSVExport::Clipboard) + writeEntry("ShowOptionsInCSVExportDialog", m_exportOptionsSection->isVisible()); + const bool store = m_alwaysUseCheckBox->isChecked(); + writeEntry("StoreOptionsForCSVExportDialog", store); + // only save if an option differs from default + + if (store && m_delimiterWidget->delimiter()!=defaultDelimiter()) + writeEntry("DefaultDelimiterForExportingCSVFiles", m_delimiterWidget->delimiter()); + else + deleteEntry("DefaultDelimiterForExportingCSVFiles"); + if (store && m_textQuote->textQuote()!=defaultTextQuote()) + writeEntry("DefaultTextQuoteForExportingCSVFiles", m_textQuote->textQuote()); + else + deleteEntry("DefaultTextQuoteForExportingCSVFiles"); + if (store && !m_characterEncodingCombo->defaultEncodingSelected()) + writeEntry("DefaultEncodingForExportingCSVFiles", m_characterEncodingCombo->selectedEncoding()); + else + deleteEntry("DefaultEncodingForExportingCSVFiles"); + if (store && !m_addColumnNamesCheckBox->isChecked()) + writeEntry("AddColumnNamesForExportingCSVFiles", m_addColumnNamesCheckBox->isChecked()); + else + deleteEntry("AddColumnNamesForExportingCSVFiles"); + + KWizard::done(result); +} + +void KexiCSVExportWizard::slotShowOptionsButtonClicked() +{ + if (m_exportOptionsSection->isVisible()) { + m_showOptionsButton->setText(i18n("Show Options >>")); + m_exportOptionsSection->hide(); + m_alwaysUseCheckBox->hide(); + if (m_defaultsBtn) + m_defaultsBtn->hide(); + } + else { + m_showOptionsButton->setText(i18n("Hide Options <<")); + m_exportOptionsSection->show(); + m_alwaysUseCheckBox->show(); + if (m_defaultsBtn) + m_defaultsBtn->show(); + } +} + +void KexiCSVExportWizard::layOutButtonRow( QHBoxLayout * layout ) +{ + QWizard::layOutButtonRow( layout ); + + //find the last sublayout + QLayout *l = 0; + for (QLayoutIterator lit( layout->iterator() ); lit.current(); ++lit) + l = lit.current()->layout(); + if (dynamic_cast(l)) { + if (!m_defaultsBtn) { + m_defaultsBtn = new KPushButton(i18n("Defaults"), this); + QWidget::setTabOrder(backButton(), m_defaultsBtn); + connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked())); + } + if (!m_exportOptionsSection->isVisible()) + m_defaultsBtn->hide(); + dynamic_cast(l)->insertWidget(0, m_defaultsBtn); + } +} + +void KexiCSVExportWizard::slotDefaultsButtonClicked() +{ + m_delimiterWidget->setDelimiter(defaultDelimiter()); + m_textQuote->setTextQuote(defaultTextQuote()); + m_addColumnNamesCheckBox->setChecked(true); + m_characterEncodingCombo->selectDefaultEncoding(); +} + +static QString convertKey(const char *key, KexiCSVExport::Mode mode) +{ + QString _key(QString::fromLatin1(key)); + if (mode == KexiCSVExport::Clipboard) { + _key.replace("Exporting", "Copying"); + _key.replace("Export", "Copy"); + _key.replace("CSVFiles", "CSVToClipboard"); + } + return _key; +} + +bool KexiCSVExportWizard::readBoolEntry(const char *key, bool defaultValue) +{ + return kapp->config()->readBoolEntry(convertKey(key, m_options.mode), defaultValue); +} + +QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue) +{ + return kapp->config()->readEntry(convertKey(key, m_options.mode), defaultValue); +} + +void KexiCSVExportWizard::writeEntry(const char *key, const QString& value) +{ + kapp->config()->writeEntry(convertKey(key, m_options.mode), value); +} + +void KexiCSVExportWizard::writeEntry(const char *key, bool value) +{ + kapp->config()->writeEntry(convertKey(key, m_options.mode), value); +} + +void KexiCSVExportWizard::deleteEntry(const char *key) +{ + kapp->config()->deleteEntry(convertKey(key, m_options.mode)); +} + +QString KexiCSVExportWizard::defaultDelimiter() const +{ + if (m_options.mode==KexiCSVExport::Clipboard) { + if (!m_options.forceDelimiter.isEmpty()) + return m_options.forceDelimiter; + else + return KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; + } + return KEXICSV_DEFAULT_FILE_DELIMITER; +} + +QString KexiCSVExportWizard::defaultTextQuote() const +{ + if (m_options.mode==KexiCSVExport::Clipboard) + return KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; + return KEXICSV_DEFAULT_FILE_TEXT_QUOTE; +} + +#include "kexicsvexportwizard.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.h b/kexi/plugins/importexport/csv/kexicsvexportwizard.h new file mode 100644 index 00000000..8fdaecd0 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.h @@ -0,0 +1,113 @@ +/* This file is part of the KDE project + Copyright (C) 2005,2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_CSVEXPORTWIZARD_H +#define KEXI_CSVEXPORTWIZARD_H + +#include + +#include "kexicsvexport.h" + +class QCheckBox; +class QGroupBox; +class KPushButton; +class KexiMainWindow; +class KexiStartupFileDialog; +class KexiCSVDelimiterWidget; +class KexiCSVTextQuoteComboBox; +class KexiCSVInfoLabel; +class KexiCharacterEncodingComboBox; +namespace KexiDB { + class TableOrQuerySchema; +} + +/*! @short Kexi CSV export wizard + Supports exporting to a file and to a clipboard. */ +class KexiCSVExportWizard : public KWizard +{ + Q_OBJECT + + public: + KexiCSVExportWizard( const KexiCSVExport::Options& options, KexiMainWindow* mainWin, + QWidget * parent = 0, const char * name = 0 ); + + virtual ~KexiCSVExportWizard(); + + bool cancelled() const; + + virtual void showPage ( QWidget * page ); + + protected slots: + virtual void next(); + virtual void done(int result); + void slotShowOptionsButtonClicked(); + void slotDefaultsButtonClicked(); + + protected: + //! reimplemented to add "Defaults" button on the left hand + virtual void layOutButtonRow( QHBoxLayout * layout ); + + //! \return default delimiter depending on mode. + QString defaultDelimiter() const; + + //! \return default text quote depending on mode. + QString defaultTextQuote() const; + + //! Helper, works like kapp->config()->readBoolEntry(const char*, bool) but if mode is Clipboard, + //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" + //! and "CSVFiles" is replaced with "CSVToClipboard" + //! in \a key, to keep the setting separate. + bool readBoolEntry(const char *key, bool defaultValue); + + //! Helper like \ref readBoolEntry(const char *, bool), but for QString values. + QString readEntry(const char *key, const QString& defaultValue = QString::null); + + //! Helper, works like kapp->config()->writeEntry(const char*,bool) but if mode is Clipboard, + //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" + //! and "CSVFiles" is replaced with "CSVToClipboard" + //! in \a key, to keep the setting separate. + void writeEntry(const char *key, bool value); + + //! Helper like \ref writeEntry(const char *, bool), but for QString values. + void writeEntry(const char *key, const QString& value); + + //! Helper like \ref writeEntry(const char *, bool), but for deleting config entry. + void deleteEntry(const char *key); + + KexiCSVExport::Options m_options; +// Mode m_mode; +// int m_itemId; + KexiMainWindow* m_mainWin; + KexiStartupFileDialog* m_fileSavePage; + QWidget* m_exportOptionsPage; + KPushButton *m_showOptionsButton; + KPushButton *m_defaultsBtn; + QGroupBox* m_exportOptionsSection; + KexiCSVInfoLabel *m_infoLblFrom, *m_infoLblTo; + KexiCSVDelimiterWidget* m_delimiterWidget; + KexiCSVTextQuoteComboBox* m_textQuote; + KexiCharacterEncodingComboBox *m_characterEncodingCombo; + QCheckBox* m_addColumnNamesCheckBox, *m_alwaysUseCheckBox; + KexiDB::TableOrQuerySchema* m_tableOrQuery; + int m_rowCount; //!< Cached row count for a table/query. + bool m_rowCountDetermined : 1; + bool m_cancelled : 1; +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp new file mode 100644 index 00000000..16a9d416 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp @@ -0,0 +1,1662 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek + + This work is based on kspread/dialogs/kspread_dlg_csv.cc + and will be merged back with KOffice libraries. + + Copyright (C) 2002-2003 Norbert Andres + Copyright (C) 2002-2003 Ariya Hidayat + Copyright (C) 2002 Laurent Montel + Copyright (C) 1999 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kexicsvimportdialog.h" +#include "kexicsvwidgets.h" + +#ifdef Q_WS_WIN +#include +#include +#endif + +#if 0 +#include +#include +#include +#include +#include +#endif + +#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/ +#define _TEXT_TYPE 0 +#define _NUMBER_TYPE 1 +#define _DATE_TYPE 2 +#define _TIME_TYPE 3 +#define _DATETIME_TYPE 4 +#define _PK_FLAG 5 + +//extra: +#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty +#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant +#define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable +#define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable +#define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096 + +class KexiCSVImportDialogTable : public QTable +{ +public: + KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 ) + : QTable(parent, name) { + f = font(); + f.setBold(true); + } + virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) { + if (row==0) + p->setFont(f); + else + p->setFont(font()); + QTable::paintCell(p, row, col, cr, selected, cg); + } + virtual void setColumnWidth( int col, int w ) { + //make columns a bit wider + QTable::setColumnWidth( col, w + 16 ); + } + QFont f; +}; + +//! Helper used to temporary disable keyboard and mouse events +void installRecursiveEventFilter(QObject *filter, QObject *object) +{ + object->installEventFilter(filter); + + if (!object->children()) + return; + + QObjectList list = *object->children(); + for(QObject *obj = list.first(); obj; obj = list.next()) + installRecursiveEventFilter(filter, obj); +} + +KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, + QWidget * parent, const char * name +) + : KDialogBase( + KDialogBase::Plain, + i18n( "Import CSV Data File" ) +//! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard + , + (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel, + Ok, + parent, + name ? name : "KexiCSVImportDialog", + true, + false, + KGuiItem( i18n("&Options")) + ), + m_mainWin(mainWin), + m_cancelled( false ), + m_adjustRows( true ), + m_startline( 0 ), + m_textquote( QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0] ), + m_mode(mode), + m_prevSelectedCol(-1), + m_columnsAdjusted(false), + m_1stRowForFieldNamesDetected(false), + m_firstFillTableCall(true), + m_blockUserEvents(false), + m_primaryKeyColumn(-1), + m_dialogCancelled(false), + m_conn(0), + m_destinationTableSchema(0), + m_allRowsLoadedInPreview(false), + m_stoppedAt_MAX_BYTES_TO_PREVIEW(false) +{ + setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu); + hide(); + setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON)); + + m_typeNames.resize(5); + m_typeNames[0] = i18n("text"); + m_typeNames[1] = i18n("number"); + m_typeNames[2] = i18n("date"); + m_typeNames[3] = i18n("time"); + m_typeNames[4] = i18n("date/time"); + + kapp->config()->setGroup("ImportExport"); + m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW); + m_maximumBytesForPreview = kapp->config()->readNumEntry("MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW); + + m_pkIcon = SmallIcon("key"); + + m_uniquenessTest.setAutoDelete(true); + + setIcon(DesktopIcon(_IMPORT_ICON)); + setSizeGripEnabled( TRUE ); + +// m_encoding = QString::fromLatin1(KGlobal::locale()->encoding()); +// m_stripWhiteSpaceInTextValuesChecked = true; + m_file = 0; + m_inputStream = 0; + + QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr"); + + m_infoLbl = new KexiCSVInfoLabel( + m_mode==File ? i18n("Preview of data from file:") + : i18n("Preview of data from clipboard:"), + plainPage() + ); + lyr->addWidget( m_infoLbl ); + + QWidget* page = new QFrame( plainPage(), "page" ); + QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr"); + lyr->addWidget( page ); + + // Delimiter: comma, semicolon, tab, space, other + m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page); + m_detectDelimiter = true; + glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 ); + + QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page); + delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 ); + + // Format: number, text, currency, + m_formatComboText = i18n( "Format for column %1:" ); + m_formatCombo = new KComboBox(page, "m_formatCombo"); + m_formatCombo->insertItem(i18n("Text")); + m_formatCombo->insertItem(i18n("Number")); + m_formatCombo->insertItem(i18n("Date")); + m_formatCombo->insertItem(i18n("Time")); + m_formatCombo->insertItem(i18n("Date/Time")); + glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 ); + + m_formatLabel = new QLabel(m_formatCombo, "", page); + m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( m_formatLabel, 0, 1 ); + + m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" ); + glyr->addWidget( m_primaryKeyField, 2, 1 ); + connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool))); + + m_comboQuote = new KexiCSVTextQuoteComboBox( page ); + glyr->addWidget( m_comboQuote, 1, 2 ); + + TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" ); + TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); + TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( TextLabel2, 0, 2 ); + + m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" ); + m_startAtLineSpinBox->setMinValue(1); + m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888")); + glyr->addWidget( m_startAtLineSpinBox, 1, 3 ); + + m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "", + page, "TextLabel3" ); + m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); + m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( m_startAtLineLabel, 0, 3 ); + + QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred ); + glyr->addItem( spacer_2, 0, 4 ); + + m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" ); + m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) ); + glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 ); + + m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" ); + m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) ); + glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 ); + + m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" ); + lyr->addWidget( m_table ); + + m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) ); + m_table->setNumRows( 0 ); + m_table->setNumCols( 0 ); + +/** @todo reuse Clipboard too! */ +/* +if ( m_mode == Clipboard ) + { + setCaption( i18n( "Inserting From Clipboard" ) ); + QMimeSource * mime = QApplication::clipboard()->data(); + if ( !mime ) + { + KMessageBox::information( this, i18n("There is no data in the clipboard.") ); + m_cancelled = true; + return; + } + + if ( !mime->provides( "text/plain" ) ) + { + KMessageBox::information( this, i18n("There is no usable data in the clipboard.") ); + m_cancelled = true; + return; + } + m_fileArray = QByteArray(mime->encodedData( "text/plain" ) ); + } + else if ( mode == File ) + {*/ + m_dateRegExp = QRegExp("(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})"); + m_timeRegExp1 = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})"); + m_timeRegExp2 = QRegExp("(\\d{1,2}):(\\d{1,2})"); + m_fpNumberRegExp = QRegExp("[\\-]{0,1}\\d*[,\\.]\\d+"); + QString caption( i18n("Open CSV Data File") ); + + if (m_mode == File) { + QStringList mimetypes( csvMimeTypes() ); +#ifdef Q_WS_WIN + //! @todo remove + QString recentDir = KGlobalSettings::documentPath(); + m_fname = QFileDialog::getOpenFileName( + KFileDialog::getStartURL(":CSVImportExport", recentDir).path(), + KexiUtils::fileDialogFilterStrings(mimetypes, false), + page, "KexiCSVImportDialog", caption); + if ( !m_fname.isEmpty() ) { + //save last visited path + KURL url; + url.setPath( m_fname ); + if (url.isLocalFile()) + KRecentDirs::add(":CSVImportExport", url.directory()); + } +#else + m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "), + this, caption); +#endif + //cancel action ! + if ( m_fname.isEmpty() ) + { + actionButton( Ok )->setEnabled( false ); + m_cancelled = true; + if (parentWidget()) + parentWidget()->raise(); + return; + } + } + else if (m_mode == Clipboard) { + QCString subtype("plain"); + m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); +/* debug + for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++) + kdDebug() << i << ": " + << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl; +*/ + } + else { + return; + } + + m_loadingProgressDlg = 0; + m_importingProgressDlg = 0; + if (m_mode == File) { + m_loadingProgressDlg = new KProgressDialog( + this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...") + .arg(QDir::convertSeparators(m_fname)), true); + m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 ); + m_loadingProgressDlg->show(); + } + + if (m_mode==Clipboard) { + m_infoLbl->setIcon("editpaste"); + } + //updateRowCountInfo(); + + m_table->setSelectionMode(QTable::NoSelection); + + connect(m_formatCombo, SIGNAL(activated(int)), + this, SLOT(formatChanged(int))); + connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)), + this, SLOT(delimiterChanged(const QString&))); + connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )), + this, SLOT(startlineSelected(int))); + connect(m_comboQuote, SIGNAL(activated(int)), + this, SLOT(textquoteSelected(int))); + connect(m_table, SIGNAL(currentChanged(int, int)), + this, SLOT(currentCellChanged(int, int))); + connect(m_table, SIGNAL(valueChanged(int,int)), + this, SLOT(cellValueChanged(int,int))); + connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)), + this, SLOT(ignoreDuplicatesChanged(int))); + connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)), + this, SLOT(slot1stRowForFieldNamesChanged(int))); + + connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked())); + + installRecursiveEventFilter(this, this); + + initLater(); +} + +KexiCSVImportDialog::~KexiCSVImportDialog() +{ + delete m_file; +} + +void KexiCSVImportDialog::initLater() +{ + if (!openData()) + return; + +// delimiterChanged(detectedDelimiter); // this will cause fillTable() + m_columnsAdjusted = false; + fillTable(); + delete m_loadingProgressDlg; + m_loadingProgressDlg = 0; + if (m_dialogCancelled) { +// m_loadingProgressDlg->hide(); + // m_loadingProgressDlg->close(); + QTimer::singleShot(0, this, SLOT(reject())); + return; + } + + currentCellChanged(0, 0); + +// updateGeometry(); + adjustSize(); + KDialog::centerOnScreen( this ); + + if (m_loadingProgressDlg) + m_loadingProgressDlg->hide(); + show(); + m_table->setFocus(); +} + +bool KexiCSVImportDialog::openData() +{ + if (m_mode!=File) //data already loaded, no encoding stuff needed + return true; + + delete m_inputStream; + m_inputStream = 0; + if (m_file) { + m_file->close(); + delete m_file; + } + m_file = new QFile(m_fname); + if (!m_file->open(IO_ReadOnly)) + { + m_file->close(); + delete m_file; + m_file = 0; + KMessageBox::sorry( this, i18n("Cannot open input file \"%1\".") + .arg(QDir::convertSeparators(m_fname)) ); + actionButton( Ok )->setEnabled( false ); + m_cancelled = true; + if (parentWidget()) + parentWidget()->raise(); + return false; + } + return true; +} + +bool KexiCSVImportDialog::cancelled() const +{ + return m_cancelled; +} + +void KexiCSVImportDialog::fillTable() +{ + KexiUtils::WaitCursor wc(true); + repaint(); + m_blockUserEvents = true; + QPushButton *pb = actionButton(KDialogBase::Cancel); + if (pb) + pb->setEnabled(true); //allow to cancel + KexiUtils::WaitCursor wait; + + if (m_table->numRows()>0) //to accept editor + m_table->setCurrentCell(0,0); + + int row, column, maxColumn; + QString field = QString::null; + + for (row = 0; row < m_table->numRows(); ++row) + for (column = 0; column < m_table->numCols(); ++column) + m_table->clearCell(row, column); + + m_detectedTypes.clear(); + m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE); + m_uniquenessTest.clear(); + m_uniquenessTest.resize(1024); + m_1stRowForFieldNamesDetected = true; + + if (true != loadRows(field, row, column, maxColumn, true)) + return; + + m_1stRowForFieldNamesDetected = false; + + // file with only one line without '\n' + if (field.length() > 0) + { + setText(row - m_startline, column, field, true); + ++row; + field = QString::null; + } + + adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) ); + + maxColumn = QMAX( maxColumn, column ); + m_table->setNumCols(maxColumn); + + for (column = 0; column < m_table->numCols(); ++column) + { +// QString header = m_table->horizontalHeader()->label(column); +// if (header != i18n("Text") && header != i18n("Number") && +// header != i18n("Date") && header != i18n("Currency")) +// const int detectedType = m_detectedTypes[column+1]; +// m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text")); + updateColumnText(column); + if (!m_columnsAdjusted) + m_table->adjustColumn(column); + } + m_columnsAdjusted = true; + + if (m_primaryKeyColumn>=0 && m_primaryKeyColumnnumCols()) { + if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) { + m_primaryKeyColumn = -1; + } + } + + m_prevSelectedCol = -1; + m_table->setCurrentCell(0,0); + currentCellChanged(0, 0); + if (m_primaryKeyColumn != -1) + m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon); + + const int count = QMAX(0, m_table->numRows()-1+m_startline); + m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW; + if (m_allRowsLoadedInPreview) { + m_startAtLineSpinBox->setMaxValue(count); + m_startAtLineSpinBox->setValue(m_startline+1); + } + m_startAtLineLabel->setText(i18n( "Start at line%1:").arg( + m_allRowsLoadedInPreview ? QString(" (1-%1)").arg(count) + : QString::null //we do not know what's real count + )); + updateRowCountInfo(); + + m_blockUserEvents = false; + repaint(); + m_table->verticalScrollBar()->repaint();//avoid missing repaint + m_table->horizontalScrollBar()->repaint();//avoid missing repaint +} + +QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream) +{ + m_file->at(0); + + // try to detect delimiter + // \t has priority, then ; then , + const QIODevice::Offset origOffset = inputStream.device()->at(); + QChar c, prevChar=0; + int detectedDelimiter = 0; + bool insideQuote = false; + + //characters by priority + const int CH_TAB_AFTER_QUOTE = 500; + const int CH_SEMICOLON_AFTER_QUOTE = 499; + const int CH_COMMA_AFTER_QUOTE = 498; + const int CH_TAB = 200; // \t + const int CH_SEMICOLON = 199; // ; + const int CH_COMMA = 198; // , + + QValueList tabsPerLine, semicolonsPerLine, commasPerLine; + int tabs = 0, semicolons = 0, commas = 0; + int line = 0; + for (uint i=0; !inputStream.atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) { + (*m_inputStream) >> c; // read one char + if (prevChar=='"') { + if (c!='"') //real quote (not double "") + insideQuote = !insideQuote; + } + if (insideQuote) { + prevChar = c; + continue; + } + if (c==' ') + continue; + if (c=='\n') {//end of line + //remember # of tabs/semicolons/commas in this line + tabsPerLine += tabs; + tabs = 0; + semicolonsPerLine += semicolons; + semicolons = 0; + commasPerLine += commas; + commas = 0; + line++; + } + else if (c=='\t') { + tabs++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter ); + } + else if (c==';') { + semicolons++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter ); + } + else if (c==',') { + commas++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter ); + } + prevChar = c; + } + + inputStream.device()->at(origOffset); //restore orig. offset + + //now, try to find a delimiter character that exists the same number of times in all the checked lines + //this detection method has priority over others + QValueList::ConstIterator it; + if (tabsPerLine.count()>1) { + tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first(); + for (it=tabsPerLine.constBegin(); it!=tabsPerLine.constEnd(); ++it) { + if (tabs != *it) + break; + } + if (tabs>0 && it==tabsPerLine.constEnd()) + return "\t"; + } + if (semicolonsPerLine.count()>1) { + semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first(); + for (it=semicolonsPerLine.constBegin(); it!=semicolonsPerLine.constEnd(); ++it) { + if (semicolons != *it) + break; + } + if (semicolons > 0 && it==semicolonsPerLine.constEnd()) + return ";"; + } + if (commasPerLine.count()>1) { + commas = commasPerLine.first(); + for (it=commasPerLine.constBegin(); it!=commasPerLine.constEnd(); ++it) { + if (commas != *it) + break; + } + if (commas > 0 && it==commasPerLine.constEnd()) + return ","; + } + //now return the winning character by looking at CH_* symbol + if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB) + return "\t"; + if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON) + return ";"; + if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA) + return ","; + + return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default +} + +tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, + bool inGUI) +{ + enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, + S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START; + field = QString::null; + const bool ignoreDups = m_ignoreDuplicates->isChecked(); + bool lastCharDelimiter = false; + bool nextRow = false; + row = column = 1; + maxColumn = 0; + QChar x; + const bool hadInputStream = m_inputStream!=0; + delete m_inputStream; + if ( m_mode == Clipboard ) { + m_inputStream = new QTextStream(m_clipboardData, IO_ReadOnly); + if (!hadInputStream) + m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER); + } + else { + m_file->at(0); //always seek at 0 because loadRows() is called many times + m_inputStream = new QTextStream(m_file); + if (m_options.defaultEncodingExplicitySet) { + QTextCodec *codec = KGlobal::charsets()->codecForName(m_options.encoding); + if (codec) + m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250")); + } + if (m_detectDelimiter) { + const QString delimiter( detectDelimiterByLookingAtFirstBytesOfFile(*m_inputStream) ); + if (m_delimiterWidget->delimiter() != delimiter) + m_delimiterWidget->setDelimiter( delimiter ); + } + } + const QChar delimiter(m_delimiterWidget->delimiter()[0]); + m_stoppedAt_MAX_BYTES_TO_PREVIEW = false; + int progressStep = 0; + if (m_importingProgressDlg) + progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 ); + int offset = 0; + for (;!m_inputStream->atEnd(); offset++) + { +//disabled: this breaks wide spreadsheets +// if (column >= m_maximumRowsForPreview) +// return true; + + if (m_importingProgressDlg && ((offset % progressStep) < 5)) { + //update progr. bar dlg on final exporting + m_importingProgressDlg->progressBar()->setValue(offset); + qApp->processEvents(); + if (m_importingProgressDlg->wasCancelled()) { + delete m_importingProgressDlg; + m_importingProgressDlg = 0; + return ::cancelled; + } + } + + (*m_inputStream) >> x; // read one char + + if (x == '\r') { + continue; // eat '\r', to handle RFC-compliant files + } + if (offset==0 && x.unicode()==0xfeff) { + // Ignore BOM, the "Byte Order Mark" + // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf) + // Probably fixed in Qt4. + continue; + } + + switch (state) + { + case S_START : + if (x == m_textquote) + { + state = S_QUOTED_FIELD; + } + else if (x == delimiter) + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + else if (x == '\n') + { + if (!inGUI) { + //fill remaining empty fields (database wants them explicitly) + for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { + setText(row - m_startline, additionalColumn, QString::null, inGUI); + } + } + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + field += x; + state = S_MAYBE_NORMAL_FIELD; + } + break; + case S_QUOTED_FIELD : + if (x == m_textquote) + { + state = S_MAYBE_END_OF_QUOTED_FIELD; + } +/*allow \n inside quoted fields + else if (x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = ""; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + }*/ + else + { + field += x; + } + break; + case S_MAYBE_END_OF_QUOTED_FIELD : + if (x == m_textquote) + { + field += x; //no, this was just escaped quote character + state = S_QUOTED_FIELD; + } + else if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + state = S_END_OF_QUOTED_FIELD; + } + break; + case S_END_OF_QUOTED_FIELD : + if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + state = S_END_OF_QUOTED_FIELD; + } + break; + case S_MAYBE_NORMAL_FIELD : + if (x == m_textquote) + { + field = QString::null; + state = S_QUOTED_FIELD; + break; + } + case S_NORMAL_FIELD : + if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + field += x; + } + } + if (x != delimiter) + lastCharDelimiter = false; + + if (nextRow) { + if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) { + // do not save to the database 1st row if it contains column names + m_importingStatement->clearArguments(); + } + else if (!saveRow(inGUI)) + return false; + + ++row; + } + + if (m_firstFillTableCall && row==2 + && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected) + { + //'1st row for field name' flag detected: reload table + m_1stRowForFieldNamesDetected = false; + m_table->setNumRows( 0 ); + m_firstFillTableCall = false; //this trick is allowed only once, on startup + m_1stRowForFieldNames->setChecked(true); //this will reload table + //slot1stRowForFieldNamesChanged(1); + m_blockUserEvents = false; + repaint(); + return false; + } + + if (!m_importingProgressDlg && row % 20 == 0) { + qApp->processEvents(); + //only for GUI mode: + if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) { + delete m_loadingProgressDlg; + m_loadingProgressDlg = 0; + m_dialogCancelled = true; + reject(); + return false; + } + } + + if (!m_firstFillTableCall && m_loadingProgressDlg) { + m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row)); + } + + if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) { + kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #" + << m_maximumRowsForPreview << endl; + break; + } + if (nextRow) { + nextRow = false; + //additional speedup: stop processing now if too many bytes were loaded for preview + kexipluginsdbg << offset << endl; + if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) { + m_stoppedAt_MAX_BYTES_TO_PREVIEW = true; + return true; + } + } + } + return true; +} + +void KexiCSVImportDialog::updateColumnText(int col) +{ + QString colName; + if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col])) + colName = m_columnNames[ col ]; + if (colName.isEmpty()) { + colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import + m_changedColumnNames[ col ] = false; + } + int detectedType = m_detectedTypes[col]; + if (detectedType==_FP_NUMBER_TYPE) + detectedType=_NUMBER_TYPE; //we're simplifying that for now + else if (detectedType==_NO_TYPE_YET) { + m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column + detectedType=_TEXT_TYPE; + } + m_table->horizontalHeader()->setLabel(col, + i18n("Column %1").arg(col+1) + " \n(" + m_typeNames[ detectedType ] + ") "); + m_table->setText(0, col, colName); + m_table->horizontalHeader()->adjustHeaderSize(); + + //check uniqueness + QValueList *list = m_uniquenessTest[col]; + if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) { + qHeapSort(*list); + QValueList::ConstIterator it=list->constBegin(); + int prevValue = *it; + ++it; + for(; it!=list->constEnd() && prevValue!=(*it); ++it) + prevValue=(*it); + if (it!=list->constEnd()) { + //duplicates: + list->clear(); + } + else { + //a candidate for PK (autodetected)! + if (-1==m_primaryKeyColumn) { + m_primaryKeyColumn=col; + } + } + } + if (list) //not needed now: conserve memory + list->clear(); +} + +void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text) +{ + int intValue; + const int type = m_detectedTypes[col]; + if (row==1 || type!=_TEXT_TYPE) { + bool found = false; + if (text.isEmpty() && type==_NO_TYPE_YET) + found = true; //real type should be found later + //detect type because it's 1st row or all prev. rows were not text + //-FP number? (trying before "number" type is a must) + if (!found && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) { + bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text); + //if (!ok) + // text.toDouble(&ok); + if (ok && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) { + m_detectedTypes[col]=_FP_NUMBER_TYPE; + found = true; //yes + } + } + //-number? + if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) { + bool ok = text.isEmpty();//empty values allowed + if (!ok) + intValue = text.toInt(&ok); + if (ok && (row==1 || type==_NO_TYPE_YET)) { + m_detectedTypes[col]=_NUMBER_TYPE; + found = true; //yes + } + } + //-date? + if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) { + if ((row==1 || type==_NO_TYPE_YET) + && (text.isEmpty() || m_dateRegExp.exactMatch(text))) + { + m_detectedTypes[col]=_DATE_TYPE; + found = true; //yes + } + } + //-time? + if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) { + if ((row==1 || type==_NO_TYPE_YET) + && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text))) + { + m_detectedTypes[col]=_TIME_TYPE; + found = true; //yes + } + } + //-date/time? + if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) { + if (row==1 || type==_NO_TYPE_YET) { + bool detected = text.isEmpty(); + if (!detected) { + const QStringList dateTimeList( QStringList::split(" ", text) ); + bool ok = dateTimeList.count()>=2; +//! @todo also support ISODateTime's "T" separator? +//! @todo also support timezones? + if (ok) { + //try all combinations + QString datePart( dateTimeList[0].stripWhiteSpace() ); + QString timePart( dateTimeList[1].stripWhiteSpace() ); + ok = m_dateRegExp.exactMatch(datePart) + && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart)); + } + detected = ok; + } + if (detected) { + m_detectedTypes[col]=_DATETIME_TYPE; + found = true; //yes + } + } + } + if (!found && type==_NO_TYPE_YET && !text.isEmpty()) { + //eventually, a non-emptytext after a while + m_detectedTypes[col]=_TEXT_TYPE; + found = true; //yes + } + //default: text type (already set) + } + //check uniqueness for this value + QValueList *list = m_uniquenessTest[col]; + if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) { + if (!list) { + list = new QValueList(); + m_uniquenessTest.insert(col, list); + } + list->append( intValue ); + } + else { + //the value is empty or uniqueness test failed in the past + if (list && !list->isEmpty()) + list->clear(); //indicate that uniqueness test failed + } +} + +bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date) +{ + if (!m_dateRegExp.exactMatch(text)) + return false; + //dddd - dd - dddd + //1 2 3 4 5 <- pos + const int d1 = m_dateRegExp.cap(1).toInt(), d3 = m_dateRegExp.cap(3).toInt(), d5 = m_dateRegExp.cap(5).toInt(); + if (m_dateRegExp.cap(2)=="/") //probably separator for american format mm/dd/yyyy + date = QDate(d5, d1, d3); + else { + if (d5 > 31) //d5 == year + date = QDate(d5, d3, d1); + else //d1 == year + date = QDate(d1, d3, d5); + } + return date.isValid(); +} + +bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time) +{ + time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1 + if (time.isValid()) + return true; + if (m_timeRegExp2.exactMatch(text)) { //hh:mm:ss + time = QTime(m_timeRegExp2.cap(1).toInt(), m_timeRegExp2.cap(3).toInt(), m_timeRegExp2.cap(5).toInt()); + return true; + } + return false; +} + +void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI) +{ + if (!inGUI) { + //save text directly to database buffer + if (col==1) { //1st col + m_importingStatement->clearArguments(); + if (m_implicitPrimaryKeyAdded) + *m_importingStatement << QVariant(); //id will be autogenerated here + } + const int detectedType = m_detectedTypes[col-1]; + if (detectedType==_NUMBER_TYPE) { + *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() ); +//! @todo what about time and float/double types and different integer subtypes? + } + else if (detectedType==_FP_NUMBER_TYPE) { + //replace ',' with '.' + QCString t(text.latin1()); + const int textLen = t.length(); + for (int i=0; i=2) { + QString datePart( dateTimeList[0].stripWhiteSpace() ); + QDate date; + if (parseDate(datePart, date)) { + QString timePart( dateTimeList[1].stripWhiteSpace() ); + QTime time; + if (parseTime(timePart, time)) + *m_importingStatement << QDateTime(date, time); + } + } + } + else //_TEXT_TYPE and the rest + *m_importingStatement << (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text); + return; + } + //save text to GUI (table view) + if (m_table->numCols() < col) { + m_table->setNumCols(col); + if ((int)m_columnNames.size() < m_table->numCols()) { + m_columnNames.resize(m_table->numCols()+10); + m_changedColumnNames.resize(m_table->numCols()+10); + } + } + + if (m_1stRowForFieldNames->isChecked()) { + if ((row+m_startline)==1) {//this is for column name + if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) { + //this column has no custom name entered by a user + //-get the name from the data cell + QString colName(text.simplifyWhiteSpace()); + if (!colName.isEmpty()) { + if (colName.left(1)>="0" && colName.left(1)<="9") + colName.prepend(i18n("Column")+" "); + m_columnNames[ col-1 ] = colName; + } + } + return; + } + } + else { + if ((row+m_startline)==1) {//this row is for column name + if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) { + QString f( text.simplifyWhiteSpace() ); + if (f.isEmpty() || !f[0].isLetter()) + m_1stRowForFieldNamesDetected = false; //this couldn't be a column name + } + } + row++; //1st row was for column names + } + + if (row < 2) // skipped by the user + return; + + if (m_table->numRows() < row) { +// if (m_maximumRowsForPreview >= row+100) + m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */ + //else +// m_table->setNumRows(m_maximumRowsForPreview); + m_table->verticalHeader()->setLabel(0, i18n("Column name")+" "); + m_adjustRows=true; + } + + m_table->setText(row - 1, col - 1, (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text)); + m_table->verticalHeader()->setLabel(row-1, QString::number(row-1)); + + detectTypeAndUniqueness(row-1, col-1, text); +} + +bool KexiCSVImportDialog::saveRow(bool inGUI) +{ + if (inGUI) { + //nothing to do + return true; + } + //save db buffer + bool res = m_importingStatement->execute(); +//todo: move + m_importingStatement->clearArguments(); + return res; +// return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer); +} + +void KexiCSVImportDialog::adjustRows(int iRows) +{ + if (m_adjustRows) + { + m_table->setNumRows( iRows ); + m_adjustRows=false; + for (int i = 0; iadjustRow(i); + } +} + +void KexiCSVImportDialog::formatChanged(int id) +{ + if (id==_PK_FLAG) { + if (m_primaryKeyColumn>=0 && m_primaryKeyColumnnumCols()) { + m_table->setPixmap(0, m_primaryKeyColumn, QPixmap()); + } + if (m_primaryKeyField->isChecked()) { + m_primaryKeyColumn = m_table->currentColumn(); + m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon); + } + else + m_primaryKeyColumn = -1; + return; + } + else { + m_detectedTypes[m_table->currentColumn()]=id; + m_primaryKeyField->setEnabled( _NUMBER_TYPE == id ); + m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() ); + } + updateColumnText(m_table->currentColumn()); +} + +void KexiCSVImportDialog::delimiterChanged(const QString& delimiter) +{ + Q_UNUSED(delimiter); + m_columnsAdjusted = false; + m_detectDelimiter = false; //selected by hand: do not detect in the future + //delayed, otherwise combobox won't be repainted + fillTableLater(); +} + +void KexiCSVImportDialog::textquoteSelected(int) +{ + const QString tq(m_comboQuote->textQuote()); + if (tq.isEmpty()) + m_textquote = 0; + else + m_textquote = tq[0]; + + kexipluginsdbg << "KexiCSVImportDialog::textquoteSelected(): " << m_textquote << endl; + + //delayed, otherwise combobox won't be repainted + fillTableLater(); +} + +void KexiCSVImportDialog::fillTableLater() +{ + m_table->setNumRows( 0 ); + QTimer::singleShot(10, this, SLOT(fillTable())); +} + +void KexiCSVImportDialog::startlineSelected(int startline) +{ +// const int startline = line.toInt() - 1; + if (m_startline == (startline-1)) + return; + m_startline = startline-1; + m_adjustRows=true; + fillTable(); + m_table->setFocus(); +} + +void KexiCSVImportDialog::currentCellChanged(int, int col) +{ + if (m_prevSelectedCol==col) + return; + m_prevSelectedCol = col; + int type = m_detectedTypes[col]; + if (type==_FP_NUMBER_TYPE) + type=_NUMBER_TYPE; //we're simplifying that for now + + m_formatCombo->setCurrentItem( type ); + m_formatLabel->setText( m_formatComboText.arg(col+1) ); + m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]); + m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled() + m_primaryKeyField->setChecked( m_primaryKeyColumn == col ); + m_primaryKeyField->blockSignals(false); +} + +void KexiCSVImportDialog::cellValueChanged(int row,int col) +{ + if (row==0) {//column name has changed + m_columnNames[ col ] = m_table->text(row, col); + m_changedColumnNames.setBit( col ); + } +} + +void KexiCSVImportDialog::accept() +{ +//! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiDialogBase code is moved to non-gui place + + KexiGUIMessageHandler msg; //! @todo make it better integrated with main window + + const uint numRows( m_table->numRows() ); + if (numRows == 0) + return; //impossible + + if (numRows == 1) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, + i18n("Data set contains no rows. Do you want to import empty table?"))) + return; + } + + KexiProject* project = m_mainWin->project(); + if (!project) { + msg.showErrorMessage(i18n("No project available.")); + return; + } + m_conn = project->dbConnection(); //cache this pointer + if (!m_conn) { + msg.showErrorMessage(i18n("No database connection available.")); + return; + } + KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table"); + if (!part) { + msg.showErrorMessage(&Kexi::partManager()); + return; + } + + //get suggested name based on the file name + QString suggestedName; + if (m_mode==File) { + suggestedName = KURL::fromPathOrURL(m_fname).fileName(); + //remove extension + if (!suggestedName.isEmpty()) { + const int idx = suggestedName.findRev("."); + if (idx!=-1) + suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace(); + } + } + + //-new part item + KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName); + if (!partItemForSavedTable) { + // msg.showErrorMessage(project); + return; + } + +#define _ERR \ + { project->deleteUnstoredItem(partItemForSavedTable); \ + m_conn = 0; \ + delete m_destinationTableSchema; \ + m_destinationTableSchema = 0; \ + return; } + + //-ask for table name/title + // (THIS IS FROM KexiMainWindowImpl::saveObject()) + bool allowOverwriting = true; + tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting ); + if (~res || !res) { + //! @todo: err + _ERR; + } + //(allowOverwriting is now set to true, if user accepts overwriting, + // and overwriting will be needed) + +// KexiDB::SchemaData sdata(part->info()->projectPartID()); +// sdata.setName( partItem->name() ); + + //-create table schema (and thus schema object) + //-assign information (THIS IS FROM KexiDialogBase::storeNewData()) + m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name()); + m_destinationTableSchema->setCaption( partItemForSavedTable->caption() ); + m_destinationTableSchema->setDescription( partItemForSavedTable->description() ); + const uint numCols( m_table->numCols() ); + + m_implicitPrimaryKeyAdded = false; + //add PK if user wanted it + int msgboxResult; + if (m_primaryKeyColumn==-1 + && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, + i18n("No Primary Key (autonumber) has been defined.\n" + "Should it be automatically defined on import (recommended)?\n\n" + "Note: An imported table without a Primary Key may not be editable (depending on database type)."), + QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"), + KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add"))))) + { + if (msgboxResult == KMessageBox::Cancel) + _ERR; //cancel accepting + + //add implicit PK field +//! @todo make this field hidden (what about e.g. pgsql?) + m_implicitPrimaryKeyAdded = true; + + QString fieldName("id"); + QString fieldCaption("Id"); + + QStringList colnames; + for (uint col = 0; col < numCols; col++) + colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() ); + + if (colnames.find(fieldName)!=colnames.end()) { + int num = 1; + while (colnames.find(fieldName+QString::number(num))!=colnames.end()) + num++; + fieldName += QString::number(num); + fieldCaption += QString::number(num); + } + KexiDB::Field *field = new KexiDB::Field( + fieldName, + KexiDB::Field::Integer, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0,0, //uint length=0, uint precision=0, + QVariant(), //QVariant defaultValue=QVariant(), + fieldCaption + ); //no description and width for now + field->setPrimaryKey(true); + field->setAutoIncrement(true); + m_destinationTableSchema->addField( field ); + } + + for (uint col = 0; col < numCols; col++) { + QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() ); + QString fieldName( KexiUtils::string2Identifier( fieldCaption ) ); + if (m_destinationTableSchema->field(fieldName)) { + QString fixedFieldName; + uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names + do { + fixedFieldName = fieldName + "_" + QString::number(i); + if (!m_destinationTableSchema->field(fixedFieldName)) + break; + i++; + } while (true); + fieldName = fixedFieldName; + fieldCaption += (" " + QString::number(i)); + } + const int detectedType = m_detectedTypes[col]; + KexiDB::Field::Type fieldType; + if (detectedType==_DATE_TYPE) + fieldType = KexiDB::Field::Date; + if (detectedType==_TIME_TYPE) + fieldType = KexiDB::Field::Time; + if (detectedType==_DATETIME_TYPE) + fieldType = KexiDB::Field::DateTime; + else if (detectedType==_NUMBER_TYPE) + fieldType = KexiDB::Field::Integer; + else if (detectedType==_FP_NUMBER_TYPE) + fieldType = KexiDB::Field::Double; +//! @todo what about time and float/double types and different integer subtypes? + else //_TEXT_TYPE and the rest + fieldType = KexiDB::Field::Text; +//! @todo what about long text? + + KexiDB::Field *field = new KexiDB::Field( + fieldName, + fieldType, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0,0, //uint length=0, uint precision=0, + QVariant(), //QVariant defaultValue=QVariant(), + fieldCaption + ); //no description and width for now + + if ((int)col == m_primaryKeyColumn) { + field->setPrimaryKey(true); + field->setAutoIncrement(true); + } + m_destinationTableSchema->addField( field ); + } + + KexiDB::Transaction transaction = m_conn->beginTransaction(); + if (transaction.isNull()) { + msg.showErrorMessage(m_conn); + _ERR; + } + KexiDB::TransactionGuard tg(transaction); + + //-create physical table + if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) { + msg.showErrorMessage(m_conn); + _ERR; + } + +#define _DROP_DEST_TABLE_AND_RETURN \ + { \ + if (m_importingProgressDlg) \ + m_importingProgressDlg->hide(); \ + project->deleteUnstoredItem(partItemForSavedTable); \ + m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \ + m_destinationTableSchema = 0; \ + m_conn = 0; \ + return; \ + } + + m_importingStatement = m_conn->prepareStatement( + KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema); + if (!m_importingStatement) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + if (m_file) { + if (!m_importingProgressDlg) { + m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg", + i18n("Importing CSV Data"), QString::null, true ); + } + m_importingProgressDlg->setLabel( + i18n("Importing CSV Data from \"%1\" into \"%2\" table...") + .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) ); + m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() ); + m_importingProgressDlg->show(); + } + + int row, column, maxColumn; + QString field = QString::null; + + // main job + res = loadRows(field, row, column, maxColumn, false /*!gui*/ ); + + delete m_importingProgressDlg; + m_importingProgressDlg = 0; + if (true != res) { + //importing cancelled or failed + if (!res) //do not display err msg when res == cancelled + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + // file with only one line without '\n' + if (field.length() > 0) + { + setText(row - m_startline, column, field, false /*!gui*/); + //fill remaining empty fields (database wants them explicitly) + for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { + setText(row - m_startline, additionalColumn, QString::null, false /*!gui*/); + } + if (!saveRow(false /*!gui*/)) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + ++row; + field = QString::null; + } + + if (!tg.commit()) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + //-now we can store the item + partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() ); + project->addStoredItem( part->info(), partItemForSavedTable ); + + QDialog::accept(); + KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".") + .arg(m_destinationTableSchema->name())); + parentWidget()->raise(); + m_conn = 0; +} + +int KexiCSVImportDialog::getHeader(int col) +{ + QString header = m_table->horizontalHeader()->label(col); + + if (header == i18n("Text type for column", "Text")) + return TEXT; + else if (header == i18n("Numeric type for column", "Number")) + return NUMBER; + else if (header == i18n("Currency type for column", "Currency")) + return CURRENCY; + else + return DATE; +} + +QString KexiCSVImportDialog::getText(int row, int col) +{ + return m_table->text(row, col); +} + +void KexiCSVImportDialog::ignoreDuplicatesChanged(int) +{ + fillTable(); +} + +void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int) +{ + m_adjustRows=true; + if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1)) + m_startline--; + fillTable(); +} + +void KexiCSVImportDialog::optionsButtonClicked() +{ + KexiCSVImportOptionsDialog dlg(m_options, this); + if (QDialog::Accepted != dlg.exec()) + return; + + KexiCSVImportOptions newOptions( dlg.options() ); + if (m_options != newOptions) { + m_options = newOptions; + if (!openData()) + return; + fillTable(); + } +} + +bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e ) +{ + QEvent::Type t = e->type(); + // temporary disable keyboard and mouse events for time-consuming tasks + if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease + || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick + || t==QEvent::Paint )) + return true; + + if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) { + QKeyEvent *ke = static_cast(e); + if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + m_table->setFocus(); + return true; + } + } + return QDialog::eventFilter( watched, e ); +} + +void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on) +{ + Q_UNUSED(on); + formatChanged(_PK_FLAG); +} + +void KexiCSVImportDialog::updateRowCountInfo() +{ + m_infoLbl->setFileName( m_fname ); + if (m_allRowsLoadedInPreview) { + m_infoLbl->setCommentText( + i18n("row count", "(rows: %1)").arg( m_table->numRows()-1+m_startline ) ); + QToolTip::remove( m_infoLbl ); + } + else { + m_infoLbl->setCommentText( + i18n("row count", "(rows: more than %1)").arg( m_table->numRows()-1+m_startline ) ); + QToolTip::add( m_infoLbl->commentLabel(), i18n("Not all rows are visible on this preview") ); + } +} + +#include "kexicsvimportdialog.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.h b/kexi/plugins/importexport/csv/kexicsvimportdialog.h new file mode 100644 index 00000000..1f7b159e --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.h @@ -0,0 +1,231 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek + + This work is based on kspread/dialogs/kspread_dlg_csv.cc + and will be merged back with KOffice libraries. + + Copyright (C) 2002-2003 Norbert Andres + Copyright (C) 2002-2003 Ariya Hidayat + Copyright (C) 2002 Laurent Montel + Copyright (C) 1999 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_CSVDIALOG_H +#define KEXI_CSVDIALOG_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "kexicsvimportoptionsdlg.h" + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QButtonGroup; +class QCheckBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QTable; +class QFile; +class KComboBox; +class KIntSpinBox; +class KProgressDialog; + +class KexiMainWindow; +class KexiCSVDelimiterWidget; +class KexiCSVTextQuoteComboBox; +class KexiCSVInfoLabel; + +/** + * @short Kexi CSV import dialog + * + * This is temporary solution for Kexi CSV import, + * based on kspread/dialogs/kspread_dlg_csv.h, cc. + * + * Provides dialog for managing CSV (comma separated value) data. + * + * Currently KexiCSVImportDialog is used for converting text into columns, + * inserting text file and pasting text from clipboard, where conversion + * from CSV (comma separated value) data is is all required. + * The different purposed mentioned above is determined + * using mode, which can be Column, File, or Clipboard respectively. +*/ +class KexiCSVImportDialog : public KDialogBase +{ + Q_OBJECT + + public: + enum Mode { Clipboard, File /*, Column*/ }; + enum Header { TEXT, NUMBER, DATE, CURRENCY }; + + //! @todo what about making it kexidb-independent? + KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, QWidget * parent, + const char * name = 0/*, QRect const & rect*/); + + virtual ~KexiCSVImportDialog(); + + bool cancelled() const; + virtual bool eventFilter ( QObject * watched, QEvent * e ); + + protected: + bool openData(); + virtual void accept(); + + private: + QGridLayout* MyDialogLayout; + QHBoxLayout* Layout1; + QTable* m_table; + KexiCSVDelimiterWidget* m_delimiterWidget; + bool m_detectDelimiter; //!< true if delimiter should be detected + //!< (true by default, set to false if user sets delimiter) + QString m_formatComboText; + QLabel* m_formatLabel; + KComboBox* m_formatCombo; + KIntSpinBox *m_startAtLineSpinBox; + KexiCSVTextQuoteComboBox* m_comboQuote; + QLabel* m_startAtLineLabel; + QLabel* TextLabel2; + QCheckBox* m_ignoreDuplicates; + QCheckBox* m_1stRowForFieldNames; + QCheckBox* m_primaryKeyField; + + KexiMainWindow* m_mainWin; + + void detectTypeAndUniqueness(int row, int col, const QString& text); + void setText(int row, int col, const QString& text, bool inGUI); + + /*! Parses date from \a text and stores into \a date. + m_dateRegExp is used for clever detection; + if '/' separated is found, it's assumed the format is american mm/dd/yyyy. + This function supports omitted zeros, so 1/2/2006 is parsed properly too. + \return true on success. */ + bool parseDate(const QString& text, QDate& date); + + /*! Parses time from \a text and stores into \a date. + m_timeRegExp1 and m_timeRegExp2 are used for clever detection; + both hh:mm:ss and hh:mm are supported. + This function supports omitted zeros, so 1:2:3 is parsed properly too. + \return true on success. */ + bool parseTime(const QString& text, QTime& time); + + /*! Called after the first fillTable() when number of rows is unknown. */ + void adjustRows(int iRows); + + int getHeader(int col); + QString getText(int row, int col); + void updateColumnText(int col); + void updateRowCountInfo(); + tristate loadRows(QString &field, int &row, int &columnm, int &maxColumn, bool inGUI); + + /*! Detects delimiter by looking at first 4K bytes of the data. Used by loadRows(). + The used algorithm: + 1. Look byte by byte and locate special characters that can be delimiters. + Special fact is taken into account: if there are '"' quotes used for text values, + delimiters that follow directly the closing quote has higher priority than the one + that follows other character. We do not assume that every text value is quoted. + Summing up, there is following hierarchy (from highest to lowest): + quote+tab, quote+semicolon, quote+comma, tab, semicolon, comma. + Space characters are skipped. Text inside quotes is skipped, as well as double + (escaped) quotes. + 2. While scanning the data, for every row following number of tabs, semicolons and commas + (only these outside of the quotes) are computed. On every line the values are appended + to a separate list (QValueList). + 3. After scanning, all the values are checked on the QValueList of tabs. + If the list has more one element (so there was more than one row) and all the values + (numbers of tabs) are equal, it's very probable the tab is a delimiter. + So, this character is returned as a delimiter. + 3a. The same algorithm as in 3. is performed for semicolon character. + 3b. The same algorithm as in 3. is performed for comma character. + 4. If the step 3. did not return a delimiter, a character found in step 1. with + the highest priority is retured as delimiter. */ + QString detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream); + + /*! Callback, called whenever row is loaded in loadRows(). When inGUI is true, + nothing is performed, else database buffer is written back to the database. */ + bool saveRow(bool inGUI); + + bool m_cancelled; + bool m_adjustRows; + int m_startline; + QChar m_textquote; + QString m_clipboardData; + QByteArray m_fileArray; + Mode m_mode; + int m_prevSelectedCol; + + //! vector of detected types, 0==text (the default), 1==number, 2==date +//! @todo more types + QValueVector m_detectedTypes; + + //! m_detectedUniqueColumns[i]==true means that i-th column has unique values + //! (only for numeric type) + QPtrVector< QValueList > m_uniquenessTest; + + QRegExp m_dateRegExp, m_timeRegExp1, m_timeRegExp2, m_fpNumberRegExp; + QValueVector m_typeNames, m_columnNames; + QBitArray m_changedColumnNames; + bool m_columnsAdjusted : 1; //!< to call adjustColumn() only once + bool m_1stRowForFieldNamesDetected : 1; //!< used to force rerun fillTable() after 1st row + bool m_firstFillTableCall : 1; //!< used to know whether it's 1st fillTable() call + bool m_blockUserEvents : 1; + int m_primaryKeyColumn; //!< index of column with PK assigned (-1 if none) + int m_maximumRowsForPreview; + int m_maximumBytesForPreview; + QPixmap m_pkIcon; + QString m_fname; + QFile* m_file; + QTextStream *m_inputStream; //!< used in loadData() + KexiCSVImportOptions m_options; + KProgressDialog *m_loadingProgressDlg, *m_importingProgressDlg; + bool m_dialogCancelled; + KexiCSVInfoLabel *m_infoLbl; + KexiDB::Connection *m_conn; //!< (temp) database connection used for importing + KexiDB::TableSchema *m_destinationTableSchema; //!< (temp) dest. table schema used for importing + KexiDB::PreparedStatement::Ptr m_importingStatement; + QValueList m_dbRowBuffer; //!< (temp) used for importing + bool m_implicitPrimaryKeyAdded; //!< (temp) used for importing + bool m_allRowsLoadedInPreview; //!< we need to know whether all rows were loaded or it's just a partial data preview + bool m_stoppedAt_MAX_BYTES_TO_PREVIEW; //!< used to compute m_allRowsLoadedInPreview + + private slots: + void fillTable(); + void fillTableLater(); + void initLater(); + void formatChanged(int id); + void delimiterChanged(const QString& delimiter); + void startlineSelected(int line); + void textquoteSelected(int); + void currentCellChanged(int, int col); + void ignoreDuplicatesChanged(int); + void slot1stRowForFieldNamesChanged(int); + void cellValueChanged(int row,int col); + void optionsButtonClicked(); + void slotPrimaryKeyFieldToggled(bool on); +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp new file mode 100644 index 00000000..b381dde3 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp @@ -0,0 +1,140 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexicsvimportoptionsdlg.h" +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +KexiCSVImportOptions::KexiCSVImportOptions() +{ + kapp->config()->setGroup("ImportExport"); + encoding = kapp->config()->readEntry("DefaultEncodingForImportingCSVFiles"); + if (encoding.isEmpty()) { + encoding = QString::fromLatin1(KGlobal::locale()->encoding()); + defaultEncodingExplicitySet = false; + } + else + defaultEncodingExplicitySet = true; + + stripWhiteSpaceInTextValuesChecked + = kapp->config()->readBoolEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles", true); +} + +KexiCSVImportOptions::~KexiCSVImportOptions() +{ +} + +bool KexiCSVImportOptions::operator== ( const KexiCSVImportOptions & opt ) const +{ + return defaultEncodingExplicitySet==opt.defaultEncodingExplicitySet + && stripWhiteSpaceInTextValuesChecked==opt.stripWhiteSpaceInTextValuesChecked + && encoding==opt.encoding; +} + +bool KexiCSVImportOptions::operator!= ( const KexiCSVImportOptions & opt ) const +{ + return !( *this==opt ); +} + +//---------------------------------- + +KexiCSVImportOptionsDialog::KexiCSVImportOptionsDialog( + const KexiCSVImportOptions& options, QWidget* parent ) + : KDialogBase( + KDialogBase::Plain, + i18n( "CSV Import Options" ), + Ok|Cancel, + Ok, + parent, + "KexiCSVImportOptionsDialog", + true, + false + ) +{ + QGridLayout *lyr = new QGridLayout( plainPage(), 5, 3, + KDialogBase::marginHint(), KDialogBase::spacingHint()); + + m_encodingComboBox = new KexiCharacterEncodingComboBox(plainPage(), options.encoding); + lyr->addWidget( m_encodingComboBox, 0, 1 ); + + QLabel* lbl = new QLabel( m_encodingComboBox, i18n("Text encoding:"), plainPage()); + lyr->addWidget( lbl, 0, 0 ); + + lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Fixed, QSizePolicy::Fixed ), 2, 1 ); + lyr->addItem( new QSpacerItem( 121, KDialogBase::spacingHint(), QSizePolicy::Expanding, QSizePolicy::Minimum ), 0, 2 ); + + m_chkAlwaysUseThisEncoding = new QCheckBox( + i18n("Always use this encoding when importing CSV data files"), plainPage()); + lyr->addWidget( m_chkAlwaysUseThisEncoding, 1, 1 ); + + m_chkStripWhiteSpaceInTextValues = new QCheckBox( + i18n("Strip leading and trailing blanks off of text values"), plainPage()); + lyr->addWidget( m_chkStripWhiteSpaceInTextValues, 3, 1 ); + lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Minimum, QSizePolicy::Expanding ), 4, 1 ); + + //update widgets + if (options.defaultEncodingExplicitySet) { + m_encodingComboBox->setSelectedEncoding(options.encoding); + m_chkAlwaysUseThisEncoding->setChecked(true); + } + m_chkStripWhiteSpaceInTextValues->setChecked(options.stripWhiteSpaceInTextValuesChecked); + + adjustSize(); + m_encodingComboBox->setFocus(); +} + +KexiCSVImportOptionsDialog::~KexiCSVImportOptionsDialog() +{ +} + +KexiCSVImportOptions KexiCSVImportOptionsDialog::options() const +{ + KexiCSVImportOptions opt; + opt.encoding = m_encodingComboBox->selectedEncoding(); + opt.stripWhiteSpaceInTextValuesChecked = m_chkStripWhiteSpaceInTextValues->isChecked(); + return opt; +} + +void KexiCSVImportOptionsDialog::accept() +{ + kapp->config()->setGroup("ImportExport"); + if (m_chkAlwaysUseThisEncoding->isChecked()) + kapp->config()->writeEntry("DefaultEncodingForImportingCSVFiles", + m_encodingComboBox->selectedEncoding()); + else + kapp->config()->deleteEntry("DefaultEncodingForImportingCSVFiles"); + + kapp->config()->writeEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles", + m_chkStripWhiteSpaceInTextValues->isChecked()); + + KDialogBase::accept(); +} + +#include "kexicsvimportoptionsdlg.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h new file mode 100644 index 00000000..e0567c9c --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXICSVOPTIONSDIALOG_H +#define KEXICSVOPTIONSDIALOG_H + +#include +#include + +class KexiCharacterEncodingComboBox; + +//! @short CSV Options +class KexiCSVImportOptions +{ + public: + KexiCSVImportOptions(); + ~KexiCSVImportOptions(); + + bool operator== ( const KexiCSVImportOptions & opt ) const; + bool operator!= ( const KexiCSVImportOptions & opt ) const; + + QString encoding; + bool defaultEncodingExplicitySet; + bool stripWhiteSpaceInTextValuesChecked; +}; + +//! @short CSV Options dialog +class KexiCSVImportOptionsDialog : public KDialogBase +{ + Q_OBJECT + public: + KexiCSVImportOptionsDialog( const KexiCSVImportOptions& options, QWidget* parent = 0 ); + virtual ~KexiCSVImportOptionsDialog(); + + KexiCSVImportOptions options() const; + + protected slots: + virtual void accept(); + + protected: + KexiCharacterEncodingComboBox *m_encodingComboBox; + QCheckBox *m_chkAlwaysUseThisEncoding; + QCheckBox *m_chkStripWhiteSpaceInTextValues; +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.cpp b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp new file mode 100644 index 00000000..8e3cf4c2 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp @@ -0,0 +1,233 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicsvwidgets.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define KEXICSV_OTHER_DELIMITER_INDEX 4 + +KexiCSVDelimiterWidget::KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent ) + : QWidget(parent, "KexiCSVDelimiterWidget") + , m_availableDelimiters(KEXICSV_OTHER_DELIMITER_INDEX) + +{ + QBoxLayout *lyr = + lineEditOnBottom ? + (QBoxLayout *)new QVBoxLayout( this, 0, KDialogBase::spacingHint() ) + : (QBoxLayout *)new QHBoxLayout( this, 0, KDialogBase::spacingHint() ); + + m_availableDelimiters[0]=KEXICSV_DEFAULT_FILE_DELIMITER; + m_availableDelimiters[1]=";"; + m_availableDelimiters[2]="\t"; + m_availableDelimiters[3]=" "; + + m_combo = new KComboBox(this, "KexiCSVDelimiterComboBox"); + m_combo->insertItem( i18n("Comma \",\"") ); //<-- KEXICSV_DEFAULT_FILE_DELIMITER + m_combo->insertItem( i18n( "Semicolon \";\"" ) ); + m_combo->insertItem( i18n( "Tabulator" ) ); + m_combo->insertItem( i18n( "Space \" \"" ) ); + m_combo->insertItem( i18n( "Other" ) ); + lyr->addWidget(m_combo); + setFocusProxy(m_combo); + + m_delimiterEdit = new KLineEdit( this, "m_delimiterEdit" ); +// m_delimiterEdit->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0, m_delimiterEdit->sizePolicy().hasHeightForWidth() ) ); + m_delimiterEdit->setMaximumSize( QSize( 30, 32767 ) ); + m_delimiterEdit->setMaxLength(1); + lyr->addWidget( m_delimiterEdit ); + if (!lineEditOnBottom) + lyr->addStretch(2); + + slotDelimiterChangedInternal(KEXICSV_DEFAULT_FILE_DELIMITER_INDEX); //this will init m_delimiter + connect(m_combo, SIGNAL(activated(int)), + this, SLOT(slotDelimiterChanged(int))); + connect(m_delimiterEdit, SIGNAL(returnPressed()), + this, SLOT(slotDelimiterLineEditReturnPressed())); + connect(m_delimiterEdit, SIGNAL(textChanged( const QString & )), + this, SLOT(slotDelimiterLineEditTextChanged( const QString & ) )); +} + +void KexiCSVDelimiterWidget::slotDelimiterChanged(int index) +{ + slotDelimiterChangedInternal(index); + if (index==KEXICSV_OTHER_DELIMITER_INDEX) + m_delimiterEdit->setFocus(); +} + +void KexiCSVDelimiterWidget::slotDelimiterChangedInternal(int index) +{ + bool changed = false; + if (index > KEXICSV_OTHER_DELIMITER_INDEX) + return; + else if (index == KEXICSV_OTHER_DELIMITER_INDEX) { + changed = m_delimiter != m_delimiterEdit->text(); + m_delimiter = m_delimiterEdit->text(); + } + else { + changed = m_delimiter != m_availableDelimiters[index]; + m_delimiter = m_availableDelimiters[index]; + } + m_delimiterEdit->setEnabled(index == KEXICSV_OTHER_DELIMITER_INDEX); + if (changed) + emit delimiterChanged(m_delimiter); +} + +void KexiCSVDelimiterWidget::slotDelimiterLineEditReturnPressed() +{ + if (m_combo->currentItem() != KEXICSV_OTHER_DELIMITER_INDEX) + return; + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +void KexiCSVDelimiterWidget::slotDelimiterLineEditTextChanged( const QString & ) +{ + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +void KexiCSVDelimiterWidget::setDelimiter(const QString& delimiter) +{ + QValueVector::ConstIterator it = m_availableDelimiters.constBegin(); + int index = 0; + for (; it != m_availableDelimiters.constEnd(); ++it, index++) { + if (*it == delimiter) { + m_combo->setCurrentItem(index); + slotDelimiterChangedInternal(index); + return; + } + } + //else: set other (custom) delimiter + m_delimiterEdit->setText(delimiter); + m_combo->setCurrentItem(KEXICSV_OTHER_DELIMITER_INDEX); + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +//---------------------------------------------------- + +KexiCSVTextQuoteComboBox::KexiCSVTextQuoteComboBox( QWidget * parent ) + : KComboBox(parent, "KexiCSVTextQuoteComboBox") +{ + insertItem( "\"" ); + insertItem( "'" ); + insertItem( i18n( "None" ) ); +} + +QString KexiCSVTextQuoteComboBox::textQuote() const +{ + if (currentItem()==2) + return QString::null; + return currentText(); +} + +void KexiCSVTextQuoteComboBox::setTextQuote(const QString& textQuote) +{ + if (textQuote=="\"" || textQuote=="'") + setCurrentText(textQuote); + else if (textQuote.isEmpty()) + setCurrentText(i18n( "None" )); +} + +//---------------------------------------------------- + +KexiCSVInfoLabel::KexiCSVInfoLabel( const QString& labelText, QWidget* parent ) + : QWidget(parent, "KexiCSVInfoLabel") +{ + QVBoxLayout *vbox = new QVBoxLayout( this, 0, KDialogBase::spacingHint() ); + QHBoxLayout *hbox = new QHBoxLayout( this ); + vbox->addLayout(hbox); + m_leftLabel = new QLabel(labelText, this); + m_leftLabel->setMinimumWidth(130); + m_leftLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_leftLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addWidget(m_leftLabel); + m_iconLbl = new QLabel(this); + m_iconLbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_iconLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + m_fnameLbl = new KActiveLabel(this); + m_fnameLbl->setFocusPolicy(NoFocus); + m_fnameLbl->setTextFormat(Qt::PlainText); + m_fnameLbl->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding,1,0)); + m_fnameLbl->setLineWidth(1); + m_fnameLbl->setFrameStyle(QFrame::Box); + m_fnameLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addSpacing(5); + hbox->addWidget(m_iconLbl); + hbox->addWidget(m_fnameLbl, 1, Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addSpacing(10); + m_commentLbl = new KActiveLabel(this); + m_commentLbl->setFocusPolicy(NoFocus); + m_commentLbl->setTextFormat(Qt::PlainText); + m_commentLbl->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_commentLbl->setLineWidth(1); + m_commentLbl->setFrameStyle(QFrame::Box); + m_commentLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addWidget(m_commentLbl, 0, Qt::AlignVCenter | Qt::AlignRight | Qt::WordBreak); + + m_separator = new QFrame(this); + m_separator->setFrameShape(QFrame::HLine); + m_separator->setFrameShadow(QFrame::Sunken); + vbox->addWidget(m_separator); +} + +void KexiCSVInfoLabel::setFileName( const QString& fileName ) +{ + m_fnameLbl->setText( QDir::convertSeparators(fileName) ); + if (!fileName.isEmpty()) { + m_iconLbl->setPixmap( + KMimeType::pixmapForURL(KURL::fromPathOrURL(fileName), 0, KIcon::Desktop) ); + } +} + +void KexiCSVInfoLabel::setLabelText( const QString& text ) +{ + m_fnameLbl->setText( text ); +// int lines = m_fnameLbl->lines(); +// m_fnameLbl->setFixedHeight( +// QFontMetrics(m_fnameLbl->currentFont()).height() * lines ); +} + +void KexiCSVInfoLabel::setIcon(const QString& iconName) +{ + m_iconLbl->setPixmap( DesktopIcon(iconName) ); +} + +void KexiCSVInfoLabel::setCommentText( const QString& text ) +{ + m_commentLbl->setText(text); +} + +//---------------------------------------------------- + +QStringList csvMimeTypes() +{ + QStringList mimetypes; + mimetypes << "text/x-csv" << "text/plain" << "all/allfiles"; + return mimetypes; +} + +#include "kexicsvwidgets.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.h b/kexi/plugins/importexport/csv/kexicsvwidgets.h new file mode 100644 index 00000000..f128b658 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvwidgets.h @@ -0,0 +1,116 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_CSVWIDGETS_H +#define KEXI_CSVWIDGETS_H + +#include +#include + +class KLineEdit; +class KActiveLabel; +class QLabel; + +#define KEXICSV_DEFAULT_FILE_TEXT_QUOTE "\"" +#define KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE "" +#define KEXICSV_DEFAULT_FILE_DELIMITER "," +#define KEXICSV_DEFAULT_CLIPBOARD_DELIMITER "\t" +#define KEXICSV_DEFAULT_FILE_DELIMITER_INDEX 0 + +//! \return a list of mimetypes usable for handling CSV format handling +QStringList csvMimeTypes(); + +/*! @short A helper widget showing a short text information with an icon. + See ctor for details. + Used by CSV import and export dialogs. */ +class KexiCSVInfoLabel : public QWidget +{ + public: + /* Sets up a new info label \a labelText label with text like "Preview of data from file:". + setFileName() can be used to display filename and setCommentAfterFileName() to display + additional comment. + + The widget's layout can look like this: + + \pre [icon] [labeltext] [filename] [comment] + */ + KexiCSVInfoLabel( const QString& labelText, QWidget* parent ); + + void setFileName( const QString& fileName ); + void setLabelText( const QString& text ); + void setCommentText( const QString& text ); +// void setIconForFileName(); + + //! sets icon pixmap to \a iconName. Used wher setIconForFilename was false in ctor. + void setIcon(const QString& iconName); + + QLabel* leftLabel() const { return m_leftLabel; } + KActiveLabel* fileNameLabel() const { return m_fnameLbl; } + KActiveLabel* commentLabel() const { return m_commentLbl; } + QFrame* separator() const { return m_separator; } + + protected: + QLabel *m_leftLabel, *m_iconLbl; + KActiveLabel *m_fnameLbl, *m_commentLbl; + QFrame* m_separator; +}; + +//! @short A combo box widget providing a list of possible delimiters +//! Used by CSV import and export dialogs +class KexiCSVDelimiterWidget : public QWidget +{ + Q_OBJECT + + public: + KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent = 0 ); + + QString delimiter() const { return m_delimiter; } + void setDelimiter(const QString& delimiter); + + signals: + void delimiterChanged(const QString& delimiter); + + protected slots: + //! only called when a delimiter was set by user directly + void slotDelimiterChanged(int idx); + void slotDelimiterChangedInternal(int idx); + void slotDelimiterLineEditTextChanged( const QString & ); + void slotDelimiterLineEditReturnPressed(); + + protected: + QString m_delimiter; + QValueVector m_availableDelimiters; + KComboBox* m_combo; + KLineEdit* m_delimiterEdit; +}; + +//! @short A combo box widget providing a list of possible quote characters +//! Used by CSV import and export dialogs +class KexiCSVTextQuoteComboBox : public KComboBox +{ + public: + KexiCSVTextQuoteComboBox( QWidget * parent = 0 ); + + QString textQuote() const; + + //! Sets text quote. Only available are: ", ', and empty string. + void setTextQuote(const QString& textQuote); +}; + +#endif -- cgit v1.2.1