diff --git a/src/kexiutils/utils.cpp b/src/kexiutils/utils.cpp index f998eadd7..483e8f751 100644 --- a/src/kexiutils/utils.cpp +++ b/src/kexiutils/utils.cpp @@ -1,1206 +1,1210 @@ /* This file is part of the KDE project - Copyright (C) 2003-2017 Jarosław Staniek + Copyright (C) 2003-2018 Jarosław Staniek Contains code from kglobalsettings.cpp: Copyright (C) 2000, 2006 David Faure Copyright (C) 2008 Friedrich W. H. Kossebau Contains code from kdialog.cpp: Copyright (C) 1998 Thomas Tanghus (tanghus@earthling.net) Additions 1999-2000 by Espen Sand (espen@kde.org) and Holger Freyther 2005-2009 Olivier Goffart 2006 Tobias Koenig 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 "utils.h" #include "utils_p.h" #include "FontSettings_p.h" #include "kexiutils_global.h" #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 #ifndef KEXI_MOBILE #include #include #include #include #include #endif #include #include #include #include #include #if HAVE_LANGINFO_H #include #endif #ifdef Q_OS_WIN #include static QRgb qt_colorref2qrgb(COLORREF col) { return qRgb(GetRValue(col), GetGValue(col), GetBValue(col)); } #endif using namespace KexiUtils; DelayedCursorHandler::DelayedCursorHandler(QWidget *widget) : startedOrActive(false), m_widget(widget), m_handleWidget(widget) { m_timer.setSingleShot(true); connect(&m_timer, SIGNAL(timeout()), this, SLOT(show())); } void DelayedCursorHandler::start(bool noDelay) { startedOrActive = true; m_timer.start(noDelay ? 0 : 1000); } void DelayedCursorHandler::stop() { startedOrActive = false; m_timer.stop(); if (m_handleWidget && m_widget) { m_widget->unsetCursor(); } else { QApplication::restoreOverrideCursor(); } } void DelayedCursorHandler::show() { const QCursor waitCursor(Qt::WaitCursor); if (m_handleWidget && m_widget) { m_widget->unsetCursor(); m_widget->setCursor(waitCursor); } else { QApplication::restoreOverrideCursor(); QApplication::setOverrideCursor(waitCursor); } } Q_GLOBAL_STATIC(DelayedCursorHandler, _delayedCursorHandler) void KexiUtils::setWaitCursor(bool noDelay) { if (qobject_cast(qApp)) { _delayedCursorHandler->start(noDelay); } } void KexiUtils::removeWaitCursor() { if (qobject_cast(qApp)) { _delayedCursorHandler->stop(); } } WaitCursor::WaitCursor(bool noDelay) : m_handler(nullptr) { setWaitCursor(noDelay); } WaitCursor::WaitCursor(QWidget *widget, bool noDelay) { DelayedCursorHandler *handler = new DelayedCursorHandler(widget); handler->start(noDelay); m_handler = handler; } WaitCursor::~WaitCursor() { if (m_handler) { qobject_cast(m_handler)->stop(); delete m_handler; } else { removeWaitCursor(); } } WaitCursorRemover::WaitCursorRemover() { m_reactivateCursor = _delayedCursorHandler->startedOrActive; _delayedCursorHandler->stop(); } WaitCursorRemover::~WaitCursorRemover() { if (m_reactivateCursor) _delayedCursorHandler->start(true); } //-------------------------------------------------------------------------------- QObject* KexiUtils::findFirstQObjectChild(QObject *o, const char* className, const char* objName) { if (!o) return 0; const QObjectList list(o->children()); foreach(QObject *child, list) { if (child->inherits(className) && (!objName || child->objectName() == objName)) return child; } //try children foreach(QObject *child, list) { child = findFirstQObjectChild(child, className, objName); if (child) return child; } return 0; } QMetaProperty KexiUtils::findPropertyWithSuperclasses(const QObject* object, const char* name) { const int index = object->metaObject()->indexOfProperty(name); if (index == -1) return QMetaProperty(); return object->metaObject()->property(index); } bool KexiUtils::objectIsA(QObject* object, const QList& classNames) { foreach(const QByteArray& ba, classNames) { if (objectIsA(object, ba.constData())) return true; } return false; } QList KexiUtils::methodsForMetaObject( const QMetaObject *metaObject, QFlags types, QFlags access) { const int count = metaObject ? metaObject->methodCount() : 0; QList result; for (int i = 0; i < count; i++) { QMetaMethod method(metaObject->method(i)); if (types & method.methodType() && access & method.access()) result += method; } return result; } QList KexiUtils::methodsForMetaObjectWithParents( const QMetaObject *metaObject, QFlags types, QFlags access) { QList result; while (metaObject) { const int count = metaObject->methodCount(); for (int i = 0; i < count; i++) { QMetaMethod method(metaObject->method(i)); if (types & method.methodType() && access & method.access()) result += method; } metaObject = metaObject->superClass(); } return result; } QList KexiUtils::propertiesForMetaObject( const QMetaObject *metaObject) { const int count = metaObject ? metaObject->propertyCount() : 0; QList result; for (int i = 0; i < count; i++) result += metaObject->property(i); return result; } QList KexiUtils::propertiesForMetaObjectWithInherited( const QMetaObject *metaObject) { QList result; while (metaObject) { const int count = metaObject->propertyCount(); for (int i = 0; i < count; i++) result += metaObject->property(i); metaObject = metaObject->superClass(); } return result; } QStringList KexiUtils::enumKeysForProperty(const QMetaProperty& metaProperty, int filter) { QStringList result; const QMetaEnum enumerator(metaProperty.enumerator()); const int count = enumerator.keyCount(); int total = 0; for (int i = 0; i < count; i++) { if (filter == INT_MIN) { result.append(QString::fromLatin1(enumerator.key(i))); } else { const int v = enumerator.value(i); if ((v & filter) && !(total & v)) { // !(total & v) is a protection adding against masks result.append(QString::fromLatin1(enumerator.key(i))); total |= v; } } } return result; } //! @internal static QFileDialog* getImageDialog(QWidget *parent, const QString &caption, const QUrl &directory, const QList &supportedMimeTypes) { QFileDialog *dialog = new QFileDialog(parent, caption); dialog->setDirectoryUrl(directory); const QStringList mimeTypeFilters = KexiUtils::convertTypesUsingFunction(supportedMimeTypes); dialog->setMimeTypeFilters(mimeTypeFilters); return dialog; } QUrl KexiUtils::getOpenImageUrl(QWidget *parent, const QString &caption, const QUrl &directory) { QScopedPointer dialog( getImageDialog(parent, caption.isEmpty() ? i18n("Open") : caption, directory, QImageReader::supportedMimeTypes())); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (QDialog::Accepted == dialog->exec()) { return dialog->selectedUrls().value(0); } else { return QUrl(); } } QUrl KexiUtils::getSaveImageUrl(QWidget *parent, const QString &caption, const QUrl &directory) { QScopedPointer dialog( getImageDialog(parent, caption.isEmpty() ? i18n("Save") : caption, directory, QImageWriter::supportedMimeTypes())); dialog->setAcceptMode(QFileDialog::AcceptSave); if (QDialog::Accepted == dialog->exec()) { return dialog->selectedUrls().value(0); } else { return QUrl(); } } #ifndef KEXI_MOBILE -QUrl KexiUtils::getStartUrl(const QUrl &startDirOrVariable, QString *recentDirClass) +QUrl KexiUtils::getStartUrl(const QUrl &startDirOrVariable, QString *recentDirClass, + const QString &fileName) { QUrl result; if (recentDirClass) { result = KFileWidget::getStartUrl(startDirOrVariable, *recentDirClass); // Fix bug introduced by Kexi 3.0.x in KexiFileWidget: remove file protocol from path // (the KRecentDirs::add(.., dir.url()) call was invalid because of prepended protocol) const QString protocol("file:/"); if (result.path().startsWith(protocol) && !result.path().startsWith(protocol + '/')) { result.setPath(result.path().mid(protocol.length() - 1)); } + if (!fileName.isEmpty()) { + result.setPath(result.path() + '/' + fileName); + } } else { qWarning() << "Missing recentDirClass"; } return result; } void KexiUtils::addRecentDir(const QString &fileClass, const QString &directory) { KRecentDirs::add(fileClass, directory); } #endif bool KexiUtils::askForFileOverwriting(const QString& filePath, QWidget *parent) { QFileInfo fi(filePath); if (!fi.exists()) { return true; } const KMessageBox::ButtonCode res = KMessageBox::warningYesNo(parent, xi18nc("@info", "The file %1 already exists." "Do you want to overwrite it?", QDir::toNativeSeparators(filePath)), QString(), KStandardGuiItem::overwrite(), KStandardGuiItem::no()); return res == KMessageBox::Yes; } QColor KexiUtils::blendedColors(const QColor& c1, const QColor& c2, int factor1, int factor2) { return QColor( int((c1.red()*factor1 + c2.red()*factor2) / (factor1 + factor2)), int((c1.green()*factor1 + c2.green()*factor2) / (factor1 + factor2)), int((c1.blue()*factor1 + c2.blue()*factor2) / (factor1 + factor2))); } QColor KexiUtils::contrastColor(const QColor& c) { int g = qGray(c.rgb()); if (g > 110) return c.dark(200); else if (g > 80) return c.light(150); else if (g > 20) return c.light(300); return Qt::gray; } QColor KexiUtils::bleachedColor(const QColor& c, int factor) { int h, s, v; c.getHsv(&h, &s, &v); QColor c2; if (factor < 100) factor = 100; if (s >= 250 && v >= 250) //for colors like cyan or red, make the result more white s = qMax(0, s - factor - 50); else if (s <= 5 && v <= 5) v += factor - 50; c2.setHsv(h, s, qMin(255, v + factor - 100)); return c2; } QIcon KexiUtils::colorizeIconToTextColor(const QPixmap& icon, const QPalette& palette, QPalette::ColorRole role) { QPixmap pm( KIconEffect().apply(icon, KIconEffect::Colorize, 1.0f, palette.color(role), false)); KIconEffect::semiTransparent(pm); return QIcon(pm); } QPixmap KexiUtils::emptyIcon(KIconLoader::Group iconGroup) { QPixmap noIcon(IconSize(iconGroup), IconSize(iconGroup)); noIcon.fill(Qt::transparent); return noIcon; } static void drawOrScalePixmapInternal(QPainter* p, const QMargins& margins, const QRect& rect, QPixmap* pixmap, QPoint* pos, Qt::Alignment alignment, bool scaledContents, bool keepAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) { Q_ASSERT(pos); if (pixmap->isNull()) return; const bool fast = false; const int w = rect.width() - margins.left() - margins.right(); const int h = rect.height() - margins.top() - margins.bottom(); //! @todo we can optimize painting by drawing rescaled pixmap here //! and performing detailed painting later (using QTimer) // QPixmap pixmapBuffer; // QPainter p2; // QPainter *target; // if (fast) { // target = p; // } else { // target = &p2; // } //! @todo only create buffered pixmap of the minimum size and then do not fillRect() // target->fillRect(0,0,rect.width(),rect.height(), backgroundColor); *pos = rect.topLeft() + QPoint(margins.left(), margins.top()); if (scaledContents) { if (keepAspectRatio) { QImage img(pixmap->toImage()); img = img.scaled(w, h, Qt::KeepAspectRatio, transformMode); if (img.width() < w) { if (alignment & Qt::AlignRight) pos->setX(pos->x() + w - img.width()); else if (alignment & Qt::AlignHCenter) pos->setX(pos->x() + w / 2 - img.width() / 2); } else if (img.height() < h) { if (alignment & Qt::AlignBottom) pos->setY(pos->y() + h - img.height()); else if (alignment & Qt::AlignVCenter) pos->setY(pos->y() + h / 2 - img.height() / 2); } if (p) { p->drawImage(*pos, img); } else { *pixmap = QPixmap::fromImage(img); } } else { if (!fast) { *pixmap = pixmap->scaled(w, h, Qt::IgnoreAspectRatio, transformMode); if (p) { p->drawPixmap(*pos, *pixmap); } } } } else { if (alignment & Qt::AlignRight) pos->setX(pos->x() + w - pixmap->width()); else if (alignment & Qt::AlignHCenter) pos->setX(pos->x() + w / 2 - pixmap->width() / 2); else //left, etc. pos->setX(pos->x()); if (alignment & Qt::AlignBottom) pos->setY(pos->y() + h - pixmap->height()); else if (alignment & Qt::AlignVCenter) pos->setY(pos->y() + h / 2 - pixmap->height() / 2); else //top, etc. pos->setY(pos->y()); *pos += QPoint(margins.left(), margins.top()); if (p) { p->drawPixmap(*pos, *pixmap); } } } void KexiUtils::drawPixmap(QPainter* p, const QMargins& margins, const QRect& rect, const QPixmap& pixmap, Qt::Alignment alignment, bool scaledContents, bool keepAspectRatio, Qt::TransformationMode transformMode) { QPixmap px(pixmap); QPoint pos; drawOrScalePixmapInternal(p, margins, rect, &px, &pos, alignment, scaledContents, keepAspectRatio, transformMode); } QPixmap KexiUtils::scaledPixmap(const QMargins& margins, const QRect& rect, const QPixmap& pixmap, QPoint* pos, Qt::Alignment alignment, bool scaledContents, bool keepAspectRatio, Qt::TransformationMode transformMode) { QPixmap px(pixmap); drawOrScalePixmapInternal(0, margins, rect, &px, pos, alignment, scaledContents, keepAspectRatio, transformMode); return px; } bool KexiUtils::loadPixmapFromData(QPixmap *pixmap, const QByteArray &data, const char *format) { bool ok = pixmap->loadFromData(data, format); if (ok) { return true; } if (format) { return false; } const QList commonFormats({"png", "jpg", "bmp", "tif"}); QList formats(commonFormats); for(int i=0; ;) { ok = pixmap->loadFromData(data, formats[i]); if (ok) { return true; } ++i; if (i == formats.count()) {// try harder if (i == commonFormats.count()) { formats += QImageReader::supportedImageFormats(); if (formats.count() == commonFormats.count()) { break; // sanity check } } else { break; } } } return false; } void KexiUtils::setFocusWithReason(QWidget* widget, Qt::FocusReason reason) { if (!widget) return; QFocusEvent fe(QEvent::FocusIn, reason); QCoreApplication::sendEvent(widget, &fe); } void KexiUtils::unsetFocusWithReason(QWidget* widget, Qt::FocusReason reason) { if (!widget) return; QFocusEvent fe(QEvent::FocusOut, reason); QCoreApplication::sendEvent(widget, &fe); } //-------- void KexiUtils::adjustIfRtl(QMargins *margins) { if (margins && QGuiApplication::isRightToLeft()) { const int left = margins->left(); margins->setLeft(margins->right()); margins->setRight(left); } } //--------- Q_GLOBAL_STATIC(FontSettingsData, g_fontSettings) QFont KexiUtils::smallestReadableFont() { return g_fontSettings->font(FontSettingsData::SmallestReadableFont); } //--------------------- KTextEditorFrame::KTextEditorFrame(QWidget * parent, Qt::WindowFlags f) : QFrame(parent, f) { QEvent dummy(QEvent::StyleChange); changeEvent(&dummy); } void KTextEditorFrame::changeEvent(QEvent *event) { if (event->type() == QEvent::StyleChange) { if (style()->objectName() != "oxygen") // oxygen already nicely paints the frame setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); else setFrameStyle(QFrame::NoFrame); } } //--------------------- int KexiUtils::marginHint() { return QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin); } int KexiUtils::spacingHint() { return QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); } void KexiUtils::setStandardMarginsAndSpacing(QLayout *layout) { setMargins(layout, KexiUtils::marginHint()); layout->setSpacing(KexiUtils::spacingHint()); } void KexiUtils::setMargins(QLayout *layout, int value) { layout->setContentsMargins(value, value, value, value); } void KexiUtils::replaceColors(QPixmap* original, const QColor& color) { Q_ASSERT(original); QImage dest(original->toImage()); replaceColors(&dest, color); *original = QPixmap::fromImage(dest); } void KexiUtils::replaceColors(QImage* original, const QColor& color) { Q_ASSERT(original); *original = original->convertToFormat(QImage::Format_ARGB32_Premultiplied); QPainter p(original); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(original->rect(), color); } bool KexiUtils::isLightColorScheme() { return KColorScheme(QPalette::Active, KColorScheme::Window).background().color().lightness() >= 128; } int KexiUtils::dimmedAlpha() { return 150; } QPalette KexiUtils::paletteWithDimmedColor(const QPalette &pal, QPalette::ColorGroup group, QPalette::ColorRole role) { QPalette result(pal); QColor color(result.color(group, role)); color.setAlpha(dimmedAlpha()); result.setColor(group, role, color); return result; } QPalette KexiUtils::paletteWithDimmedColor(const QPalette &pal, QPalette::ColorRole role) { QPalette result(pal); QColor color(result.color(role)); color.setAlpha(dimmedAlpha()); result.setColor(role, color); return result; } QPalette KexiUtils::paletteForReadOnly(const QPalette &palette) { QPalette p(palette); p.setBrush(QPalette::Base, palette.brush(QPalette::Disabled, QPalette::Base)); p.setBrush(QPalette::Text, palette.brush(QPalette::Disabled, QPalette::Text)); p.setBrush(QPalette::Highlight, palette.brush(QPalette::Disabled, QPalette::Highlight)); p.setBrush(QPalette::HighlightedText, palette.brush(QPalette::Disabled, QPalette::HighlightedText)); return p; } void KexiUtils::setBackgroundColor(QWidget *widget, const QColor &color) { widget->setAutoFillBackground(true); QPalette pal(widget->palette()); pal.setColor(widget->backgroundRole(), color); widget->setPalette(pal); } //--------------------- void KexiUtils::installRecursiveEventFilter(QObject *object, QObject *filter) { if (!object || !filter || !object->isWidgetType()) return; // qDebug() << "Installing event filter on widget:" << object // << "directed to" << filter->objectName(); object->installEventFilter(filter); const QObjectList list(object->children()); foreach(QObject *obj, list) { installRecursiveEventFilter(obj, filter); } } void KexiUtils::removeRecursiveEventFilter(QObject *object, QObject *filter) { object->removeEventFilter(filter); if (!object->isWidgetType()) return; const QObjectList list(object->children()); foreach(QObject *obj, list) { removeRecursiveEventFilter(obj, filter); } } PaintBlocker::PaintBlocker(QWidget* parent) : QObject(parent) , m_enabled(true) { parent->installEventFilter(this); } void PaintBlocker::setEnabled(bool set) { m_enabled = set; } bool PaintBlocker::enabled() const { return m_enabled; } bool PaintBlocker::eventFilter(QObject* watched, QEvent* event) { if (m_enabled && watched == parent() && event->type() == QEvent::Paint) { return true; } return false; } tristate KexiUtils::openHyperLink(const QUrl &url, QWidget *parent, const OpenHyperlinkOptions &options) { #ifdef KEXI_MOBILE //! @todo Q_UNUSED(url) Q_UNUSED(parent) Q_UNUSED(options) #else if (url.isLocalFile()) { QFileInfo fileInfo(url.toLocalFile()); if (!fileInfo.exists()) { KMessageBox::sorry(parent, xi18nc("@info", "The file or directory %1 does not exist.", fileInfo.absoluteFilePath())); return false; } } if (!url.isValid()) { KMessageBox::sorry(parent, xi18nc("@info", "Invalid hyperlink %1.", url.url(QUrl::PreferLocalFile))); return false; } QMimeDatabase db; QString type = db.mimeTypeForUrl(url).name(); if (!options.allowExecutable && KRun::isExecutableFile(url, type)) { KMessageBox::sorry(parent, xi18nc("@info", "Executable %1 not allowed.", url.url(QUrl::PreferLocalFile))); return false; } if (!options.allowRemote && !url.isLocalFile()) { KMessageBox::sorry(parent, xi18nc("@info", "Remote hyperlink %1 not allowed.", url.url(QUrl::PreferLocalFile))); return false; } if (KRun::isExecutableFile(url, type)) { int ret = KMessageBox::questionYesNo(parent , xi18nc("@info", "Do you want to run this file?" "Running executables can be dangerous.") , QString() , KGuiItem(xi18nc("@action:button Run script file", "Run"), koIconName("system-run")) , KStandardGuiItem::no() , "AllowRunExecutable", KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Yes) { return cancelled; } } switch(options.tool) { case OpenHyperlinkOptions::DefaultHyperlinkTool: #if KIO_VERSION >= QT_VERSION_CHECK(5, 31, 0) return KRun::runUrl(url, type, parent, KRun::RunFlags(KRun::RunExecutables)); #else return KRun::runUrl(url, type, parent); #endif case OpenHyperlinkOptions::BrowserHyperlinkTool: return QDesktopServices::openUrl(url); case OpenHyperlinkOptions::MailerHyperlinkTool: return QDesktopServices::openUrl(url); default:; } #endif return false; } // ---- KexiDBDebugTreeWidget::KexiDBDebugTreeWidget(QWidget *parent) : QTreeWidget(parent) { } void KexiDBDebugTreeWidget::copy() { if (currentItem()) { qApp->clipboard()->setText(currentItem()->text(0)); } } // ---- DebugWindow::DebugWindow(QWidget * parent) : QWidget(parent, Qt::Window) { } // ---- QSize KexiUtils::comboBoxArrowSize(QStyle *style) { if (!style) { style = QApplication::style(); } QStyleOptionComboBox cbOption; return style->subControlRect(QStyle::CC_ComboBox, &cbOption, QStyle::SC_ComboBoxArrow).size(); } void KexiUtils::addDirtyFlag(QString *text) { Q_ASSERT(text); *text = xi18nc("'Dirty (modified) object' flag", "%1*", *text); } //! From klocale_kde.cpp //! @todo KEXI3 support other OS-es (use from klocale_*.cpp) static QByteArray systemCodeset() { QByteArray codeset; #if HAVE_LANGINFO_H // Qt since 4.2 always returns 'System' as codecForLocale and KDE (for example // KEncodingFileDialog) expects real encoding name. So on systems that have langinfo.h use // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own // workaround codeset = nl_langinfo(CODESET); if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) { // means ascii, "C"; QTextCodec doesn't know, so avoid warning codeset = "ISO-8859-1"; } #endif return codeset; } QTextCodec* g_codecForEncoding = 0; bool setEncoding(int mibEnum) { QTextCodec *codec = QTextCodec::codecForMib(mibEnum); if (codec) { g_codecForEncoding = codec; } return codec != 0; } //! From klocale_kde.cpp static void initEncoding() { if (!g_codecForEncoding) { // This all made more sense when we still had the EncodingEnum config key. QByteArray codeset = systemCodeset(); if (!codeset.isEmpty()) { QTextCodec *codec = QTextCodec::codecForName(codeset); if (codec) { setEncoding(codec->mibEnum()); } } else { setEncoding(QTextCodec::codecForLocale()->mibEnum()); } if (!g_codecForEncoding) { qWarning() << "Cannot resolve system encoding, defaulting to ISO 8859-1."; const int mibDefault = 4; // ISO 8859-1 setEncoding(mibDefault); } Q_ASSERT(g_codecForEncoding); } } QByteArray KexiUtils::encoding() { initEncoding(); return g_codecForEncoding->name(); } namespace { //! @internal for graphicEffectsLevel() class GraphicEffectsLevel { public: GraphicEffectsLevel() { KConfigGroup g(KSharedConfig::openConfig(), "KDE-Global GUI Settings"); // Asking for hasKey we do not ask for graphicEffectsLevelDefault() that can // contain some very slow code. If we can save that time, do it. (ereslibre) if (g.hasKey("GraphicEffectsLevel")) { value = ((GraphicEffects) g.readEntry("GraphicEffectsLevel", QVariant((int) NoEffects)).toInt()); return; } // For now, let always enable animations by default. The plan is to make // this code a bit smarter. (ereslibre) value = ComplexAnimationEffects; } GraphicEffects value; }; } Q_GLOBAL_STATIC(GraphicEffectsLevel, g_graphicEffectsLevel) GraphicEffects KexiUtils::graphicEffectsLevel() { return g_graphicEffectsLevel->value; } #if defined Q_OS_UNIX && !defined Q_OS_MACOS //! For detectedDesktopSession() class DetectedDesktopSession { public: DetectedDesktopSession() : name(detect()), isKDE(name == QStringLiteral("KDE")) { } const QByteArray name; const bool isKDE; private: static QByteArray detect() { // https://www.freedesktop.org/software/systemd/man/pam_systemd.html#%24XDG_SESSION_DESKTOP // KDE, GNOME, UNITY, LXDE, MATE, XFCE... const QString xdgSessionDesktop = qgetenv("XDG_SESSION_DESKTOP").trimmed(); if (!xdgSessionDesktop.isEmpty()) { return xdgSessionDesktop.toLatin1().toUpper(); } // Similar to detectDesktopEnvironment() from qgenericunixservices.cpp const QString xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").trimmed(); if (!xdgCurrentDesktop.isEmpty()) { return xdgCurrentDesktop.toLatin1().toUpper(); } // fallbacks if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) { return QByteArrayLiteral("KDE"); } if (!qEnvironmentVariableIsEmpty("GNOME_DESKTOP_SESSION_ID")) { return QByteArrayLiteral("GNOME"); } const QString desktopSession = qgetenv("DESKTOP_SESSION").trimmed(); if (desktopSession.compare("gnome", Qt::CaseInsensitive) == 0) { return QByteArrayLiteral("GNOME"); } else if (desktopSession.compare("xfce", Qt::CaseInsensitive) == 0) { return QByteArrayLiteral("XFCE"); } return QByteArray(); } }; Q_GLOBAL_STATIC(DetectedDesktopSession, s_detectedDesktopSession) QByteArray KexiUtils::detectedDesktopSession() { return s_detectedDesktopSession->name; } bool KexiUtils::isKDEDesktopSession() { return s_detectedDesktopSession->isKDE; } bool KexiUtils::shouldUseNativeDialogs() { #if defined Q_OS_UNIX && !defined Q_OS_MACOS return isKDEDesktopSession() || detectedDesktopSession().isEmpty(); #else return true; #endif } //! @return value of XFCE property @a property for channel @a channel //! Sets the value pointed by @a ok to status. //! @todo Should be part of desktop integration or KF static QByteArray xfceSettingValue(const QByteArray &channel, const QByteArray &property, bool *ok = nullptr) { if (ok) { *ok = false; } QByteArray result; const QString program = QString::fromLatin1("xfconf-query"); const QString programPath = QStandardPaths::findExecutable(program); const QStringList arguments{ program, "-c", channel, "-p", property }; QProcess process; process.start(programPath, arguments);//, QIODevice::ReadOnly | QIODevice::Text); qDebug() << "a" << int(process.exitCode()); if (!process.waitForStarted()) { qWarning() << "Count not execute command" << programPath << arguments << "error:" << process.error(); return QByteArray(); } const int exitCode = process.exitCode(); // !=0 e.g. for "no such property or channel" if (exitCode != 0 || !process.waitForFinished() || process.exitStatus() != QProcess::NormalExit) { qWarning() << "Count not finish command" << programPath << arguments << "error:" << process.error() << "exit code:" << exitCode << "exit status:" << process.exitStatus(); return QByteArray(); } if (ok) { *ok = true; } result = process.readAll(); result.chop(1); return result; } #else QByteArray KexiUtils::detectedDesktopSession() { return QByteArray(); } #endif bool KexiUtils::activateItemsOnSingleClick(QWidget *widget) { const KConfigGroup mainWindowGroup = KSharedConfig::openConfig()->group("MainWindow"); #ifdef Q_OS_WIN return mainWindowGroup.readEntry("SingleClickOpensItem", true); #else if (mainWindowGroup.hasKey("SingleClickOpensItem")) { return mainWindowGroup.readEntry("SingleClickOpensItem", true); } const QByteArray desktopSession = detectedDesktopSession(); if (desktopSession == "XFCE") { /* To test: Set to true: fconf-query -c xfce4-desktop -p /desktop-icons/single-click -n -t bool -s true Get value: xfconf-query -c xfce4-desktop -p /desktop-icons/single-click Reset: xfconf-query -c xfce4-desktop -p /desktop-icons/single-click -r */ return xfceSettingValue("xfce4-desktop", "/desktop-icons/single-click") == "true"; } # if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) Q_UNUSED(widget) return QApplication::styleHints()->singleClickActivation(); # else QStyle *style = widget ? widget->style() : QApplication::style(); return style->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, widget); # endif #endif } // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp QColor KexiUtils::inactiveTitleColor() { #ifdef Q_OS_WIN return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTION)); #else KConfigGroup g(KSharedConfig::openConfig(), "WM"); return g.readEntry("inactiveBackground", QColor(224, 223, 222)); #endif } // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp QColor KexiUtils::inactiveTextColor() { #ifdef Q_OS_WIN return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTIONTEXT)); #else KConfigGroup g(KSharedConfig::openConfig(), "WM"); return g.readEntry("inactiveForeground", QColor(75, 71, 67)); #endif } // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp QColor KexiUtils::activeTitleColor() { #ifdef Q_OS_WIN return qt_colorref2qrgb(GetSysColor(COLOR_ACTIVECAPTION)); #else KConfigGroup g(KSharedConfig::openConfig(), "WM"); return g.readEntry("activeBackground", QColor(48, 174, 232)); #endif } // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp QColor KexiUtils::activeTextColor() { #ifdef Q_OS_WIN return qt_colorref2qrgb(GetSysColor(COLOR_CAPTIONTEXT)); #else KConfigGroup g(KSharedConfig::openConfig(), "WM"); return g.readEntry("activeForeground", QColor(255, 255, 255)); #endif } QString KexiUtils::makeStandardCaption(const QString &userCaption, CaptionFlags flags) { QString caption = KAboutData::applicationData().displayName(); if (caption.isEmpty()) { return QCoreApplication::instance()->applicationName(); } QString captionString = userCaption.isEmpty() ? caption : userCaption; // If the document is modified, add '[modified]'. if (flags & ModifiedCaption) { captionString += QString::fromUtf8(" [") + xi18n("modified") + QString::fromUtf8("]"); } if (!userCaption.isEmpty()) { // Add the application name if: // User asked for it, it's not a duplication and the app name (caption()) is not empty if (flags & AppNameCaption && !caption.isEmpty() && !userCaption.endsWith(caption)) { // TODO: check to see if this is a transient/secondary window before trying to add the app name // on platforms that need this captionString += xi18nc("Document/application separator in titlebar", " – ") + caption; } } return captionString; } QString themedIconName(const QString &name) { static bool firstUse = true; if (firstUse) { // workaround for some kde-related crash const bool _unused = KIconLoader::global()->iconPath(name, KIconLoader::NoGroup, true).isEmpty(); Q_UNUSED(_unused); firstUse = false; } // try load themed icon const QColor background = qApp->palette().background().color(); const bool useDarkIcons = background.value() > 100; return QLatin1String(useDarkIcons ? "dark_" : "light_") + name; } QIcon themedIcon(const QString &name) { const QString realName(themedIconName(name)); const QIcon icon = QIcon::fromTheme(realName); // fallback if (icon.isNull()) { return QIcon::fromTheme(name); } return icon; } QString KexiUtils::localizedStringToHtmlSubstring(const KLocalizedString& string) { return string.toString(Kuit::RichText) .remove(QLatin1String("")).remove(QLatin1String("")); } bool KexiUtils::cursorAtEnd(const QLineEdit *lineEdit) { if (!lineEdit) { return false; } if (lineEdit->inputMask().isEmpty()) { return lineEdit->cursorPosition() >= lineEdit->displayText().length(); } else { return lineEdit->cursorPosition() >= (lineEdit->displayText().length() - 1); } } QDebug operator<<(QDebug dbg, const QDomNode &node) { QString s; QTextStream str(&s, QIODevice::WriteOnly); node.save(str, 2); dbg << qPrintable(s); return dbg; } diff --git a/src/kexiutils/utils.h b/src/kexiutils/utils.h index 68fee742e..fc00999af 100644 --- a/src/kexiutils/utils.h +++ b/src/kexiutils/utils.h @@ -1,731 +1,733 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 Jarosław Staniek Contains code from kglobalsettings.h: Copyright (C) 2000, 2006 David Faure Copyright (C) 2008 Friedrich W. H. Kossebau Contains code from kdialog.h: Copyright (C) 1998 Thomas Tanghus (tanghus@earthling.net) Additions 1999-2000 by Espen Sand (espen@kde.org) and Holger Freyther 2005-2009 Olivier Goffart 2006 Tobias Koenig 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 KEXIUTILS_UTILS_H #define KEXIUTILS_UTILS_H #include "kexiutils_export.h" #include "kexi_global.h" #include #include #include #include #include #include #include #include #include class QColor; class QDomNode; class QMetaProperty; class QLayout; class QLineEdit; class KLocalizedString; //! @short General Utils namespace KexiUtils { //! \return true if parent of \a o that is of type \a className or false otherwise inline bool parentIs(QObject* o, const char* className = 0) { if (!o) return false; while ((o = o->parent())) { if (o->inherits(className)) return true; } return false; } //! \return parent object of \a o that is of type \a type or 0 if no such parent template inline type findParentByType(QObject* o) { if (!o) return 0; while ((o = o->parent())) { if (dynamic_cast< type >(o)) return dynamic_cast< type >(o); } return 0; } /*! \return first found child of \a o, inheriting \a className. If objName is 0 (the default), all object names match. Returned pointer type is casted. */ KEXIUTILS_EXPORT QObject* findFirstQObjectChild(QObject *o, const char* className, const char* objName); /*! \return first found child of \a o, that inherit \a className. If \a objName is 0 (the default), all object names match. Returned pointer type is casted. */ template inline type findFirstChild(QObject *o, const char* className, const char* objName = 0) { return ::qobject_cast< type >(findFirstQObjectChild(o, className, objName)); } //! Finds property for name \a name and object \a object returns it index; //! otherwise returns a null QMetaProperty. KEXIUTILS_EXPORT QMetaProperty findPropertyWithSuperclasses(const QObject* object, const char* name); //! \return true if \a object object is of class name \a className inline bool objectIsA(QObject* object, const char* className) { return 0 == qstrcmp(object->metaObject()->className(), className); } //! \return true if \a object object is of the class names inside \a classNames KEXIUTILS_EXPORT bool objectIsA(QObject* object, const QList& classNames); //! \return a list of methods for \a metaObject meta object. //! The methods are of type declared in \a types and have access declared //! in \a access. KEXIUTILS_EXPORT QList methodsForMetaObject( const QMetaObject *metaObject, QFlags types = QFlags(QMetaMethod::Method | QMetaMethod::Signal | QMetaMethod::Slot), QFlags access = QFlags(QMetaMethod::Private | QMetaMethod::Protected | QMetaMethod::Public)); //! Like \ref KexiUtils::methodsForMetaObject() but includes methods from all //! parent meta objects of the \a metaObject. KEXIUTILS_EXPORT QList methodsForMetaObjectWithParents( const QMetaObject *metaObject, QFlags types = QFlags(QMetaMethod::Method | QMetaMethod::Signal | QMetaMethod::Slot), QFlags access = QFlags(QMetaMethod::Private | QMetaMethod::Protected | QMetaMethod::Public)); //! \return a list with all this class's properties. KEXIUTILS_EXPORT QList propertiesForMetaObject( const QMetaObject *metaObject); //! \return a list with all this class's properties including thise inherited. KEXIUTILS_EXPORT QList propertiesForMetaObjectWithInherited( const QMetaObject *metaObject); //! \return a list of enum keys for meta property \a metaProperty. //! If @a filter is not INT_MIN, the method only returns enum keys that overlap with filter //! and are not combination of other keys. KEXIUTILS_EXPORT QStringList enumKeysForProperty(const QMetaProperty &metaProperty, int filter = INT_MIN); //! Convert a list @a list of @a SourceType type to another list of @a DestinationType //! type using @a convertMethod function /*! Example: @code QList list = ....; QStringList result = KexiUtils::convertTypesUsingFunction(list); @endcode */ template QList convertTypesUsingFunction(const QList &list) { QList result; foreach(const SourceType &element, list) { result.append(ConvertFunction(element)); } return result; } //! Convert a list @a list of @a SourceType type to another list of @a DestinationType //! type using @a convertMethod /*! Example: @code QVariantList list = ....; QStringList result = KexiUtils::convertTypesUsingMethod(list); @endcode */ template QList convertTypesUsingMethod(const QList &list) { QList result; foreach(const SourceType &element, list) { result.append((element.*ConvertMethod)()); } return result; } /*! Sets "wait" cursor with 1 second delay (or 0 seconds if noDelay is true). Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */ KEXIUTILS_EXPORT void setWaitCursor(bool noDelay = false); /*! Remove "wait" cursor previously set with \a setWaitCursor(), even if it's not yet visible. Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */ KEXIUTILS_EXPORT void removeWaitCursor(); /*! Helper class. Allocate it in your code block as follows: KexiUtils::WaitCursor wait; .. and wait cursor will be visible (with one second delay) until you're in this block, without a need to call removeWaitCursor() before exiting the block. Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */ class KEXIUTILS_EXPORT WaitCursor { public: //! Wait cursor handler for application explicit WaitCursor(bool noDelay = false); //! @overload //! Wait cursor handler for widget @a widget explicit WaitCursor(QWidget *widget, bool noDelay = false); ~WaitCursor(); private: QObject *m_handler; }; /*! Helper class. Allocate it in your code block as follows: KexiUtils::WaitCursorRemover remover; .. and the wait cursor will be hidden unless you leave this block, without a need to call setWaitCursor() before exiting the block. After leaving the codee block, the cursor will be visible again, if it was visible before creating the WaitCursorRemover object. Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */ class KEXIUTILS_EXPORT WaitCursorRemover { public: WaitCursorRemover(); ~WaitCursorRemover(); private: bool m_reactivateCursor; }; /*! Creates a modal file dialog which returns the selected url of image filr to open or an empty string if none was chosen. Like KFileDialog::getImageOpenUrl(). */ //! @todo KEXI3 add equivalent of kfiledialog:/// KEXIUTILS_EXPORT QUrl getOpenImageUrl(QWidget *parent = 0, const QString &caption = QString(), const QUrl &directory = QUrl()); /*! Creates a modal file dialog with returns the selected url of image file to save or an empty string if none was chosen. Like KFileDialog::getSaveUrl(). */ //! @todo KEXI3 add equivalent of kfiledialog:/// KEXIUTILS_EXPORT QUrl getSaveImageUrl(QWidget *parent = 0, const QString &caption = QString(), const QUrl &directory = QUrl()); #ifndef KEXI_MOBILE /** * This method implements the logic to determine the user's default directory * to be listed. E.g. the documents directory, home directory or a recently * used directory. * * Use instead of KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass). * * @param startDir A URL specifying the initial directory, or using the - * @c kfiledialog:/// syntax to specify a last used - * directory. If this URL specifies a file name, it is - * ignored. Refer to the KFileWidget::KFileWidget() - * documentation for the @c kfiledialog:/// URL syntax. + * @c kfiledialog:/// syntax to specify a last used directory. + * If the string includes a file name, it is ignored. + * Refer to the KFileWidget::KFileWidget() documentation for the @c kfiledialog:/// + * URL syntax. * @param recentDirClass If the @c kfiledialog:/// syntax is used, this * will return the string to be passed to KRecentDirs::dir() and - * KRecentDirs::add(). + * KRecentDirs::add(). Optional file name is supported. + * @param fileName Optional file name that is added to the resulting URL. * @return The URL that should be listed by default (e.g. by KFileDialog or * KDirSelectDialog). * * @see KFileWidget::KFileWidget() * @since 3.1.0 * @todo Make it independent of KIOFileWidgets */ -KEXIUTILS_EXPORT QUrl getStartUrl(const QUrl &startDirOrVariable, QString *recentDirClass); +KEXIUTILS_EXPORT QUrl getStartUrl(const QUrl &startDirOrVariable, QString *recentDirClass, + const QString &fileName = QString()); /** * Associates @p directory with @p fileClass * * Use instead of KRecentDirs::add() * * @since 3.1.0 * @todo Make it independent of KIOFileWidgets */ KEXIUTILS_EXPORT void addRecentDir(const QString &fileClass, const QString &directory); #endif // !KEXI_MOBILE /*! Displays a "The file %1 already exists. Do you want to overwrite it?" Yes/No message box. @a parent is used as a parent of the message box. @return @c true if @a filePath file does not exist or user has agreed to overwrite it; returns @c false if user does not agree to overwrite it. */ KEXIUTILS_EXPORT bool askForFileOverwriting(const QString& filePath, QWidget *parent = nullptr); /*! A global setting for minimal readable font. This can be used in dockers, rulers and other places where space is at a premium. @see QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont). @todo Add integration with KDE platform theme (how to detect it?); for now we don't assume it's installed */ KEXIUTILS_EXPORT QFont smallestReadableFont(); /*! \return a color being a result of blending \a c1 with \a c2 with \a factor1 and \a factor1 factors: (c1*factor1+c2*factor2)/(factor1+factor2). */ KEXIUTILS_EXPORT QColor blendedColors(const QColor& c1, const QColor& c2, int factor1 = 1, int factor2 = 1); /*! \return a contrast color for a color \a c: If \a c is light color, darker color created using c.dark(200) is returned; otherwise lighter color created using c.light(200) is returned. */ KEXIUTILS_EXPORT QColor contrastColor(const QColor& c); /*! \return a lighter color for a color \a c and a factor \a factor. For colors like Qt::red or Qt::green where hue and saturation are near to 255, hue is decreased so the result will be more bleached. For black color the result is dark gray rather than black. */ KEXIUTILS_EXPORT QColor bleachedColor(const QColor& c, int factor); /*! \return icon set computed as a result of colorizing \a icon pixmap with \a role color of \a palette palette. This function is useful for displaying monochromed icons on the list view or table view header, to avoid bloat, but still have the color compatible with accessibility settings. */ KEXIUTILS_EXPORT QIcon colorizeIconToTextColor(const QPixmap& icon, const QPalette& palette, QPalette::ColorRole role = QPalette::ButtonText); /*! Replaces colors in pixmap @a original using @a color color. Used for coloring bitmaps that have to reflect the foreground color. */ KEXIUTILS_EXPORT void replaceColors(QPixmap* original, const QColor& color); /*! Replaces colors in image @a original using @a color color. Used for coloring bitmaps that have to reflect the foreground color. */ KEXIUTILS_EXPORT void replaceColors(QImage* original, const QColor& color); /*! @return true if curent color scheme is light. Lightness of window background is checked to measure this. */ KEXIUTILS_EXPORT bool isLightColorScheme(); /*! @return alpha value for dimmed color (150). */ KEXIUTILS_EXPORT int dimmedAlpha(); /*! @return palette @a pal with dimmed color @a role. @see dimmedAlpha() */ KEXIUTILS_EXPORT QPalette paletteWithDimmedColor(const QPalette &pal, QPalette::ColorGroup group, QPalette::ColorRole role); /*! @overload paletteWithDimmedColor(const QPalette &, QPalette::ColorGroup, QPalette::ColorRole) */ KEXIUTILS_EXPORT QPalette paletteWithDimmedColor(const QPalette &pal, QPalette::ColorRole role); /*! @return palette altered for indicating "read only" flag. */ KEXIUTILS_EXPORT QPalette paletteForReadOnly(const QPalette &palette); /*! Convenience function that sets background color @a color for widget @a widget. "autoFillBackground" property is set to true for the widget. Background color role is obtained from QWidget::backgroundRole(). */ KEXIUTILS_EXPORT void setBackgroundColor(QWidget *widget, const QColor &color); /*! \return empty (fully transparent) pixmap that can be used as a place for icon of size \a iconGroup */ KEXIUTILS_EXPORT QPixmap emptyIcon(KIconLoader::Group iconGroup); #ifdef KEXI_DEBUG_GUI //! Creates debug window for convenient debugging output KEXIUTILS_EXPORT QWidget *createDebugWindow(QWidget *parent); //! Connects push button action to \a receiver and its \a slot. This allows to execute debug-related actions //! using buttons displayed in the debug window. KEXIUTILS_EXPORT void connectPushButtonActionForDebugWindow(const char* actionName, const QObject *receiver, const char* slot); #endif //! Sets focus for widget \a widget with reason \a reason. KEXIUTILS_EXPORT void setFocusWithReason(QWidget* widget, Qt::FocusReason reason); //! Unsets focus for widget \a widget with reason \a reason. KEXIUTILS_EXPORT void unsetFocusWithReason(QWidget* widget, Qt::FocusReason reason); //! If the application's layout direction, swaps left and right margins. //! @see QGuiApplication::isRightToLeft() void adjustIfRtl(QMargins *margins); //! Draws pixmap @a pixmap on painter @a p using predefined parameters. //! Used in KexiDBImageBox and KexiBlobTableEdit. KEXIUTILS_EXPORT void drawPixmap(QPainter* p, const QMargins& margins, const QRect& rect, const QPixmap& pixmap, Qt::Alignment alignment, bool scaledContents, bool keepAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation); //! Scales pixmap @a pixmap on painter @a p using predefined parameters. //! Used in KexiDBImageBox and KexiBlobTableEdit. KEXIUTILS_EXPORT QPixmap scaledPixmap(const QMargins& margins, const QRect& rect, const QPixmap& pixmap, QPoint* pos, Qt::Alignment alignment, bool scaledContents, bool keepAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation); //! This function should be used instead of QPixmap::loadFromData(). /** Loads a pixmap from @a data into @a pixmap. First tries to detect format, on failure * tries to load most common formats: png, jpg, bmp, tif. Then tries to load any of format * returned by QImageReader::supportedImageFormats(). * If @a format is provided, only this format is tried. * @return on success. * @note This function exists because QPixmap::loadFromData() not always works when there * are broken image format plugins installed (as it was the case with KRA plugin). * @todo Idea: Support while/black list of supported image formats. It would be useful * for security reasons because files can be loaded from remote locations. * @todo For the black/white list an enum describing local/remote data source is needed. */ KEXIUTILS_EXPORT bool loadPixmapFromData(QPixmap *pixmap, const QByteArray &data, const char *format = nullptr); //! A helper for automatic deleting of contents of containers. template class ContainerDeleter { public: explicit ContainerDeleter(Container& container) : m_container(container) {} ~ContainerDeleter() { clear(); } void clear() { qDeleteAll(m_container); m_container.clear(); } private: Container& m_container; }; /*! A modified QFrame which sets up sunken styled panel frame style depending on the current widget style. The widget also reacts on style changes. */ class KEXIUTILS_EXPORT KTextEditorFrame : public QFrame { Q_OBJECT public: explicit KTextEditorFrame(QWidget * parent = 0, Qt::WindowFlags f = 0); protected: virtual void changeEvent(QEvent *event); }; /** * Returns the number of pixels that should be used between a * dialog edge and the outermost widget(s) according to the KDE standard. * * Copied from QDialog. * * @deprecated Use the style's pixelMetric() function to query individual margins. * Different platforms may use different values for the four margins. */ KEXIUTILS_EXPORT int marginHint(); /** * Returns the number of pixels that should be used between * widgets inside a dialog according to the KDE standard. * * Copied from QDialog. * * @deprecated Use the style's layoutSpacing() function to query individual spacings. * Different platforms may use different values depending on widget types and pairs. */ KEXIUTILS_EXPORT int spacingHint(); /*! Sets KexiUtils::marginHint() margins and KexiUtils::spacingHint() spacing for the layout @a layout. */ KEXIUTILS_EXPORT void setStandardMarginsAndSpacing(QLayout *layout); /*! Sets the same @a value for layout @a layout margins. */ KEXIUTILS_EXPORT void setMargins(QLayout *layout, int value); //! sometimes we leave a space in the form of empty QFrame and want to insert here //! a widget that must be instantiated by hand. //! This macro inserts a widget \a what into a frame \a where. #define GLUE_WIDGET(what, where) \ { QVBoxLayout *lyr = new QVBoxLayout(where); \ lyr->addWidget(what); } //! A tool for setting temporary value for boolean variable. /*! After destruction of the instance, the variable is set back to the original value. This class is useful in recursion guards. To use it, declare class atrribute of type bool and block it, e.g.: @code bool m_myNonRecursiveFunctionEnabled; // ... set m_myNonRecursiveFunctionEnabled initially to true void myNonRecursiveFunctionEnabled() { if (!m_myNonRecursiveFunctionEnabled) return; KexiUtils::BoolBlocker guard(&m_myNonRecursiveFunctionEnabled, false); // function's body guarded against recursion... } @endcode */ class KEXIUTILS_EXPORT BoolBlocker { public: inline BoolBlocker(bool *var, bool tempValue) : v(var), origValue(*var) { *var = tempValue; } inline ~BoolBlocker() { *v = origValue; } private: bool *v; bool origValue; }; /*! This helper function install an event filter on @a object and all of its children, directed to @a filter. */ KEXIUTILS_EXPORT void installRecursiveEventFilter(QObject *object, QObject *filter); /*! This helper function removes an event filter installed before on @a object and all of its children. */ KEXIUTILS_EXPORT void removeRecursiveEventFilter(QObject *object, QObject *filter); //! Blocks paint events on specified widget. /*! Works recursively. Useful when widget should be hidden without changing geometry it takes. */ class KEXIUTILS_EXPORT PaintBlocker : public QObject { Q_OBJECT public: explicit PaintBlocker(QWidget* parent); void setEnabled(bool set); bool enabled() const; protected: virtual bool eventFilter(QObject* watched, QEvent* event); private: bool m_enabled; }; /*! * \short Options for opening of hyperlinks * \sa openHyperLink() */ class KEXIUTILS_EXPORT OpenHyperlinkOptions : public QObject { Q_OBJECT public: /*! * A tool used for opening hyperlinks */ enum HyperlinkTool{ DefaultHyperlinkTool, /*!< Default tool for a given type of the hyperlink */ BrowserHyperlinkTool, /*!< Opens hyperlink in a browser */ MailerHyperlinkTool /*!< Opens hyperlink in a default mailer */ }; Q_ENUM(HyperlinkTool) OpenHyperlinkOptions() : tool(DefaultHyperlinkTool) , allowExecutable(false) , allowRemote(false) {} HyperlinkTool tool; bool allowExecutable; bool allowRemote; }; /*! * Opens the given \a url using \a options * It can fail if opening a given link is not possible or allowed. * @return true on success, cancelled if user has cancelled the opening and false on failure */ KEXIUTILS_EXPORT tristate openHyperLink(const QUrl &url, QWidget *parent, const OpenHyperlinkOptions &options); //! \return size of combo box arrow according to \a style /*! Application's style is the default. \see QStyle::SC_ComboBoxArrow */ KEXIUTILS_EXPORT QSize comboBoxArrowSize(QStyle *style = 0); //! Adds a dirty ("document modified") flag to @a text according to current locale. //! It is usually "*" character appended. KEXIUTILS_EXPORT void addDirtyFlag(QString *text); //! @return The name of the user's preferred encoding //! Based on KLocale::encoding() KEXIUTILS_EXPORT QByteArray encoding(); //! The desired level of effects on the GUI enum GraphicEffect { NoEffects = 0x0000, ///< GUI with no effects at all. GradientEffects = 0x0001, ///< GUI with only gradients enabled. SimpleAnimationEffects = 0x0002, ///< GUI with simple animations enabled. ComplexAnimationEffects = 0x0006 ///< GUI with complex animations enabled. ///< Note that ComplexAnimationsEffects implies SimpleAnimationEffects. }; Q_DECLARE_FLAGS(GraphicEffects, GraphicEffect) //! @return the desired level of effects on the GUI. //! @note A copy of KGlobalSettings::graphicEffectsLevel() needed for porting from kdelibs4. KEXIUTILS_EXPORT GraphicEffects graphicEffectsLevel(); //! @return the inactive titlebar background color //! @note A copy of KGlobalSettings::inactiveTitleColor() needed for porting from kdelibs4. KEXIUTILS_EXPORT QColor inactiveTitleColor(); //! @return the inactive titlebar text (foreground) color //! @note A copy of KGlobalSettings::inactiveTextColor() needed for porting from kdelibs4. KEXIUTILS_EXPORT QColor inactiveTextColor(); //! @return the active titlebar background color //! @note A copy of KGlobalSettings::activeTitleColor() needed for porting from kdelibs4. KEXIUTILS_EXPORT QColor activeTitleColor(); //! @return the active titlebar text (foreground) color //! @note A copy of KGlobalSettings::activeTextColor() needed for porting from kdelibs4. KEXIUTILS_EXPORT QColor activeTextColor(); //! @return Paper White color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor paperWhite() { return QColor(0xfcfcfc); } //! @return Cardboard Grey color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor cardboardGrey() { return QColor(0xeff0f1); } //! @return Alternative to Normal background (Cardboard Grey), //! see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor cardboardGreyAlternative() { return QColor(0xbdc3c7); } //! @return Icon Grey color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor iconGrey() { return QColor(0x4d4d4d); } //! @return Charcoal Grey color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor charcoalGrey() { return QColor(0x31363b); } //! @return Charcoal Grey color made a bit darker, suitable for disabled dark base, //! see https://techbase.kde.org/Projects/Usability/HIG/Color //inline QColor charcoalGreyDarker() { return charcoalGrey().darker(125); } //! @return Shade Black color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor shadeBlack() { return QColor(0x232629); } //! @return Shade Black color made a bit lighter, suitable for alternate base, //! see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor shadeBlackLighter() { return shadeBlack().lighter(125); } //! @return Shade Black color, see https://techbase.kde.org/Projects/Usability/HIG/Color inline QColor plasmaBlue() { return QColor(0x3daee9); } /*! @return @c true if whether the app runs in a single click mode (the default). @c false if returned if the app runs in double click mode. The flag is checked in two stages. Stage 1. Application config file's "SingleClickOpensItem" value in "MainWindow" group is checked. If it exists, the value is returned. On Windows if it does not exist, @c true is returned, on other systems Stage 2 checking is performed. Stage 2. For Qt < 5.5 this information is taken from @a widget widget's style. If there is no widget specified, QApplication::style() is used. For Qt >= 5.5 the result is equal to QApplication::styleHints()->singleClickActivation() and @a widget is ignored. @note This is a replacement for bool KGlobalSettings::singleClick(). */ KEXIUTILS_EXPORT bool activateItemsOnSingleClick(QWidget *widget = 0); /** * Detects name of desktop session based on environment variables. * * Possible value are like GNOME, XFCE, KDE. They are always uppercase. Following environment * variables are used: XDG_SESSION_DESKTOP. XDG_CURRENT_DESKTOP, DESKTOP_SESSION, KDE_FULL_SESSION, * GNOME_DESKTOP_SESSION_ID. * * @return empty string if no desktop session was detected or sessions are not supported for the * running OS (Windows, macOS, non-desktop OS). * * @note use QApplication::styleHints() if possible. */ KEXIUTILS_EXPORT QByteArray detectedDesktopSession(); /** * @return true is this is a KDE / Plasma desktop session * * Detection is based on detectedDesktopSession(). */ KEXIUTILS_EXPORT bool isKDEDesktopSession(); /** * @brief Returns @c true if native operating system's dialogs should be used * * Returns @c false if Qt's standard dialogs should be used instead of the operating system native * dialogs. Can be used with QColorDialog, QFileDialog and QFontDialog. * * Depends on the curent desktop in use: * - on Unix (other than macOS) returns @c true if isKDEDesktopSession() is @c true or if desktop * session can't be detected, @c false for other desktops * - @c true for all other operating systems, i.e. for MS Windows, macOS, etc. * * @todo Share this code with KReport and Kexi */ KEXIUTILS_EXPORT bool shouldUseNativeDialogs(); /** * @enum CaptionFlag * Used to specify how to construct a window caption * * @value AppNameCaption Indicates that the method shall include * the application name when making the caption string. * @value ModifiedCaption Causes a 'modified' sign will be included in the * returned string. This is useful when indicating that a file is * modified, i.e., it contains data that has not been saved. */ enum CaptionFlag { NoCaptionFlags = 0, AppNameCaption = 1, ModifiedCaption = 2 }; Q_DECLARE_FLAGS(CaptionFlags, CaptionFlag) /** * Builds a caption that contains the application name along with the * userCaption using a standard layout. * * To make a compliant caption for your window, simply do: * @p setWindowTitle(makeStandardCaption(yourCaption)); * * To ensure that the caption is appropriate to the desktop in which the * application is running, pass in a pointer to the window the caption will * be applied to. * * @param userCaption The caption string you want to display in the * window caption area. Do not include the application name! * @param flags * @return the created caption * Based on KDialog::makeStandardCaption() */ KEXIUTILS_EXPORT QString makeStandardCaption(const QString &userCaption, CaptionFlags flags = AppNameCaption); /** * Return rich text for @a string. Equivalent of KLocalizedString::toString(Kuit::RichText) * but and is removed so the result can be used as %* argument in other string. */ KEXIUTILS_EXPORT QString localizedStringToHtmlSubstring(const KLocalizedString& string); /** * @return @c true if text cursor is at the end of the line edit @a lineEdit. * If the @a lineEdit edit has input mask, cursor is at the end if it's at position * lineEdit->displayText().length() - 1 or further. If the @a lineEdit has no input mask, cursor * is at the end if it's at position lineEdit->text().length() or further. */ KEXIUTILS_EXPORT bool cursorAtEnd(const QLineEdit *lineEdit); } //namespace KexiUtils /** * Writes the DOM tree to the stream and returns a reference to the stream. */ KEXIUTILS_EXPORT QDebug operator<<(QDebug dbg, const QDomNode &node); #endif //KEXIUTILS_UTILS_H diff --git a/src/plugins/importexport/csv/kexicsvexportwizard.cpp b/src/plugins/importexport/csv/kexicsvexportwizard.cpp index ea2491050..a98f6be2d 100644 --- a/src/plugins/importexport/csv/kexicsvexportwizard.cpp +++ b/src/plugins/importexport/csv/kexicsvexportwizard.cpp @@ -1,430 +1,452 @@ /* This file is part of the KDE project Copyright (C) 2012 Oleg Kukharchuk - Copyright (C) 2005-2017 Jarosław Staniek + Copyright (C) 2005-2018 Jarosław 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 +#include #include +#include +#include #include #include #include +namespace { + const QString DEFAULT_EXTENSION("csv"); + + //! Adds extension if missing + //! @todo Move to KexiFileWidgetInterface or so + void addExtensionIfNeeded(QString *fileName) { + QMimeDatabase db; + const QMimeType currentMimeType(db.mimeTypeForFile(*fileName, QMimeDatabase::MatchExtension)); + qDebug() << currentMimeType.name(); + if (!fileName->isEmpty() && currentMimeType.isDefault()) { // no known extension, add + fileName->append('.' + DEFAULT_EXTENSION); + } + } +} + KexiCSVExportWizard::KexiCSVExportWizard(const KexiCSVExport::Options& options, QWidget * parent) : KAssistantDialog(parent) , m_options(options) , m_importExportGroup(KSharedConfig::openConfig()->group("ImportExport")) { KexiMainWindowIface::global()->setReasonableDialogSize(this); - buttonBox()->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); if (m_options.mode == KexiCSVExport::Clipboard) { //! @todo KEXI3 ? - button(QDialogButtonBox::Ok)->setText(xi18n("Copy")); + finishButton()->setText(xi18n("Copy")); } else { - button(QDialogButtonBox::Ok)->setText(xi18n("Export")); + finishButton()->setText(xi18n("Export")); } QString infoLblFromText; QString captionOrName; KexiGUIMessageHandler msgh(this); KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection(); if (m_options.useTempQuery) { m_tableOrQuery = new KDbTableOrQuerySchema(KexiMainWindowIface::global()->unsavedQuery(options.itemId)); captionOrName = conn->querySchema(m_options.itemId)->captionOrName(); } else { m_tableOrQuery = new KDbTableOrQuerySchema(conn, m_options.itemId); captionOrName = m_tableOrQuery->captionOrName(); } if (m_tableOrQuery->table()) { if (m_options.mode == KexiCSVExport::Clipboard) { setWindowTitle(xi18nc("@title:window", "Copy Data From Table to Clipboard")); infoLblFromText = xi18n("Copying data from table:"); } else { setWindowTitle(xi18nc("@title:window", "Export Data From Table to CSV File")); infoLblFromText = xi18n("Exporting data from table:"); } } else if (m_tableOrQuery->query()) { if (m_options.mode == KexiCSVExport::Clipboard) { setWindowTitle(xi18nc("@title:window", "Copy Data From Query to Clipboard")); infoLblFromText = xi18n("Copying data from table:"); } else { setWindowTitle(xi18nc("@title:window", "Export Data From Query to CSV File")); infoLblFromText = xi18n("Exporting data from query:"); } } else { msgh.showErrorMessage(conn->result(), KDbMessageHandler::Error, xi18n("Could not open data for exporting.")); m_canceled = true; return; } QString text = "\n" + captionOrName; int m_recordCount = conn->recordCount(m_tableOrQuery); int columns = m_tableOrQuery->fieldCount(conn); text += "\n"; if (m_recordCount > 0) text += xi18n("(rows: %1, columns: %2)", m_recordCount, columns); else text += xi18n("(columns: %1)", columns); infoLblFromText.append(text); // OK, source data found. // Setup pages // 1. File Save Page if (m_options.mode == KexiCSVExport::File) { - const QUrl url("kfiledialog:///CSVImportExport"); // startDir + QString defaultFileName(KDbUtils::stringToFileName(captionOrName)); + addExtensionIfNeeded(&defaultFileName); m_fileIface = KexiFileWidgetInterface::createWidget( - url, KexiFileFilters::CustomSavingFileBasedDB, this); + QUrl("kfiledialog:///CSVImportExport"), KexiFileFilters::CustomSavingFileBasedDB, + defaultFileName, this); m_fileIface->setAdditionalMimeTypes(csvMimeTypes()); - m_fileIface->setDefaultExtension("csv"); - //TODO m_fileSaveWidget->setLocationText( - // KDbUtils::stringToFileName(captionOrName)); + m_fileIface->setDefaultExtension(DEFAULT_EXTENSION); m_fileSavePage = new KPageWidgetItem(m_fileIface->widget(), xi18n("Enter Name of File You Want to Save Data To")); addPage(m_fileSavePage); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); } /* 2. Export options m_exportOptionsPage exportOptionsLyr m_infoLblFrom m_infoLblTo m_showOptionsButton m_exportOptionsSection exportOptionsSectionLyr */ m_exportOptionsWidget = new QWidget(this); m_exportOptionsWidget->setObjectName("m_exportOptionsPage"); QGridLayout *exportOptionsLyr = new QGridLayout(m_exportOptionsWidget); exportOptionsLyr->setObjectName("exportOptionsLyr"); m_infoLblFrom = new KexiCSVInfoLabel(infoLblFromText, m_exportOptionsWidget, true/*showFnameLine*/); KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId( QString("org.kexi-project.%1").arg(m_tableOrQuery->table() ? "table" : "query")); if (partInfo) { m_infoLblFrom->setIcon(partInfo->iconName()); } m_infoLblFrom->separator()->hide(); m_infoLblFrom->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_infoLblFrom, 0, 0, 1, 2); m_infoLblTo = new KexiCSVInfoLabel( (m_options.mode == KexiCSVExport::File) ? xi18n("To CSV file:") : xi18n("To clipboard."), m_exportOptionsWidget, true/*showFnameLine*/); if (m_options.mode == KexiCSVExport::Clipboard) m_infoLblTo->setIcon(koIconName("edit-paste")); m_infoLblTo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_infoLblTo, 1, 0, 1, 2); exportOptionsLyr->setRowStretch(2, 1); m_showOptionsButton = new QPushButton(xi18n("Show Options >>")); m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked())); exportOptionsLyr->addWidget(m_showOptionsButton, 3, 1, Qt::AlignRight); // - m_exportOptionsSection = new QGroupBox(""/*xi18n("Options")*/); m_exportOptionsSection->setObjectName("m_exportOptionsSection"); m_exportOptionsSection->setAlignment(Qt::Vertical); m_exportOptionsSection->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_exportOptionsSection, 4, 0, 1, 2); QGridLayout *exportOptionsSectionLyr = new QGridLayout; exportOptionsLyr->setObjectName("exportOptionsLyr"); m_exportOptionsSection->setLayout(exportOptionsSectionLyr); // -delimiter QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:")); exportOptionsSectionLyr->addWidget(delimiterLabel, 0, 0); m_delimiterWidget = new KexiCSVDelimiterWidget(false /* !lineEditOnBottom*/); m_delimiterWidget->setDelimiter(defaultDelimiter()); delimiterLabel->setBuddy(m_delimiterWidget); exportOptionsSectionLyr->addWidget(m_delimiterWidget, 0, 1); // -text quote QLabel *textQuoteLabel = new QLabel(xi18n("Text quote:")); exportOptionsSectionLyr->addWidget(textQuoteLabel, 1, 0); QWidget *textQuoteWidget = new QWidget; QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget); m_textQuote = new KexiCSVTextQuoteComboBox(textQuoteWidget); m_textQuote->setTextQuote(defaultTextQuote()); textQuoteLabel->setBuddy(m_textQuote); textQuoteLyr->addWidget(m_textQuote); textQuoteLyr->addStretch(0); exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1); // - character encoding QLabel *characterEncodingLabel = new QLabel(xi18n("Text encoding:")); exportOptionsSectionLyr->addWidget(characterEncodingLabel, 2, 0); m_characterEncodingCombo = new KexiCharacterEncodingComboBox(); m_characterEncodingCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); characterEncodingLabel->setBuddy(m_characterEncodingCombo); exportOptionsSectionLyr->addWidget(m_characterEncodingCombo, 2, 1); // - checkboxes m_addColumnNamesCheckBox = new QCheckBox(xi18n("Add column names as the first row")); m_addColumnNamesCheckBox->setChecked(true); exportOptionsSectionLyr->addWidget(m_addColumnNamesCheckBox, 3, 1); m_defaultsBtn = new QPushButton(xi18n("Defaults"), this); connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked())); exportOptionsLyr->addWidget(m_defaultsBtn, 5, 0); exportOptionsLyr->setColumnStretch(1, 1); m_alwaysUseCheckBox = new QCheckBox( m_options.mode == KexiCSVExport::Clipboard ? xi18n("Always use above options for copying") : xi18n("Always use above options for exporting")); exportOptionsLyr->addWidget(m_alwaysUseCheckBox, 5, 1, Qt::AlignRight); m_exportOptionsSection->hide(); m_defaultsBtn->hide(); m_alwaysUseCheckBox->hide(); // - m_exportOptionsPage = new KPageWidgetItem(m_exportOptionsWidget, m_options.mode == KexiCSVExport::Clipboard ? xi18n("Copying") : xi18n("Exporting")); addPage(m_exportOptionsPage); // load settings 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)); } // -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); updateGeometry(); } KexiCSVExportWizard::~KexiCSVExportWizard() { delete m_tableOrQuery; } bool KexiCSVExportWizard::canceled() const { return m_canceled; } void KexiCSVExportWizard::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev) { Q_UNUSED(prev) if (page == m_fileSavePage) { m_fileIface->widget()->setFocus(); } else if (page == m_exportOptionsPage) { if (m_options.mode == KexiCSVExport::File) m_infoLblTo->setFileName(selectedFile()); } } QString KexiCSVExportWizard::selectedFile() const { return m_fileIface->selectedFile(); } void KexiCSVExportWizard::next() { if (currentPage() == m_fileSavePage) { + const QString selectedFile(this->selectedFile()); + QString newSelectedFile(selectedFile); + addExtensionIfNeeded(&newSelectedFile); + if (selectedFile != newSelectedFile) { + m_fileIface->setSelectedFile(newSelectedFile); + } if (!m_fileIface->checkSelectedFile()) { return; } KAssistantDialog::next(); return; } KAssistantDialog::next(); } void KexiCSVExportWizard::done(int result) { KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection(); if (QDialog::Accepted == result) { if (m_fileSavePage) { //qDebug() << selectedFile(); m_options.fileName = selectedFile(); } m_options.delimiter = m_delimiterWidget->delimiter(); m_options.textQuote = m_textQuote->textQuote(); m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked(); if (!KexiCSVExport::exportData(conn, m_tableOrQuery, m_options)) return; //store options 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"); } else if (QDialog::Rejected == result) { //nothing to do } KAssistantDialog::done(result); } void KexiCSVExportWizard::slotShowOptionsButtonClicked() { if (m_exportOptionsSection->isVisible()) { m_showOptionsButton->setText(xi18n("Show Options >>")); m_exportOptionsSection->hide(); m_alwaysUseCheckBox->hide(); m_defaultsBtn->hide(); } else { m_showOptionsButton->setText(xi18n("Hide Options <<")); m_exportOptionsSection->show(); m_alwaysUseCheckBox->show(); m_defaultsBtn->show(); } } 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 m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue); } QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue) { return m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue); } void KexiCSVExportWizard::writeEntry(const char *key, const QString& value) { m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value); } void KexiCSVExportWizard::writeEntry(const char *key, bool value) { m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value); } void KexiCSVExportWizard::deleteEntry(const char *key) { m_importExportGroup.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; } diff --git a/src/plugins/importexport/csv/kexicsvimportdialog.cpp b/src/plugins/importexport/csv/kexicsvimportdialog.cpp index 7fa30e8b4..4ed34d35a 100644 --- a/src/plugins/importexport/csv/kexicsvimportdialog.cpp +++ b/src/plugins/importexport/csv/kexicsvimportdialog.cpp @@ -1,2180 +1,2177 @@ /* This file is part of the KDE project Copyright (C) 2005-2017 Jarosław Staniek Copyright (C) 2012 Oleg Kukharchuk This work is based on kspread/dialogs/kspread_dlg_csv.cc. 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 "kexicsvimportdialog.h" #include "KexiCSVImportDialogModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexicsvwidgets.h" #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 #define _IMPORT_ICON koIconNeededWithSubs("change to file_import or so", "file_import","table") //! @internal An item delegate for KexiCSVImportDialog's table view class KexiCSVImportDialogItemDelegate : public QStyledItemDelegate { Q_OBJECT public: KexiCSVImportDialogItemDelegate(QObject *parent = 0); virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; KexiCSVImportDialogItemDelegate::KexiCSVImportDialogItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QWidget* KexiCSVImportDialogItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem newOption(option); QWidget *editor = QStyledItemDelegate::createEditor(parent, newOption, index); if (editor && index.row() == 0) { QFont f(editor->font()); f.setBold(true); editor->setFont(f); } return editor; } // -- //! @internal class KexiCSVImportStatic { public: KexiCSVImportStatic() : types(QVector() << KDbField::Text << KDbField::Integer << KDbField::Double << KDbField::Boolean << KDbField::Date << KDbField::Time << KDbField::DateTime) { typeNames.insert(KDbField::Text, KDbField::typeGroupName(KDbField::TextGroup)); typeNames.insert(KDbField::Integer, KDbField::typeGroupName(KDbField::IntegerGroup)); typeNames.insert(KDbField::Double, KDbField::typeGroupName(KDbField::FloatGroup)); typeNames.insert(KDbField::Boolean, KDbField::typeName(KDbField::Boolean)); typeNames.insert(KDbField::Date, KDbField::typeName(KDbField::Date)); typeNames.insert(KDbField::Time, KDbField::typeName(KDbField::Time)); typeNames.insert(KDbField::DateTime, KDbField::typeName(KDbField::DateTime)); for (int i = 0; i < types.size(); ++i) { indicesForTypes.insert(types[i], i); } } const QVector types; QHash typeNames; QHash indicesForTypes; }; Q_GLOBAL_STATIC(KexiCSVImportStatic, kexiCSVImportStatic) #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 #define MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW 1930 #define PROGRESS_STEP_MS (1000/5) // 5 updates per second static bool shouldSaveRow(int row, bool firstRowForFieldNames) { return row > (firstRowForFieldNames ? 1 : 0); } // -- class Q_DECL_HIDDEN KexiCSVImportDialog::Private { public: Private() : imported(false) { } ~Private() { qDeleteAll(m_uniquenessTest); } void clearDetectedTypes() { m_detectedTypes.clear(); } void clearUniquenessTests() { qDeleteAll(m_uniquenessTest); m_uniquenessTest.clear(); } KDbField::Type detectedType(int col) const { return m_detectedTypes.value(col, KDbField::InvalidType); } void setDetectedType(int col, KDbField::Type type) { if (m_detectedTypes.count() <= col) { for (int i = m_detectedTypes.count(); i < col; ++i) { // append missing bits m_detectedTypes.append(KDbField::InvalidType); } m_detectedTypes.append(type); } else { m_detectedTypes[col] = type; } } QList* uniquenessTest(int col) const { return m_uniquenessTest.value(col); } void setUniquenessTest(int col, QList* test) { if (m_uniquenessTest.count() <= col) { for (int i = m_uniquenessTest.count(); i < col; ++i) { // append missing bits m_uniquenessTest.append(0); } m_uniquenessTest.append(test); } else { m_uniquenessTest[col] = test; } } bool imported; private: //! vector of detected types //! @todo more types QList m_detectedTypes; //! m_detectedUniqueColumns[i]==true means that i-th column has unique values //! (only for numeric type) QList< QList* > m_uniquenessTest; }; // -- KexiCSVImportDialog::KexiCSVImportDialog(Mode mode, QWidget * parent) : KAssistantDialog(parent), m_parseComments(false), m_canceled(false), m_adjustRows(true), m_startline(0), m_textquote(QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0]), m_commentSymbol(QString(KEXICSV_DEFAULT_COMMENT_START)[0]), m_mode(mode), m_columnsAdjusted(false), m_firstFillTableCall(true), m_blockUserEvents(false), m_primaryKeyColumn(-1), m_dialogCanceled(false), m_conn(0), m_fieldsListModel(0), m_destinationTableSchema(0), m_implicitPrimaryKeyAdded(false), m_allRowsLoadedInPreview(false), m_stoppedAt_MAX_BYTES_TO_PREVIEW(false), m_stringNo("no"), m_stringI18nNo(xi18n("no")), m_stringFalse("false"), m_stringI18nFalse(xi18n("false")), m_partItemForSavedTable(0), m_importInProgress(false), m_importCanceled(false), d(new Private) { setWindowTitle( mode == File ? xi18nc("@title:window", "Import CSV Data From File") : xi18nc("@title:window", "Paste CSV Data From Clipboard") ); setWindowIcon(_IMPORT_ICON); //! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard setObjectName("KexiCSVImportDialog"); setSizeGripEnabled(true); KexiMainWindowIface::global()->setReasonableDialogSize(this); KGuiItem::assign(configureButton(), KStandardGuiItem::configure()); + KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); finishButton()->setEnabled(false); backButton()->setEnabled(false); KConfigGroup importExportGroup(KSharedConfig::openConfig()->group("ImportExport")); m_maximumRowsForPreview = importExportGroup.readEntry( "MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW); m_maximumBytesForPreview = importExportGroup.readEntry( "MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW); m_minimumYearFor100YearSlidingWindow = importExportGroup.readEntry( "MinimumYearFor100YearSlidingWindow", MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW); m_pkIcon = KexiSmallIcon("database-key"); if (m_mode == File) { createFileOpenPage(); } else if (m_mode == Clipboard) { QString subtype("plain"); m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); /* debug for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++) qDebug() << i << ": " << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i); */ } else { return; } m_file = 0; m_inputStream = 0; createOptionsPage(); createImportMethodPage(); createTableNamePage(); createImportPage(); /** @todo reuse Clipboard too! */ /*if ( m_mode == Clipboard ) { setWindowTitle( xi18n( "Inserting From Clipboard" ) ); QMimeSource * mime = QApplication::clipboard()->data(); if ( !mime ) { KMessageBox::information( this, xi18n("There is no data in the clipboard.") ); m_canceled = true; return; } if ( !mime->provides( "text/plain" ) ) { KMessageBox::information( this, xi18n("There is no usable data in the clipboard.") ); m_canceled = true; return; } m_fileArray = QByteArray(mime->encodedData( "text/plain" ) ); } else if ( mode == File ) {*/ m_dateRegExp = QRegularExpression("^(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})$"); m_timeRegExp1 = QRegularExpression("^(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$"); m_timeRegExp2 = QRegularExpression("^(\\d{1,2}):(\\d{1,2})$"); m_fpNumberRegExp1 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+$"); // E notation, e.g. 0.1e2, 0.1e+2, 0.1e-2, 0.1E2, 0.1E+2, 0.1E-2 m_fpNumberRegExp2 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+[Ee][+-]{0,1}\\d+$"); m_loadingProgressDlg = 0; if (m_mode == Clipboard) { m_infoLbl->setIcon(koIconName("edit-paste")); } m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_formatCombo, SIGNAL(activated(int)), this, SLOT(formatChanged(int))); connect(m_delimiterWidget, SIGNAL(delimiterChanged(QString)), this, SLOT(delimiterChanged(QString))); connect(m_commentWidget, SIGNAL(commentSymbolChanged(QString)), this, SLOT(commentSymbolChanged(QString))); connect(m_startAtLineSpinBox, SIGNAL(valueChanged(int)), this, SLOT(startlineSelected(int))); connect(m_comboQuote, SIGNAL(activated(int)), this, SLOT(textquoteSelected(int))); connect(m_tableView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentCellChanged(QModelIndex,QModelIndex))); connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)), this, SLOT(ignoreDuplicatesChanged(int))); connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)), this, SLOT(slot1stRowForFieldNamesChanged(int))); connect(configureButton(), &QPushButton::clicked, this, &KexiCSVImportDialog::optionsButtonClicked); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); KexiUtils::installRecursiveEventFilter(this, this); if ( m_mode == Clipboard ) initLater(); } KexiCSVImportDialog::~KexiCSVImportDialog() { delete m_file; delete m_inputStream; delete d; } void KexiCSVImportDialog::next() { KPageWidgetItem *curPage = currentPage(); if (curPage == m_openFilePage) { if (m_fileIface->checkSelectedFile()) { m_fname = m_fileIface->selectedFile(); } else { return; } if (!openData()) { return; } } else if (curPage == m_optionsPage) { const int numRows(m_table->rowCount()); if (numRows == 0) return; //impossible if (numRows == 1) { if (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Data set contains no rows. Do you want to import empty table?"))) return; } } else if (curPage == m_saveMethodPage) { if (m_newTableOption->isChecked()) { m_tableNameWidget->setCurrentIndex(0); m_newTableWidget->setFocus(); } else { m_tableNameWidget->setCurrentIndex(1); m_tablesList->setFocus(); } } else if (curPage == m_tableNamePage) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTableOption->isChecked()) { m_partItemForSavedTable->setCaption(m_newTableWidget->captionText()); m_partItemForSavedTable->setName(m_newTableWidget->nameText()); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); KDbObject tmp; tristate res = (part && part->info()) ? m_conn->loadObjectData( project->typeIdForPluginId(part->info()->pluginId()), m_newTableWidget->nameText(), &tmp) : false; if (res == true) { KMessageBox::information(this, "

" + part->i18nMessage("Object %1 already exists.", 0) .subs(m_newTableWidget->nameText()).toString() + "

" + xi18n("Please choose other name.") + "

" ); return; } else if (res == false) { qFatal("Plugin org.kexi-project.table not found"); return; } } else { m_partItemForSavedTable = m_tablesList->selectedPartItem(); } } KAssistantDialog::next(); } void KexiCSVImportDialog::slotShowSchema(KexiPart::Item *item) { if (!item) { return; } nextButton()->setEnabled(true); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema(conn, item->identifier()); m_tableCaptionLabel->setText(tableOrQuery->captionOrName()); m_tableNameLabel->setText(tableOrQuery->name()); m_recordCountLabel->setText(QString::number(conn->recordCount(tableOrQuery))); m_colCountLabel->setText(QString::number(tableOrQuery->fieldCount(conn))); delete m_fieldsListModel; m_fieldsListModel = new KexiFieldListModel(m_fieldsListView, ShowDataTypes); m_fieldsListModel->setSchema(conn, tableOrQuery); m_fieldsListView->setModel(m_fieldsListModel); m_fieldsListView->header()->resizeSections(QHeaderView::ResizeToContents); } void KexiCSVImportDialog::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev) { nextButton()->setEnabled(page == m_saveMethodPage ? false : true); finishButton()->setEnabled(page == m_importPage ? true : false); - if (page == m_importPage) { - KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); - } configureButton()->setEnabled(page == m_optionsPage); nextButton()->setEnabled(page == m_importPage ? false : true); backButton()->setEnabled(page == m_openFilePage ? false : true); if (page == m_saveMethodPage && prev == m_tableNamePage && m_partItemForSavedTable) { if (m_newTableOption->isChecked()) { KexiMainWindowIface::global()->project()->deleteUnstoredItem(m_partItemForSavedTable); } m_partItemForSavedTable = 0; } if(page == m_optionsPage){ if (m_mode == File) { m_loadingProgressDlg = new QProgressDialog(this); m_loadingProgressDlg->setObjectName("m_loadingProgressDlg"); m_loadingProgressDlg->setLabelText( xi18nc("@info", "Loading CSV Data from %1...", QDir::toNativeSeparators(m_fname))); m_loadingProgressDlg->setWindowTitle(xi18nc("@title:window", "Loading CSV Data")); m_loadingProgressDlg->setModal(true); m_loadingProgressDlg->setMaximum(m_maximumRowsForPreview); m_loadingProgressDlg->show(); } // delimiterChanged(detectedDelimiter); // this will cause fillTable() m_detectDelimiter = true; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { // m_loadingProgressDlg->hide(); // m_loadingProgressDlg->close(); QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); m_tableView->setFocus(); } else if (page == m_saveMethodPage) { m_newTableOption->setFocus(); } else if (page == m_tableNamePage) { if (m_newTableOption->isChecked() && !m_partItemForSavedTable) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); //get suggested name based on the file name QString suggestedName; if (m_mode == File) { suggestedName = QUrl(m_fname).fileName(); //remove extension if (!suggestedName.isEmpty()) { const int idx = suggestedName.lastIndexOf('.'); if (idx != -1) { suggestedName = suggestedName.mid(0, idx).simplified(); } } } KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } //-new part item m_partItemForSavedTable = project->createPartItem(part->info(), suggestedName); if (!m_partItemForSavedTable) { msg.showErrorMessage(project->result()); return; } m_newTableWidget->setCaptionText(m_partItemForSavedTable->caption()); m_newTableWidget->setNameText(m_partItemForSavedTable->name()); m_newTableWidget->captionLineEdit()->setFocus(); m_newTableWidget->captionLineEdit()->selectAll(); } else if (!m_newTableOption->isChecked()) { KexiPart::Item *i = m_tablesList->selectedPartItem(); if (!i) { nextButton()->setEnabled(false); } slotShowSchema(i); } } else if (page == m_importPage) { m_fromLabel->setFileName(m_fname); m_toLabel->setFileNameText(m_partItemForSavedTable->name()); m_importingProgressBar->hide(); m_importProgressLabel->hide(); } } void KexiCSVImportDialog::createFileOpenPage() { m_fileIface = KexiFileWidgetInterface::createWidget(QUrl("kfiledialog:///CSVImportExport"), KexiFileFilters::CustomOpening, this); m_fileIface->setAdditionalMimeTypes(csvMimeTypes()); m_fileIface->setDefaultExtension("csv"); m_fileIface->connectFileSelectedSignal(this, SLOT(next())); m_openFilePage = new KPageWidgetItem(m_fileIface->widget(), xi18n("Select Import Filename")); addPage(m_openFilePage); } void KexiCSVImportDialog::createOptionsPage() { QWidget *m_optionsWidget = new QWidget(this); QVBoxLayout *lyr = new QVBoxLayout(m_optionsWidget); m_infoLbl = new KexiCSVInfoLabel( m_mode == File ? xi18n("Preview of data from file:") : xi18n("Preview of data from clipboard"), m_optionsWidget, m_mode == File /*showFnameLine*/ ); lyr->addWidget(m_infoLbl); QWidget* page = new QFrame(m_optionsWidget); QGridLayout *glyr = new QGridLayout(page); lyr->addWidget(page); // Delimiter: comma, semicolon, tab, space, other m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page); glyr->addWidget(m_delimiterWidget, 1, 0, 1, 1); QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:"), page); delimiterLabel->setBuddy(m_delimiterWidget); delimiterLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(delimiterLabel, 0, 0, 1, 1); m_commentWidget = new KexiCSVCommentWidget(true, page); glyr->addWidget(m_commentWidget, 1, 4); QLabel *commentLabel = new QLabel(xi18n("Comment symbol:"), page); commentLabel->setBuddy(m_commentWidget); commentLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(commentLabel, 0, 4); // Format: number, text... //! @todo Object and Currency types m_formatCombo = new KComboBox(page); m_formatCombo->setObjectName("m_formatCombo"); for (int i = 0; i < kexiCSVImportStatic->types.size(); ++i) { m_formatCombo->addItem(kexiCSVImportStatic->typeNames.value(kexiCSVImportStatic->types[i])); } glyr->addWidget(m_formatCombo, 1, 1, 1, 1); m_formatLabel = new QLabel(page); m_formatLabel->setBuddy(m_formatCombo); m_formatLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_formatLabel, 0, 1); m_primaryKeyField = new QCheckBox(xi18n("Primary key"), page); m_primaryKeyField->setObjectName("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(xi18n("Text quote:"), page); TextLabel2->setBuddy(m_comboQuote); TextLabel2->setObjectName("TextLabel2"); TextLabel2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); TextLabel2->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(TextLabel2, 0, 2); m_startAtLineSpinBox = new QSpinBox(page); m_startAtLineSpinBox->setObjectName("m_startAtLineSpinBox"); m_startAtLineSpinBox->setMinimum(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(page); m_startAtLineLabel->setBuddy(m_startAtLineSpinBox); m_startAtLineLabel->setObjectName("m_startAtLineLabel"); m_startAtLineLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); m_startAtLineLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_startAtLineLabel, 0, 3); m_ignoreDuplicates = new QCheckBox(page); m_ignoreDuplicates->setObjectName("m_ignoreDuplicates"); m_ignoreDuplicates->setText(xi18n("Ignore duplicated delimiters")); glyr->addWidget(m_ignoreDuplicates, 2, 2, 1, 2); m_1stRowForFieldNames = new QCheckBox(page); m_1stRowForFieldNames->setObjectName("m_1stRowForFieldNames"); m_1stRowForFieldNames->setText(xi18n("First row contains column names")); glyr->addWidget(m_1stRowForFieldNames, 3, 2, 1, 2); QSpacerItem* spacer_2 = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred); glyr->addItem(spacer_2, 0, 5, 4, 1); glyr->setColumnStretch(5, 2); m_tableView = new QTableView(m_optionsWidget); m_table = new KexiCSVImportDialogModel(m_tableView); m_table->setObjectName("m_table"); m_tableView->setModel(m_table); m_tableItemDelegate = new KexiCSVImportDialogItemDelegate(m_tableView); m_tableView->setItemDelegate(m_tableItemDelegate); lyr->addWidget(m_tableView); QSizePolicy spolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); spolicy.setHorizontalStretch(1); spolicy.setVerticalStretch(1); m_tableView->setSizePolicy(spolicy); m_optionsPage = new KPageWidgetItem(m_optionsWidget, xi18n("Import Options")); addPage(m_optionsPage); } void KexiCSVImportDialog::createImportMethodPage() { m_saveMethodWidget = new QWidget(this); QGridLayout *l = new QGridLayout(m_saveMethodWidget); m_newTableOption = new QRadioButton( xi18nc("@option:check CSV import: data will be appended to a new table", "&New table")); m_newTableOption->setChecked(true); m_existingTableOption = new QRadioButton( xi18nc("@option:check CSV import: data will be appended to existing table", "&Existing table")); l->addWidget(m_newTableOption, 0, 0, 1, 1); l->addWidget(m_existingTableOption, 1, 0, 1, 1); QSpacerItem *hSpacer = new QSpacerItem(200, 20, QSizePolicy::Preferred, QSizePolicy::Minimum); QSpacerItem *vSpacer = new QSpacerItem(20, 200, QSizePolicy::Minimum, QSizePolicy::Expanding); l->addItem(hSpacer, 1, 1, 1, 1); l->addItem(vSpacer, 2, 0, 1, 1); m_saveMethodPage = new KPageWidgetItem(m_saveMethodWidget, xi18n("Choose Destination for Imported Data")); addPage(m_saveMethodPage); } void KexiCSVImportDialog::createTableNamePage() { m_tableNameWidget = new QStackedWidget(this); m_tableNameWidget->setObjectName("m_tableNameWidget"); QWidget *page1=new QWidget(m_tableNameWidget); m_newTableWidget = new KexiNameWidget(QString(), page1); m_newTableWidget->addNameSubvalidator(new KDbObjectNameValidator( KexiMainWindowIface::global()->project()->dbConnection()->driver())); QVBoxLayout *l=new QVBoxLayout(page1); l->addWidget(m_newTableWidget); l->addStretch(1); m_tableNameWidget->addWidget(page1); QSplitter *splitter = new QSplitter(m_tableNameWidget); QWidget *tablesListParentWidget = new QWidget; QVBoxLayout *tablesListParentWidgetLayout = new QVBoxLayout(tablesListParentWidget); tablesListParentWidgetLayout->setMargin(0); QLabel *tablesListLabel = new QLabel(xi18nc("@label", "Select existing table:")); tablesListParentWidgetLayout->addWidget(tablesListLabel); KexiProjectNavigator::Features tablesListFeatures = KexiProjectNavigator::DefaultFeatures; tablesListFeatures &= (~KexiProjectNavigator::AllowSingleClickForOpeningItems); tablesListFeatures &= (~KexiProjectNavigator::ClearSelectionAfterAction); tablesListFeatures |= KexiProjectNavigator::Borders; m_tablesList = new KexiProjectNavigator(tablesListParentWidget, tablesListFeatures); tablesListParentWidgetLayout->addWidget(m_tablesList, 1); tablesListLabel->setBuddy(m_tablesList); QString errorString; m_tablesList->setProject(KexiMainWindowIface::global()->project(), "org.kexi-project.table", &errorString, false); connect (m_tablesList, SIGNAL(openOrActivateItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(next())); connect (m_tablesList, SIGNAL(selectionChanged(KexiPart::Item*)), this, SLOT(slotShowSchema(KexiPart::Item*))); splitter->addWidget(tablesListParentWidget); QWidget *tableDetailsWidget = new QWidget; QFormLayout *formLayout = new QFormLayout(tableDetailsWidget); formLayout->setContentsMargins(KexiUtils::marginHint(), 0, 0, 0); formLayout->addRow(new QLabel(xi18nc("@label Preview of selected table", "Table preview:"))); formLayout->addRow(xi18nc("@label", "Name:"), m_tableNameLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Caption:"), m_tableCaptionLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Row count:"), m_recordCountLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Column count:"), m_colCountLabel = new QLabel(tableDetailsWidget)); formLayout->addItem(new QSpacerItem(1, KexiUtils::spacingHint())); m_fieldsListView = new QTreeView(tableDetailsWidget); m_fieldsListView->setItemsExpandable(false); m_fieldsListView->setRootIsDecorated(false); QSizePolicy fieldsListViewPolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); fieldsListViewPolicy.setVerticalStretch(1); m_fieldsListView->setSizePolicy(fieldsListViewPolicy); formLayout->addRow(new QLabel(xi18nc("@label", "Fields:"))); formLayout->addRow(m_fieldsListView); splitter->addWidget(tableDetailsWidget); splitter->setStretchFactor(splitter->indexOf(tableDetailsWidget), 1); m_tableNameWidget->addWidget(splitter); m_tableNamePage = new KPageWidgetItem(m_tableNameWidget, xi18nc("@label", "Choose Name of Destination Table")); addPage(m_tableNamePage); } void KexiCSVImportDialog::createImportPage() { m_importWidget = new QWidget(this); m_fromLabel = new KexiCSVInfoLabel(m_mode == File ? xi18n("From CSV file:") : xi18n("From Clipboard"), m_importWidget, m_mode == File); m_fromLabel->separator()->hide(); if (m_mode != File) { m_fromLabel->setIcon(koIconName("edit-paste")); } m_toLabel = new KexiCSVInfoLabel(xi18nc("@label Importing CSV data to table:", "To table:"), m_importWidget, true); KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId("org.kexi-project.table"); m_toLabel->setIcon(partInfo->iconName()); m_importProgressLabel = new QLabel(m_importWidget); m_importingProgressBar = new QProgressBar(m_importWidget); QVBoxLayout *l = new QVBoxLayout(m_importWidget); l->addWidget(m_fromLabel); l->addWidget(m_toLabel); l->addSpacing(m_importProgressLabel->fontMetrics().height()); l->addWidget(m_importProgressLabel); l->addWidget(m_importingProgressBar); l->addStretch(1); m_importingProgressBar->hide(); m_importProgressLabel->hide(); m_importPage = new KPageWidgetItem(m_importWidget, xi18n("Ready to Import")); addPage(m_importPage); } void KexiCSVImportDialog::initLater() { if (!openData()) return; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); show(); m_tableView->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(QIODevice::ReadOnly)) { m_file->close(); delete m_file; m_file = 0; KMessageBox::sorry(this, xi18n("Cannot open input file %1.", QDir::toNativeSeparators(m_fname))); nextButton()->setEnabled(false); m_canceled = true; if (parentWidget()) parentWidget()->raise(); return false; } return true; } bool KexiCSVImportDialog::canceled() const { return m_canceled; } void KexiCSVImportDialog::fillTable() { KexiUtils::WaitCursor wc(true); repaint(); m_blockUserEvents = true; button(QDialogButtonBox::Cancel)->setEnabled(true); KexiUtils::WaitCursor wait; if (m_table->rowCount() > 0) //to accept editor m_tableView->setCurrentIndex(QModelIndex()); int row, column, maxColumn; QString field; m_table->clear(); d->clearDetectedTypes(); d->clearUniquenessTests(); m_primaryKeyColumn = -1; if (true != loadRows(field, row, column, maxColumn, true)) return; // file with only one line without EOL if (field.length() > 0) { setText(row - m_startline, column, field, true); ++row; field.clear(); } adjustRows(row - m_startline - (m_1stRowForFieldNames->isChecked() ? 1 : 0)); maxColumn = qMax(maxColumn, column); m_table->setColumnCount(maxColumn); for (column = 0; column < m_table->columnCount(); ++column) { updateColumn(column); if (!m_columnsAdjusted) m_tableView->resizeColumnToContents(column); } m_columnsAdjusted = true; if (m_primaryKeyColumn >= 0 && m_primaryKeyColumn < m_table->columnCount()) { if (KDbField::Integer != d->detectedType(m_primaryKeyColumn)) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = -1; } } m_tableView->setCurrentIndex(m_table->index(0, 0)); currentCellChanged(m_table->index(0, 0), QModelIndex()); setPrimaryKeyIcon(m_primaryKeyColumn, true); const int count = qMax(0, m_table->rowCount() - 1 + m_startline); m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW; if (count > 1) { if (m_allRowsLoadedInPreview) { m_startAtLineSpinBox->setMaximum(count); m_startAtLineSpinBox->setValue(m_startline + 1); } m_startAtLineSpinBox->setEnabled(true); m_startAtLineLabel->setText( m_allRowsLoadedInPreview ? xi18n("Start at line (1-%1):", count) : xi18n("Start at line:") //we do not know what's real count ); m_startAtLineLabel->setEnabled(true); } else { // no data m_startAtLineSpinBox->setMaximum(1); m_startAtLineSpinBox->setValue(1); m_startAtLineSpinBox->setEnabled(false); m_startAtLineLabel->setText(xi18n("Start at line:")); m_startAtLineLabel->setEnabled(false); } updateRowCountInfo(); m_blockUserEvents = false; repaint(); } QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream *inputStream) { // try to detect delimiter // \t has priority, then ; then , const qint64 origOffset = inputStream->pos(); 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; // , QList tabsPerLine, semicolonsPerLine, commasPerLine; int tabs = 0, semicolons = 0, commas = 0; int line = 0; bool wasChar13 = false; // true if previous x was '\r' for (int 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 (wasChar13 && c == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = c == '\r'; if (c == '\n' || c == '\r') {//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->seek(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 QList::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, S_COMMENT } state = S_START; field.clear(); const bool ignoreDups = m_ignoreDuplicates->isChecked(); bool lastCharDelimiter = false; bool nextRow = false; row = column = 1; m_prevColumnForSetText = 0; maxColumn = 0; QChar x; const bool hadInputStream = m_inputStream != 0; delete m_inputStream; if (m_mode == Clipboard) { m_inputStream = new QTextStream(&m_clipboardData, QIODevice::ReadOnly); if (!hadInputStream) m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER); } else { m_file->seek(0); //always seek at 0 because loadRows() is called many times m_inputStream = new QTextStream(m_file); QTextCodec *codec = KCharsets::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]); const QChar commentSymbol(m_commentWidget->commentSymbol()[0]); m_stoppedAt_MAX_BYTES_TO_PREVIEW = false; if (m_importingProgressBar) { m_elapsedTimer.start(); m_elapsedMs = m_elapsedTimer.elapsed(); } int offset = 0; bool wasChar13 = false; // true if previous x was '\r' for (;; ++offset) { if (m_importingProgressBar && (offset % 0x100) == 0 && (m_elapsedMs + PROGRESS_STEP_MS) < m_elapsedTimer.elapsed()) { //update progr. bar dlg on final exporting m_elapsedMs = m_elapsedTimer.elapsed(); m_importingProgressBar->setValue(offset); qApp->processEvents(); if (m_importCanceled) { return ::cancelled; } } if (m_inputStream->atEnd()) { if (x != '\n' && x != '\r') { x = '\n'; // simulate missing \n at end wasChar13 = false; } else { break; // finish! } } else { (*m_inputStream) >> x; // read one char } if (wasChar13 && x == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = x == '\r'; 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) { field.clear(); if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } else if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { if (!inGUI) { //fill remaining empty fields (database wants them explicitly) for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { setText(row - m_startline, additionalColumn, QString(), inGUI); } } nextRow = true; if (ignoreDups && lastCharDelimiter) { // we're ignoring repeated delimiters so remove any extra trailing delimiters --column; } maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; maxColumn -= 1; break; } } 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' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } 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' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_COMMENT : if (x == '\n' || x == '\r') { state = S_START; } if (lastCharDelimiter) { lastCharDelimiter = false; } break; case S_MAYBE_NORMAL_FIELD : case S_NORMAL_FIELD : if (state == S_MAYBE_NORMAL_FIELD && x == m_textquote) { field.clear(); state = S_QUOTED_FIELD; break; } if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { field += x; } break; } // switch if (x != delimiter) lastCharDelimiter = false; if (nextRow) { if (!inGUI && !shouldSaveRow(row - m_startline, m_1stRowForFieldNames->isChecked())) { // do not save to the database 1st row if it contains column names m_valuesToInsert.clear(); } else if (!saveRow(inGUI)) return false; ++row; } if (m_firstFillTableCall && row == 2 && !m_1stRowForFieldNames->isChecked() && m_table->firstRowForFieldNames()) { m_table->clear(); m_firstFillTableCall = false; //this trick is allowed only once, on startup m_1stRowForFieldNames->setChecked(true); //this will reload table m_blockUserEvents = false; repaint(); return false; } if (!m_importingProgressBar && row % 20 == 0) { qApp->processEvents(); //only for GUI mode: if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCanceled()) { delete m_loadingProgressDlg; m_loadingProgressDlg = 0; m_dialogCanceled = true; reject(); return false; } } if (!m_firstFillTableCall && m_loadingProgressDlg) { m_loadingProgressDlg->setValue(qMin(m_maximumRowsForPreview, row)); } if (inGUI && row > (m_maximumRowsForPreview + (m_table->firstRowForFieldNames() ? 1 : 0))) { //qDebug() << "loading stopped at row #" << m_maximumRowsForPreview; break; } if (nextRow) { nextRow = false; //additional speedup: stop processing now if too many bytes were loaded for preview //qDebug() << offset; if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) { m_stoppedAt_MAX_BYTES_TO_PREVIEW = true; return true; } } } return true; } void KexiCSVImportDialog::updateColumn(int col) { KDbField::Type detectedType = d->detectedType(col); if (detectedType == KDbField::InvalidType) { d->setDetectedType(col, KDbField::Text); //entirely empty column detectedType = KDbField::Text; } m_table->setHeaderData(col, Qt::Horizontal, QString(xi18n("Column %1", col + 1) + " \n(" + kexiCSVImportStatic->typeNames[detectedType].toLower() + ") ")); m_tableView->horizontalHeader()->adjustSize(); if (m_primaryKeyColumn == -1 && isPrimaryKeyAllowed(col)) { m_primaryKeyColumn = col; } } bool KexiCSVImportDialog::isPrimaryKeyAllowed(int col) { QList *list = d->uniquenessTest(col); if (m_primaryKeyColumn != -1 || !list || list->isEmpty()) { return false; } bool result = false; int expectedRowCount = m_table->rowCount(); if (m_table->firstRowForFieldNames()) { expectedRowCount--; } if (list->count() == expectedRowCount) { qSort(*list); QList::ConstIterator it = list->constBegin(); int prevValue = *it; ++it; for (; it != list->constEnd() && prevValue != (*it); ++it) { prevValue = (*it); } result = it == list->constEnd(); // no duplicates } list->clear(); // not needed now: conserve memory return result; } void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text) { int intValue; KDbField::Type type = d->detectedType(col); if (row == 1 || type != KDbField::Text) { bool found = false; if (text.isEmpty() && type == KDbField::InvalidType) 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 == KDbField::Integer || type == KDbField::Double || type == KDbField::InvalidType)) { bool ok = text.isEmpty() || m_fpNumberRegExp1.match(text).hasMatch() || m_fpNumberRegExp2.match(text).hasMatch(); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Double); found = true; //yes } } //-number? if (!found && (row == 1 || type == KDbField::Integer || type == KDbField::InvalidType)) { bool ok = text.isEmpty();//empty values allowed if (!ok) intValue = text.toInt(&ok); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Integer); found = true; //yes } } //-date? if (!found && (row == 1 || type == KDbField::Date || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_dateRegExp.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Date); found = true; //yes } } //-time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_timeRegExp1.match(text).hasMatch() || m_timeRegExp2.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Time); found = true; //yes } } //-date/time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if (row == 1 || type == KDbField::InvalidType) { bool detected = text.isEmpty(); if (!detected) { const QStringList dateTimeList(text.split(' ')); 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].trimmed()); QString timePart(dateTimeList[1].trimmed()); ok = m_dateRegExp.match(datePart).hasMatch() && (m_timeRegExp1.match(timePart).hasMatch() || m_timeRegExp2.match(timePart).hasMatch()); } detected = ok; } if (detected) { d->setDetectedType(col, KDbField::DateTime); found = true; //yes } } } if (!found && type == KDbField::InvalidType && !text.isEmpty()) { //eventually, a non-emptytext after a while d->setDetectedType(col, KDbField::Text); found = true; //yes } //default: text type (already set) } type = d->detectedType(col); //qDebug() << type; if (type == KDbField::Integer) { // check uniqueness for this value QList *list = d->uniquenessTest(col); if (text.isEmpty()) { if (list) { list->clear(); // empty value cannot be in PK } } else { if (!list) { list = new QList(); d->setUniquenessTest(col, list); } list->append(intValue); } } } QDate KexiCSVImportDialog::buildDate(int y, int m, int d) const { if (y < 100) { if ((1900 + y) >= m_minimumYearFor100YearSlidingWindow) return QDate(1900 + y, m, d); else return QDate(2000 + y, m, d); } return QDate(y, m, d); } bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date) { QRegularExpressionMatch match = m_dateRegExp.match(text); if (!match.hasMatch()) return false; //dddd - dd - dddd //1 2 3 4 5 <- pos const int d1 = match.captured(1).toInt(), d3 = match.captured(3).toInt(), d5 = match.captured(5).toInt(); switch (m_options.dateFormat) { case KexiCSVImportOptions::DMY: date = buildDate(d5, d3, d1); break; case KexiCSVImportOptions::YMD: date = buildDate(d1, d3, d5); break; case KexiCSVImportOptions::MDY: date = buildDate(d5, d1, d3); break; case KexiCSVImportOptions::AutoDateFormat: if (match.captured(2) == "/") { //probably separator for american format mm/dd/yyyy date = buildDate(d5, d1, d3); } else { if (d5 > 31) //d5 == year date = buildDate(d5, d3, d1); else //d1 == year date = buildDate(d1, d3, d5); } break; default:; } 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; QRegularExpressionMatch match = m_timeRegExp2.match(text); if (match.hasMatch()) { //hh:mm:ss time = QTime(match.captured(1).toInt(), match.captured(3).toInt(), match.captured(5).toInt()); return true; } return false; } void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI) { if (!inGUI) { if (!shouldSaveRow(row, m_1stRowForFieldNames->isChecked())) return; // do not care about this value if it contains column names (these were already used) //save text directly to database buffer if (m_prevColumnForSetText == 0) { //1st call m_valuesToInsert.clear(); if (m_implicitPrimaryKeyAdded) { m_valuesToInsert << QVariant(); //id will be autogenerated here } } if ((m_prevColumnForSetText + 1) < col) { //skipped one or more columns //before this: save NULLs first for (int i = m_prevColumnForSetText + 1; i < col; i++) { if (m_options.nullsImportedAsEmptyTextChecked && KDbField::isTextType(d->detectedType(i-1))) { m_valuesToInsert << QString(""); } else { m_valuesToInsert << QVariant(); } } } m_prevColumnForSetText = col; const KDbField::Type detectedType = d->detectedType(col-1); if (detectedType == KDbField::Integer) { m_valuesToInsert << (text.isEmpty() ? QVariant() : text.toInt()); //! @todo what about time and float/double types and different integer subtypes? } else if (detectedType == KDbField::Double) { //replace ',' with '.' QByteArray t(text.toLatin1()); const int textLen = t.length(); for (int i = 0; i < textLen; i++) { if (t[i] == ',') { t[i] = '.'; break; } } m_valuesToInsert << (t.isEmpty() ? QVariant() : t.toDouble()); } else if (detectedType == KDbField::Boolean) { const QString t(text.trimmed().toLower()); if (t.isEmpty()) m_valuesToInsert << QVariant(); else if (t == "0" || t == m_stringNo || t == m_stringI18nNo || t == m_stringFalse || t == m_stringI18nFalse) m_valuesToInsert << QVariant(false); else m_valuesToInsert << QVariant(true); //anything nonempty } else if (detectedType == KDbField::Date) { QDate date; if (parseDate(text, date)) m_valuesToInsert << date; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::Time) { QTime time; if (parseTime(text, time)) m_valuesToInsert << time; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::DateTime) { QStringList dateTimeList(text.split(' ')); if (dateTimeList.count() < 2) dateTimeList = text.split('T'); //also support ISODateTime's "T" separator //! @todo also support timezones? if (dateTimeList.count() >= 2) { //try all combinations QString datePart(dateTimeList[0].trimmed()); QDate date; if (parseDate(datePart, date)) { QString timePart(dateTimeList[1].trimmed()); QTime time; if (parseTime(timePart, time)) m_valuesToInsert << QDateTime(date, time); else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else { // Text type and the rest if (m_options.nullsImportedAsEmptyTextChecked && text.isNull()) { //default value is empty string not null - otherwise querying data without knowing SQL is very confusing m_valuesToInsert << QString(""); } else { m_valuesToInsert <columnCount() < col) { m_table->setColumnCount(col); } if (!m_1stRowForFieldNames->isChecked()) { if ((row + m_startline) == 1) {//this row is for column name if (m_table->firstRowForFieldNames() && !m_1stRowForFieldNames->isChecked()) { QString f(text.simplified()); if (f.isEmpty() || !f[0].isLetter()) { m_table->setFirstRowForFieldNames(false); } } } row++; //1st row was for column names } else { if ((row + m_startline) == 1) {//this is for column name m_table->setRowCount(1); QString colName(text.simplified()); if (!colName.isEmpty()) { if (colName.at(0) >= QLatin1Char('0') && colName.at(0) <= QLatin1Char('9')) { colName.prepend(xi18n("Column") + " "); } m_table->setData(m_table->index(0, col - 1), colName); } return; } } if (row < 2) // skipped by the user return; if (m_table->rowCount() < row) { m_table->setRowCount(row + 100); /* We add more rows at a time to limit recalculations */ m_adjustRows = true; } m_table->setData(m_table->index(row-1 ,col-1),m_options.trimmedInTextValuesChecked ? text.trimmed() : text); detectTypeAndUniqueness(row - 1, col - 1, text); } bool KexiCSVImportDialog::saveRow(bool inGUI) { if (inGUI) { //nothing to do return true; } bool res = m_importingStatement.execute(m_valuesToInsert); //! @todo move if (!res) { const QStringList msgList = KexiUtils::convertTypesUsingMethod(m_valuesToInsert); const KMessageBox::ButtonCode msgRes = KMessageBox::warningContinueCancelList(this, xi18nc("@info", "An error occurred during insert record."), QStringList(msgList.join(";")), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "SkipImportErrors" ); res = msgRes == KMessageBox::Continue; } m_valuesToInsert.clear(); return res; } void KexiCSVImportDialog::adjustRows(int iRows) { if (m_adjustRows) { m_table->setRowCount(iRows); m_adjustRows = false; for (int i = 0; i < iRows; i++) m_tableView->resizeRowToContents(i); } } void KexiCSVImportDialog::formatChanged(int index) { if (index < 0 || index >= kexiCSVImportStatic->types.size()) return; KDbField::Type type = kexiCSVImportStatic->types[index]; d->setDetectedType(m_tableView->currentIndex().column(), type); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->setChecked(m_primaryKeyColumn == m_tableView->currentIndex().column() && m_primaryKeyField->isEnabled()); updateColumn(m_tableView->currentIndex().column()); } 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::commentSymbolChanged(const QString& commentSymbol) { QString noneString = QString(xi18n("None")); if (commentSymbol.compare(noneString) == 0) { m_parseComments = false; } else { m_parseComments = true; } 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]; //qDebug() << m_textquote; //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::fillTableLater() { m_table->setColumnCount(0); QTimer::singleShot(10, this, SLOT(fillTable())); } void KexiCSVImportDialog::startlineSelected(int startline) { if (m_startline == (startline - 1)) return; m_startline = startline - 1; m_adjustRows = true; m_columnsAdjusted = false; fillTable(); m_tableView->setFocus(); } void KexiCSVImportDialog::currentCellChanged(const QModelIndex &cur, const QModelIndex &prev) { if (prev.column() == cur.column() || !cur.isValid()) return; const KDbField::Type type = d->detectedType(cur.column()); m_formatCombo->setCurrentIndex(kexiCSVImportStatic->indicesForTypes.value(type, -1)); m_formatLabel->setText(xi18n("Format for column %1:", cur.column() + 1)); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled() m_primaryKeyField->setChecked(m_primaryKeyColumn == cur.column()); m_primaryKeyField->blockSignals(false); } //! Used in emergency by accept() void KexiCSVImportDialog::dropDestinationTable(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { m_importingProgressBar->hide(); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ m_destinationTableSchema = 0; m_conn = 0; } //! Used in emergency by accept() void KexiCSVImportDialog::raiseErrorInAccept(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { finishButton()->setEnabled(true); - KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; delete m_destinationTableSchema; m_destinationTableSchema = 0; m_conn = 0; backButton()->setEnabled(true); m_importInProgress = false; m_importingProgressBar->hide(); } void KexiCSVImportDialog::accept() { if (d->imported) { parentWidget()->raise(); bool openingCanceled; KexiWindow *win = KexiMainWindowIface::global()->openedWindowFor(m_partItemForSavedTable); if (win) { KexiMainWindowIface::global()->closeObject(m_partItemForSavedTable); } KexiMainWindowIface::global()->openObject(m_partItemForSavedTable, Kexi::DataViewMode, &openingCanceled); KAssistantDialog::accept(); } else { import(); } } void KexiCSVImportDialog::import() { //! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiWindow code is moved to non-gui place KMessageBox::enableMessage("SkipImportErrors"); KexiGUIMessageHandler msg; //! @todo make it better integrated with main window KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTableOption->isChecked()) { m_destinationTableSchema = new KDbTableSchema(m_partItemForSavedTable->name()); m_destinationTableSchema->setCaption(m_partItemForSavedTable->caption()); m_destinationTableSchema->setDescription(m_partItemForSavedTable->description()); const int numCols(m_table->columnCount()); m_implicitPrimaryKeyAdded = false; //add PK if user wanted it int msgboxResult; if ( m_primaryKeyColumn == -1 && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "No primary key (autonumber) has been defined." "Should it be automatically defined on import (recommended)?" "An imported table without a primary key may not be " "editable (depending on database type)."), QString(), KGuiItem(xi18nc("@action:button Add Database Primary Key to a Table", "&Add Primary Key"), KexiIconName("database-key")), KGuiItem(xi18nc("@action:button Do Not Add Database Primary Key to a Table", "Do &Not Add"), KStandardGuiItem::no().icon())))) { if (msgboxResult == KMessageBox::Cancel) { raiseErrorInAccept(project, m_partItemForSavedTable); return; //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"); QSet colnames; for (int col = 0; col < numCols; col++) colnames.insert(m_table->data(m_table->index(0, col)).toString().toLower().simplified()); if (colnames.contains(fieldName)) { int num = 1; while (colnames.contains(fieldName + QString::number(num))) num++; fieldName += QString::number(num); fieldCaption += QString::number(num); } KDbField *field = new KDbField( fieldName, KDbField::Integer, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now field->setPrimaryKey(true); field->setAutoIncrement(true); if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } for (int col = 0; col < numCols; col++) { QString fieldCaption(m_table->data(m_table->index(0, col)).toString().simplified()); QString fieldName; if (fieldCaption.isEmpty()) { int i = 0; do { fieldCaption = xi18nc("@title:column Column 1, Column 2, etc.", "Column %1", i + 1); fieldName = KDb::stringToIdentifier(fieldCaption); if (!m_destinationTableSchema->field(fieldName)) { break; } i++; } while (true); } else { fieldName = KDb::stringToIdentifier(fieldCaption); if (m_destinationTableSchema->field(fieldName)) { QString fixedFieldName; int 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)); } } KDbField::Type detectedType = d->detectedType(col); //! @todo what about time and float/double types and different integer subtypes? //! @todo what about long text? if (detectedType == KDbField::InvalidType) { detectedType = KDbField::Text; } KDbField *field = new KDbField( fieldName, detectedType, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now if ((int)col == m_primaryKeyColumn) { field->setPrimaryKey(true); field->setAutoIncrement(true); } if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } } else { m_implicitPrimaryKeyAdded = false; m_destinationTableSchema = m_conn->tableSchema(m_partItemForSavedTable->name()); int firstColumn = 0; if (m_destinationTableSchema->field(0)->isPrimaryKey() && m_primaryKeyColumn == -1) { m_implicitPrimaryKeyAdded = true; firstColumn = 1; } if (m_destinationTableSchema->fields()->size() - firstColumn < m_table->columnCount()) { KMessageBox::error(this, xi18nc("@info", "Field count does not match." "Please choose another table.")); return; } } m_importInProgress = true; backButton()->setEnabled(false); finishButton()->setEnabled(false); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } KDbTransaction transaction = m_conn->beginTransaction(); if (transaction.isNull()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } KDbTransactionGuard tg(transaction); //-create physical table if (m_newTableOption->isChecked() && !m_conn->createTable(m_destinationTableSchema, KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::Default) & ~KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::DropDestination))) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } m_importingStatement = m_conn->prepareStatement( KDbPreparedStatement::InsertStatement, m_destinationTableSchema); if (!m_importingStatement.isValid()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } if (m_file) { m_importProgressLabel->setText(xi18n("Importing data...")); m_importingProgressBar->setMaximum(QFileInfo(*m_file).size() - 1); m_importingProgressBar->show(); m_importProgressLabel->show(); } int row, column, maxColumn; QString field; // main job tristate res = loadRows(field, row, column, maxColumn, false /*!gui*/); if (true != res) { //importing canceled or failed if (!res) { //do not display err msg when res == cancelled m_importProgressLabel->setText(xi18n("Import has been canceled.")); } else if (~res) { m_importProgressLabel->setText(xi18n("Error occurred during import.")); } raiseErrorInAccept(project, m_partItemForSavedTable); 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(), false /*!gui*/); } if (!saveRow(false /*!gui*/)) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } ++row; field.clear(); } if (!tg.commit()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } //-now we can store the item if (m_newTableOption->isChecked()) { m_partItemForSavedTable->setIdentifier(m_destinationTableSchema->id()); project->addStoredItem(part->info(), m_partItemForSavedTable); } m_importingProgressBar->hide(); m_importProgressLabel->setText(xi18nc("@info", "Data has been successfully imported to table %1.", m_destinationTableSchema->name())); m_importInProgress = false; //qDebug()<<"IMPORT DONE"; KGuiItem::assign(finishButton(), KStandardGuiItem::open()); finishButton()->setEnabled(true); KGuiItem::assign(button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); nextButton()->setEnabled(false); backButton()->setEnabled(false); m_conn = 0; d->imported = true; } void KexiCSVImportDialog::reject() { //qDebug()<<"IMP_P"<horizontalHeaderItem(col)->text(); if (header == xi18nc("Text type for column", "Text")) return TEXT; else if (header == xi18nc("Numeric type for column", "Number")) return NUMBER; else if (header == xi18nc("Currency type for column", "Currency")) return CURRENCY; else return DATE; } QString KexiCSVImportDialog::getText(int row, int col) { return m_table->item(row, col)->text(); } void KexiCSVImportDialog::ignoreDuplicatesChanged(int) { fillTable(); } void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int state) { m_adjustRows = true; if (m_1stRowForFieldNames->isChecked() && m_startline > 0 && m_startline >= (m_startAtLineSpinBox->maximum() - 1)) { m_startline--; } m_columnsAdjusted = false; fillTable(); m_table->setFirstRowForFieldNames(state); } 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_tableView->setFocus(); return true; } } return QDialog::eventFilter(watched, e); } void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = on ? m_tableView->currentIndex().column() : -1; setPrimaryKeyIcon(m_primaryKeyColumn, true); } void KexiCSVImportDialog::setPrimaryKeyIcon(int column, bool set) { if (column >= 0 && column < m_table->columnCount()) { m_table->setData(m_table->index(0, column), set ? m_pkIcon : QPixmap(), Qt::DecorationRole); } } void KexiCSVImportDialog::updateRowCountInfo() { m_infoLbl->setFileName(m_fname); if (m_allRowsLoadedInPreview) { m_infoLbl->setCommentText( xi18nc("row count", "(rows: %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(QString()); } else { m_infoLbl->setCommentText( xi18nc("row count", "(rows: more than %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(xi18n("Not all rows are visible on this preview")); } } QPushButton* KexiCSVImportDialog::configureButton() const { // Help button is used as Configure return button(QDialogButtonBox::Help); } #include "kexicsvimportdialog.moc" diff --git a/src/widget/KexiFileRequester.cpp b/src/widget/KexiFileRequester.cpp index 3fd4b7e78..ee186c6ac 100644 --- a/src/widget/KexiFileRequester.cpp +++ b/src/widget/KexiFileRequester.cpp @@ -1,567 +1,609 @@ /* This file is part of the KDE project - Copyright (C) 2016-2017 Jarosław Staniek + Copyright (C) 2016-2018 Jarosław 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 "KexiFileRequester.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { enum KexiFileSystemModelColumnIds { NameColumnId, LastModifiedColumnId }; } //! A model for KexiFileRequester class KexiFileSystemModel : public QFileSystemModel { Q_OBJECT public: explicit KexiFileSystemModel(QObject *parent = nullptr) : QFileSystemModel(parent) { } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return LastModifiedColumnId + 1; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { const int col = index.column(); if (col == NameColumnId) { switch (role) { case Qt::DecorationRole: { if (isDir(index)) { return koIcon("folder"); } else { return QIcon::fromTheme(m_mimeDb.mimeTypeForFile(filePath(index)).iconName()); } } default: break; } return QFileSystemModel::data(index, role); } else if (col == LastModifiedColumnId) { const QWidget *parentWidget = qobject_cast(QObject::parent()); switch (role) { case Qt::DisplayRole: return parentWidget->locale().toString(QFileSystemModel::lastModified(index), QLocale::ShortFormat); default: break; } } return QVariant(); } Qt::ItemFlags flags(const QModelIndex& index) const override { Q_UNUSED(index) return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } private: QMimeDatabase m_mimeDb; }; //! @internal class KexiUrlCompletion : public KUrlCompletion { public: explicit KexiUrlCompletion(QList *filterRegExps, QList *filterMimeTypes) : KUrlCompletion(KUrlCompletion::FileCompletion) , m_filterRegExps(filterRegExps) , m_filterMimeTypes(filterMimeTypes) { } using KUrlCompletion::postProcessMatches; //! Reimplemented to match the filter void postProcessMatches(QStringList *matches) const override { for (QStringList::Iterator matchIt = matches->begin(); matchIt != matches->end();) { if (fileMatchesFilter(*matchIt)) { ++matchIt; } else { matchIt = matches->erase(matchIt); } } } private: /** * @return @c true if @a fileName matches the current regular expression as well as the mime types * * The mime type matching allows to overcome issues with patterns such as *.doc being used * for text/plain mime types but really belonging to application/msword mime type. */ bool fileMatchesFilter(const QString &fileName) const { bool found = false; for (QRegExp *regexp : *m_filterRegExps) { if (regexp->exactMatch(fileName)) { found = true; break; } } if (!found) { return false; } const QMimeType mimeType(m_mimeDb.mimeTypeForFile(fileName)); qDebug() << mimeType; int i = m_filterMimeTypes->indexOf(mimeType); return i >= 0; } const QList * const m_filterRegExps; const QList * const m_filterMimeTypes; QMimeDatabase m_mimeDb; }; //! @internal class Q_DECL_HIDDEN KexiFileRequester::Private : public QObject { Q_OBJECT public: Private(KexiFileRequester *r) : q(r) { } ~Private() { qDeleteAll(filterRegExps); } static QString urlToPath(const QUrl &url) { QString filePath = QDir::toNativeSeparators(url.path(QUrl::RemoveScheme | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); #ifdef Q_OS_WIN if (filePath.startsWith('\\')) { filePath = filePath.mid(1); } else if (filePath.startsWith("file:\\")) { filePath = filePath.mid(6); } #endif return filePath; } public Q_SLOTS: void updateUrl(const QUrl &url) { updateFileName(urlToPath(url)); } void updateFileName(const QString &filePath) { const QFileInfo fileInfo(filePath); QString dirPath; if (fileInfo.isDir()) { dirPath = fileInfo.absoluteFilePath(); } else { dirPath = fileInfo.absolutePath(); } dirPath = QDir::toNativeSeparators(dirPath); if (filePath.isEmpty()) { // display Windows Explorer's "Computer" folder name for the top level #ifdef Q_OS_WIN QString computerNameString = QSettings( "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" "CLSID\\{20D04FE0-3AEA-1069-A2D8-08002B30309D}", QSettings::NativeFormat).value("Default").toString(); if (computerNameString.isEmpty()) { computerNameString = xi18n("Computer"); } urlLabel->setText(computerNameString); folderIcon->setPixmap(koSmallIcon("computer")); upButton->setEnabled(false); #else urlLabel->setText("/"); folderIcon->setPixmap(koSmallIcon("folder")); upButton->setEnabled(false); #endif } else { urlLabel->setText(dirPath); folderIcon->setPixmap(koSmallIcon("folder")); upButton->setEnabled(filePath != "/"); } if (model->rootPath() != dirPath) { model->setRootPath(dirPath); - list->setRootIndex(model->index(filePath)); + list->setRootIndex(model->index(dirPath)); list->resizeColumnToContents(LastModifiedColumnId); urlCompletion->setDir(QUrl::fromLocalFile(dirPath)); } - const QModelIndex fileIndex = model->index(filePath); - list->scrollTo(fileIndex); - list->selectionModel()->select(fileIndex, QItemSelectionModel::ClearAndSelect); + if (!fileInfo.isDir()) { + list->clearSelection(); + const QModelIndex fileIndex = model->index(filePath); + list->scrollTo(fileIndex); + list->selectionModel()->select(fileIndex, QItemSelectionModel::ClearAndSelect); + /*qWarning() << model->rootPath() << fileIndex.isValid() << model->filePath(fileIndex) + << model->fileName(fileIndex) << list->selectionModel()->selection().isEmpty() + << q->filters()->isExistingFileRequired();*/ + const QString newText(QFileInfo(filePath).fileName()); + if (newText != locationEdit->lineEdit()->text()) { + KexiUtils::BoolBlocker guard(&locationEditTextChangedEnabled, false); + locationEdit->lineEdit()->setText(newText); + } + } } void itemClicked(const QModelIndex &index) { handleItem(index, std::bind(&KexiFileRequester::fileHighlighted, q, std::placeholders::_1), true); if (activateItemsOnSingleClick) { handleItem(index, std::bind(&KexiFileRequester::fileSelected, q, std::placeholders::_1), false); } } void itemActivated(const QModelIndex &index) { if (!activateItemsOnSingleClick) { handleItem(index, std::bind(&KexiFileRequester::fileSelected, q, std::placeholders::_1), true); } } void upButtonClicked() { QString dirPath(urlLabel->text()); QDir dir(dirPath); if (dirPath.isEmpty() || !dir.cdUp()) { updateFileName(QString()); } else { updateFileName(dir.absolutePath()); } //! @todo update button enable flag } void selectUrlButtonClicked() { QUrl dirUrl; #ifdef Q_OS_WIN if (!upButton->isEnabled()) { // Computer folder, see http://doc.qt.io/qt-5/qfiledialog.html#setDirectoryUrl dirUrl = QUrl("clsid:0AC0837C-BBF8-452A-850D-79D08E667CA7"); } #else if (false) { } #endif else { dirUrl = QUrl::fromLocalFile(urlLabel->text()); } QUrl selectedUrl = QFileDialog::getExistingDirectoryUrl(q, QString(), dirUrl); if (selectedUrl.isLocalFile()) { updateFileName(selectedUrl.toLocalFile()); } } void locationEditTextChanged(const QString &text) { + if (!locationEditTextChangedEnabled) { + return; + } locationEdit->lineEdit()->setModified(true); if (text.isEmpty()) { list->clearSelection(); } QFileInfo info(model->rootPath() + '/' + text); if (info.isFile() && model->rootDirectory().exists(text)) { updateFileName(model->rootDirectory().absoluteFilePath(text)); // select file - } else { + } else if (q->filters()->isExistingFileRequired()) { updateFileName(model->rootPath()); // only dir, unselect file + } else { + updateFileName(info.absoluteFilePath()); // only dir, unselect file } } void locationEditReturnPressed() { QString text(locationEdit->lineEdit()->text()); if (text.isEmpty()) { return; } if (text == QStringLiteral("~")) { text = QDir::homePath(); } else if (text.startsWith(QStringLiteral("~/"))) { text = QDir::home().absoluteFilePath(text.mid(2)); } if (QDir::isAbsolutePath(text)) { QFileInfo info(text); if (!info.isReadable()) { return; } if (info.isDir()) { // jump to absolute dir and clear the editor updateFileName(info.canonicalFilePath()); locationEdit->lineEdit()->clear(); } else { // jump to absolute dir and select the file in it updateFileName(info.dir().canonicalPath()); locationEdit->lineEdit()->setText(info.fileName()); locationEditReturnPressed(); } } else { // relative path QFileInfo info(model->rootPath() + '/' + text); if (info.isReadable() && info.isDir()) { // jump to relative dir and clear the editor updateFileName(info.canonicalFilePath()); locationEdit->lineEdit()->clear(); } else { // emit the file selection //not needed - preselected: updateFileName(text); emit q->fileSelected(q->selectedFile()); } } } void slotFilterComboChanged() { const QStringList patterns = filterCombo->currentFilter().split(' '); //qDebug() << patterns; model->setNameFilters(patterns); qDeleteAll(filterRegExps); filterRegExps.clear(); for (const QString &pattern : patterns) { filterRegExps.append(new QRegExp(pattern, Qt::CaseInsensitive, QRegExp::Wildcard)); } } + //! @todo added to display select filename, still does not work + void directoryLoaded() + { + if (!list->selectionModel()->selectedIndexes().isEmpty()) { + list->scrollTo(list->selectionModel()->selectedIndexes().first()); + } + } + private: void handleItem(const QModelIndex &index, std::function sig, bool silent) { const QString filePath(model->filePath(index)); if (model->isDir(index)) { QFileInfo info(filePath); if (info.isReadable()) { updateFileName(filePath); } else { if (silent) { KMessageBox::error(q, xi18n("Could not enter directory %1.", QDir::toNativeSeparators(info.absoluteFilePath()))); } } } else { emit sig(filePath); } } public: KexiFileRequester* const q; QPushButton *upButton; QLabel *folderIcon; QLabel *urlLabel; QPushButton *selectUrlButton; KexiFileSystemModel *model; QTreeView *list; bool activateItemsOnSingleClick; KUrlComboBox *locationEdit; KexiUrlCompletion *urlCompletion; KFileFilterCombo *filterCombo; QList filterRegExps; //!< Regular expression for the completer in the URL box QList filterMimeTypes; + bool locationEditTextChangedEnabled = true; }; KexiFileRequester::KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, - QWidget *parent) - : QWidget(parent), KexiFileWidgetInterface(fileOrVariable), d(new Private(this)) + const QString &fileName, QWidget *parent) + : QWidget(parent), KexiFileWidgetInterface(fileOrVariable, fileName), d(new Private(this)) { init(); - const QString fileName = Private::urlToPath(startUrl()); - d->updateFileName(fileName); + const QString actualFileName = Private::urlToPath(startUrl()); setMode(mode); + d->updateFileName(actualFileName); // note: we had to call it after setMode(), not before +} + +KexiFileRequester::KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, + QWidget *parent) + : KexiFileRequester(fileOrVariable, mode, QString(), parent) +{ } KexiFileRequester::KexiFileRequester(const QString &selectedFileName, KexiFileFilters::Mode mode, QWidget *parent) - : QWidget(parent), KexiFileWidgetInterface(QUrl(selectedFileName)), d(new Private(this)) + : QWidget(parent) + , KexiFileWidgetInterface(QUrl(selectedFileName), QString()) + , d(new Private(this)) { init(); - d->updateFileName(selectedFileName); setMode(mode); + d->updateFileName(selectedFileName); // note: we had to call it after setMode(), not before } KexiFileRequester::~KexiFileRequester() { const QString startDir(d->urlLabel->text()); addRecentDir(startDir); delete d; } void KexiFileRequester::init() { // [^] [Dir ][..] // [ files list ] // [ location ] // [ filter combo ] QVBoxLayout *lyr = new QVBoxLayout(this); setContentsMargins(QMargins()); lyr->setContentsMargins(QMargins()); QHBoxLayout *urlLyr = new QHBoxLayout; urlLyr->setContentsMargins(QMargins()); lyr->addLayout(urlLyr); d->upButton = new QPushButton; d->upButton->setFocusPolicy(Qt::NoFocus); d->upButton->setIcon(koIcon("go-up")); d->upButton->setToolTip(xi18n("Go to parent directory")); d->upButton->setFlat(true); connect(d->upButton, &QPushButton::clicked, d, &KexiFileRequester::Private::upButtonClicked); urlLyr->addWidget(d->upButton); d->folderIcon = new QLabel; urlLyr->addWidget(d->folderIcon); d->urlLabel = new QLabel; d->urlLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->urlLabel->setWordWrap(true); d->urlLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); urlLyr->addWidget(d->urlLabel, 1); d->selectUrlButton = new QPushButton; d->selectUrlButton->setFocusPolicy(Qt::NoFocus); d->selectUrlButton->setIcon(koIcon("folder")); d->selectUrlButton->setToolTip(xi18n("Select directory")); d->selectUrlButton->setFlat(true); connect(d->selectUrlButton, &QPushButton::clicked, d, &KexiFileRequester::Private::selectUrlButtonClicked); urlLyr->addWidget(d->selectUrlButton); d->list = new QTreeView; d->activateItemsOnSingleClick = KexiUtils::activateItemsOnSingleClick(d->list); connect(d->list, &QTreeView::clicked, d, &KexiFileRequester::Private::itemClicked); connect(d->list, &QTreeView::activated, d, &KexiFileRequester::Private::itemActivated); d->list->setRootIsDecorated(false); d->list->setItemsExpandable(false); d->list->header()->hide(); lyr->addWidget(d->list); d->model = new KexiFileSystemModel(d->list); d->model->setNameFilterDisables(false); + connect(d->model, &QFileSystemModel::directoryLoaded, d, &Private::directoryLoaded); d->list->setModel(d->model); d->list->header()->setStretchLastSection(false); d->list->header()->setSectionResizeMode(NameColumnId, QHeaderView::Stretch); d->list->header()->setSectionResizeMode(LastModifiedColumnId, QHeaderView::ResizeToContents); QGridLayout *bottomLyr = new QGridLayout; lyr->addLayout(bottomLyr); QLabel *locationLabel = new QLabel(xi18n("Name:")); bottomLyr->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight); d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true); + setFocusProxy(d->locationEdit); d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); connect(d->locationEdit, &KUrlComboBox::editTextChanged, d, &KexiFileRequester::Private::locationEditTextChanged); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) connect(d->locationEdit, QOverload<>::of(&KUrlComboBox::returnPressed), d, &Private::locationEditReturnPressed); #else connect(d->locationEdit, static_cast(&KUrlComboBox::returnPressed), d, &Private::locationEditReturnPressed); #endif d->urlCompletion = new KexiUrlCompletion(&d->filterRegExps, &d->filterMimeTypes); d->locationEdit->setCompletionObject(d->urlCompletion); d->locationEdit->setAutoDeleteCompletionObject(true); d->locationEdit->lineEdit()->setClearButtonEnabled(true); locationLabel->setBuddy(d->locationEdit); bottomLyr->addWidget(d->locationEdit, 0, 1, Qt::AlignVCenter); QLabel *filterLabel = new QLabel(xi18n("Filter:")); bottomLyr->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight); d->filterCombo = new KFileFilterCombo; connect(d->filterCombo, &KFileFilterCombo::filterChanged, d, &Private::slotFilterComboChanged); filterLabel->setBuddy(d->filterCombo); bottomLyr->addWidget(d->filterCombo, 1, 1, Qt::AlignVCenter); } QString KexiFileRequester::selectedFile() const { const QModelIndexList list(d->list->selectionModel()->selectedIndexes()); - if (list.isEmpty()) { + if (list.isEmpty() || d->model->isDir(list.first())) { // no file selection but try entered filename + const QString text(d->locationEdit->lineEdit()->text().trimmed()); + if (!text.isEmpty() && !filters()->isExistingFileRequired()) { + const QFileInfo info(currentDir() + '/' + text); + if (info.isNativePath()) { + return info.absoluteFilePath(); + } + } return QString(); } if (d->model->isDir(list.first())) { return QString(); } return d->model->filePath(list.first()); } QString KexiFileRequester::highlightedFile() const { return selectedFile(); } QString KexiFileRequester::currentDir() const { return d->model->rootPath(); } void KexiFileRequester::setSelectedFile(const QString &name) { d->updateFileName(name); } void KexiFileRequester::updateFilters() { // Update filters for the file model, filename completion and the filter combo const QStringList patterns = filters()->allGlobPatterns(); if (patterns != d->model->nameFilters()) { d->model->setNameFilters(patterns); qDeleteAll(d->filterRegExps); d->filterRegExps.clear(); for (const QString &pattern : patterns) { d->filterRegExps.append(new QRegExp(pattern, Qt::CaseInsensitive, QRegExp::Wildcard)); } d->filterMimeTypes = filters()->mimeTypes(); d->filterCombo->setFilter(filters()->toString(KexiFileFilters::KDEFormat)); } } void KexiFileRequester::setWidgetFrame(bool set) { d->list->setFrameShape(set ? QFrame::StyledPanel : QFrame::NoFrame); d->list->setLineWidth(set ? 1 : 0); } void KexiFileRequester::applyEnteredFileName() { } QStringList KexiFileRequester::currentFilters() const { return QStringList(); } void KexiFileRequester::showEvent(QShowEvent *event) { setFiltersUpdated(false); updateFilters(); QWidget::showEvent(event); } #include "KexiFileRequester.moc" diff --git a/src/widget/KexiFileRequester.h b/src/widget/KexiFileRequester.h index 1f2a0a45f..ce8a36480 100644 --- a/src/widget/KexiFileRequester.h +++ b/src/widget/KexiFileRequester.h @@ -1,101 +1,104 @@ /* This file is part of the KDE project - Copyright (C) 2016-2017 Jarosław Staniek + Copyright (C) 2016-2018 Jarosław 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 KEXIFILEREQUESTER_H #define KEXIFILEREQUESTER_H #include "KexiFileWidgetInterface.h" #include //! @brief A widget showing a line edit and a button, which invokes a file dialog class KEXIEXTWIDGETS_EXPORT KexiFileRequester : public QWidget, public KexiFileWidgetInterface { Q_OBJECT public: - explicit KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, - QWidget *parent = nullptr); + KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, + const QString &fileName, QWidget *parent = nullptr); - explicit KexiFileRequester(const QString &selectedFile, KexiFileFilters::Mode mode, - QWidget *parent = nullptr); + KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, + QWidget *parent = nullptr); + + KexiFileRequester(const QString &selectedFile, KexiFileFilters::Mode mode, + QWidget *parent = nullptr); ~KexiFileRequester() override; /** * Returns the full path of the selected file in the local filesystem. * (Local files only) */ QString selectedFile() const override; /** * Returns the full path of the highlighted file in the local filesystem. * (Local files only) */ QString highlightedFile() const override; /** * @return the currently shown directory. */ QString currentDir() const override; //! Sets file mode //void setFileMode(KexiFileFilters::Mode mode); //! @return additional mime types //QStringList additionalMimeTypes() const; //! @return excluded mime types //QStringList excludedMimeTypes() const; //! @return the default filter, used when an empty filter is set //QString defaultFilter() const; Q_SIGNALS: void fileHighlighted(const QString &name); void fileSelected(const QString &name); public Q_SLOTS: //! Sets the url void setSelectedFile(const QString &name) override; /** * Sets whether the line edit draws itself with a frame. */ void setWidgetFrame(bool set) override; protected: /** * Updates filters in the widget based on current filter selection. */ void updateFilters() override; void applyEnteredFileName() override; QStringList currentFilters() const override; void showEvent(QShowEvent *event) override; private: void init(); Q_DISABLE_COPY(KexiFileRequester) class Private; Private * const d; }; #endif // KEXIFILEREQUESTER_H diff --git a/src/widget/KexiFileWidget.cpp b/src/widget/KexiFileWidget.cpp index 4cf7c8454..ffeb81227 100644 --- a/src/widget/KexiFileWidget.cpp +++ b/src/widget/KexiFileWidget.cpp @@ -1,319 +1,334 @@ /* This file is part of the KDE project - Copyright (C) 2003-2017 Jarosław Staniek + Copyright (C) 2003-2018 Jarosław 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 "KexiFileWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiFileWidget::Private { public: Private() { } QUrl selectedUrl; }; //------------------ +static QUrl startDirWithFileName(const QUrl &startDirOrVariable, const QString &fileName) +{ + QUrl result = startDirOrVariable; + if (!fileName.isEmpty()) { + result.setPath(result.path() + '/' + fileName); + } + return result; +} + KexiFileWidget::KexiFileWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, - QWidget *parent) - : KFileWidget(startDirOrVariable, parent) - , KexiFileWidgetInterface(startDirOrVariable) + const QString &fileName, QWidget *parent) + : KFileWidget(startDirWithFileName(startDirOrVariable, fileName), parent) + , KexiFileWidgetInterface(startDirOrVariable, fileName) , d(new Private) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); QAction *previewAction = actionCollection()->action("preview"); if (previewAction) { previewAction->setChecked(false); } setFocusProxy(locationEdit()); connect(this, &KFileWidget::fileHighlighted, this, &KexiFileWidget::slotFileHighlighted); setMode(mode); } +KexiFileWidget::KexiFileWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, + QWidget *parent) + : KexiFileWidget(startDirOrVariable, mode, QString(), parent) +{ +} + KexiFileWidget::~KexiFileWidget() { done(); #if 0 qDebug() << d->recentDirClass; if (!d->recentDirClass.isEmpty()) { QString hf = highlightedFile(); QUrl dir; if (hf.isEmpty()) { dir = baseUrl(); } else { QFileInfo fi(hf); QString dirStr = fi.isDir() ? fi.absoluteFilePath() : fi.dir().absolutePath(); dir = QUrl::fromLocalFile(dirStr); } //qDebug() << dir; //qDebug() << highlightedFile(); if (!dir.isEmpty()) KRecentDirs::add(d->recentDirClass, dir.path()); } #endif delete d; } void KexiFileWidget::slotFileHighlighted(const QUrl& url) { //qDebug() << url; d->selectedUrl = url; emit fileSelected(selectedFile()); } void KexiFileWidget::slotFileSelected(const QUrl& url) { Q_UNUSED(url) //qDebug() << url; emit fileSelected(selectedFile()); } void KexiFileWidget::setMode(KexiFileFilters::Mode mode) { KexiFileWidgetInterface::setMode(mode); } void KexiFileWidget::updateFilters() { if (filtersUpdated()) return; setFiltersUpdated(true); clearFilter(); filters()->setDefaultFilter(filterWidget()->defaultFilter()); setFilter(filters()->toString(KexiFileFilters::KDEFormat)); if (filters()->mode() == KexiFileFilters::Opening || filters()->mode() == KexiFileFilters::CustomOpening) { KFileWidget::setMode(KFile::ExistingOnly | KFile::LocalOnly | KFile::File); setOperationMode(KFileWidget::Opening); } else { KFileWidget::setMode(KFile::LocalOnly | KFile::File); setOperationMode(KFileWidget::Saving); } } void KexiFileWidget::showEvent(QShowEvent * event) { setFiltersUpdated(false); updateFilters(); KFileWidget::showEvent(event); } /*TODO QString KexiFileWidget::selectedFile() const { #ifdef Q_OS_WIN // QString path = selectedFile(); //js @todo // qDebug() << "selectedFile() ==" << path << "'" << url().fileName() << "'" << m_lineEdit->text(); QString path = dir()->absolutePath(); if (!path.endsWith('/') && !path.endsWith("\\")) path.append("/"); path += m_lineEdit->text(); // QString path = QFileInfo(selectedFile()).dirPath(true) + "/" + m_lineEdit->text(); #else // QString path = locationEdit->currentText().trimmed(); //url.path().trimmed(); that does not work, if the full path is not in the location edit !!!!! QString path( KFileWidget::selectedFile() ); qDebug() << "prev selectedFile() ==" << path; qDebug() << "locationEdit ==" << locationEdit()->currentText().trimmed(); //make sure user-entered path is acceped: //! @todo KEXI3 setSelection( locationEdit()->currentText().trimmed() ); // path = KFileWidget::selectedFile(); path = locationEdit()->currentText().trimmed(); qDebug() << "selectedFile() ==" << path; #endif if (!currentFilter().isEmpty()) { if (d->mode & SavingFileBasedDB) { const QStringList filters( currentFilter().split(' ') ); qDebug()<< "filter ==" << filters; QString ext( QFileInfo(path).suffix() ); bool hasExtension = false; foreach (const QString& filter, filters) { const QString f( filter.trimmed() ); hasExtension = !f.mid(2).isEmpty() && ext==f.mid(2); if (hasExtension) break; } if (!hasExtension) { //no extension: add one QString defaultExtension( d->defaultExtension ); if (defaultExtension.isEmpty()) defaultExtension = filters.first().trimmed().mid(2); //first one path += (QString(".")+defaultExtension); qDebug() << "KexiFileWidget::checkURL(): append extension," << path; } } } qDebug() << "KexiFileWidget::currentFileName() ==" << path; return path; } */ QString KexiFileWidget::selectedFile() const { return d->selectedUrl.toLocalFile(); } QString KexiFileWidget::highlightedFile() const { return d->selectedUrl.toLocalFile(); } void KexiFileWidget::setSelectedFile(const QString &name) { d->selectedUrl = QUrl::fromLocalFile(name); } QString KexiFileWidget::currentDir() const { return KFileWidget::baseUrl().toLocalFile(); } void KexiFileWidget::applyEnteredFileName() { const QString enteredFileName(locationEdit()->lineEdit()->text()); if (enteredFileName.isEmpty()) { return; } //qDebug() << enteredFileName; //qDebug() << locationEdit()->urls(); //qDebug() << currentDir(); setSelectedFile(currentDir()); // FIXME: find first... if (QDir::isAbsolutePath(enteredFileName)) { setSelectedFile(enteredFileName); } else { setSelectedFile(currentDir() + enteredFileName); } } QStringList KexiFileWidget::currentFilters() const { return currentFilter().split(QLatin1Char(' ')); } void KexiFileWidget::accept() { //qDebug(); KFileWidget::accept(); // qDebug() << selectedFile(); // locationEdit->setFocus(); // QKeyEvent ev(QEvent::KeyPress, Qt::Key_Enter, '\n', 0); // QApplication::sendEvent(locationEdit, &ev); // QApplication::postEvent(locationEdit, &ev); // qDebug() << "KexiFileWidget::accept() m_lastUrl ==" << m_lastUrl.path(); // if (m_lastUrl.path()==currentURL().path()) {//(js) to prevent more multiple kjob signals (I do not know why this is) /* if (d->lastFileName==selectedFile()) {//(js) to prevent more multiple kjob signals (I do not know why this is) // m_lastUrl=QUrl(); d->lastFileName.clear(); qDebug() << "d->lastFileName==selectedFile()"; #ifdef Q_OS_WIN return; #endif } qDebug() << "KexiFileWidget::accept(): path =" << selectedFile(); if ( checkSelectedFile() ) { emit accepted(); } d->lastFileName = selectedFile(); #ifdef Q_OS_WIN saveLastVisitedPath(d->lastFileName); #endif*/ } void KexiFileWidget::reject() { //qDebug(); emit rejected(); } #if 0 // TODO? void KexiFileWidget::setLocationText(const QString& text) { locationEdit()->setUrl(QUrl(text)); /* #ifdef Q_OS_WIN //js @todo setSelection(fn); #else setSelection(fn); // locationEdit->setCurrentText(fn); // locationEdit->lineEdit()->setEdited( true ); // setSelection(fn); #endif*/ } #endif void KexiFileWidget::focusInEvent(QFocusEvent *event) { Q_UNUSED(event) locationEdit()->setFocus(); } /*bool KexiFileWidget::eventFilter ( QObject * watched, QEvent * e ) { //filter-out ESC key if (e->type()==QEvent::KeyPress && static_cast(e)->key()==Qt::Key_Escape && static_cast(e)->state()==Qt::NoButton) { static_cast(e)->accept(); emit rejected(); return true; } return KexiFileWidgetBase::eventFilter(watched,e); } */ void KexiFileWidget::setWidgetFrame(bool set) { Q_UNUSED(set) } diff --git a/src/widget/KexiFileWidget.h b/src/widget/KexiFileWidget.h index 3459ec37e..5eb1bb6af 100644 --- a/src/widget/KexiFileWidget.h +++ b/src/widget/KexiFileWidget.h @@ -1,114 +1,117 @@ /* This file is part of the KDE project - Copyright (C) 2003-2017 Jarosław Staniek + Copyright (C) 2003-2018 Jarosław 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 KEXIFILEWIDGET_H #define KEXIFILEWIDGET_H #include #ifdef KEXI_USE_KFILEWIDGET #include "KexiFileWidgetInterface.h" #include //! @short Widget for opening/saving files supported by Kexi /*! For simplicity, initially the widget has hidden the preview pane. */ class KEXIEXTWIDGETS_EXPORT KexiFileWidget : public KFileWidget, public KexiFileWidgetInterface { Q_OBJECT public: //! @todo KEXI3 add equivalent of kfiledialog:/// for startDirOrVariable + KexiFileWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, + const QString &fileName, QWidget *parent = nullptr); + KexiFileWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, QWidget *parent = nullptr); ~KexiFileWidget() override; using KFileWidget::setMode; /** * Returns the full path of the selected file in the local filesystem. * (Local files only) */ QString selectedFile() const override; /** * Returns the full path of the highlighted file in the local filesystem. * (Local files only) */ QString highlightedFile() const override; /** * @return the currently shown directory. */ QString currentDir() const override; public Q_SLOTS: void setMode(KexiFileFilters::Mode mode); //! Just sets locationWidget()->setCurrentText(text) //void setLocationText(const QString& text) override; /** * Sets the file name to preselect to @p name * * This takes absolute URLs and relative file names. */ void setSelectedFile(const QString &name) override; //! Typing a file that doesn't exist closes the file dialog, we have to //! handle this case better here. virtual void accept(); /** * Sets whether the line edit draws itself with a frame. */ void setWidgetFrame(bool set) override; Q_SIGNALS: void fileHighlighted(const QString &name); void fileSelected(const QString &name); void rejected(); protected Q_SLOTS: virtual void reject(); void slotFileHighlighted(const QUrl& url); void slotFileSelected(const QUrl& url); protected: virtual void showEvent(QShowEvent *event); virtual void focusInEvent(QFocusEvent *event); /** * Updates filters in the widget based on current filter selection. */ void updateFilters() override; void applyEnteredFileName() override; QStringList currentFilters() const override; private: class Private; Private * const d; }; #endif // KEXI_USE_KFILEWIDGET #endif // KEXIFILEWIDGET_H diff --git a/src/widget/KexiFileWidgetInterface.cpp b/src/widget/KexiFileWidgetInterface.cpp index a42f132a7..af6ac4b33 100644 --- a/src/widget/KexiFileWidgetInterface.cpp +++ b/src/widget/KexiFileWidgetInterface.cpp @@ -1,271 +1,289 @@ /* This file is part of the KDE project - Copyright (C) 2003-2017 Jarosław Staniek + Copyright (C) 2003-2018 Jarosław 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 "KexiFileWidgetInterface.h" #include #include "KexiFileRequester.h" #include #include #ifdef KEXI_USE_KFILEWIDGET #include "KexiFileWidget.h" #include #include #endif #include //! @internal class Q_DECL_HIDDEN KexiFileWidgetInterface::Private { public: Private() { } QUrl startUrl; KexiFileFilters filters; QString defaultExtension; bool confirmOverwrites = true; bool filtersUpdated = false; QString highlightedName; QString recentDirClass; }; //------------------ -KexiFileWidgetInterface::KexiFileWidgetInterface(const QUrl &startDirOrVariable) +KexiFileWidgetInterface::KexiFileWidgetInterface(const QUrl &startDirOrVariable, + const QString &fileName) : d(new Private) { if (startDirOrVariable.scheme() == "kfiledialog") { - d->startUrl = KexiUtils::getStartUrl(startDirOrVariable, &d->recentDirClass); + d->startUrl = KexiUtils::getStartUrl(startDirOrVariable, &d->recentDirClass, fileName); + } else if (fileName.isEmpty()) { + d->startUrl = startDirOrVariable; } else { d->startUrl = startDirOrVariable; + d->startUrl.setPath(startDirOrVariable.path() + '/' + fileName); } } KexiFileWidgetInterface::~KexiFileWidgetInterface() { delete d; } QUrl KexiFileWidgetInterface::startUrl() const { return d->startUrl; } void KexiFileWidgetInterface::addRecentDir(const QString &name) { if (!d->recentDirClass.isEmpty() && QDir(name).exists()) { KexiUtils::addRecentDir(d->recentDirClass, name); } } void KexiFileWidgetInterface::done() { qDebug() << d->recentDirClass; if (!d->recentDirClass.isEmpty()) { QString f = selectedFile(); QString dir; if (f.isEmpty()) { dir = currentDir(); } else { QFileInfo fi(f); QString dirStr = fi.isDir() ? fi.absoluteFilePath() : fi.dir().absolutePath(); dir = dirStr; } qDebug() << dir; qDebug() << selectedFile(); addRecentDir(dir); } } // static KexiFileWidgetInterface *KexiFileWidgetInterface::createWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, + const QString &fileName, QWidget *parent) { #ifdef KEXI_USE_KFILEWIDGET bool useKFileWidget; KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); if (group.hasKey("UseKFileWidget")) { // allow to override useKFileWidget = group.readEntry("UseKFileWidget", false); } else { useKFileWidget = KexiUtils::isKDEDesktopSession(); } if (useKFileWidget) { - return new KexiFileWidget(startDirOrVariable, mode, parent); + return new KexiFileWidget(startDirOrVariable, mode, fileName, parent); } #endif - return new KexiFileRequester(startDirOrVariable, mode, parent); + return new KexiFileRequester(startDirOrVariable, mode, fileName, parent); +} + +// static +KexiFileWidgetInterface *KexiFileWidgetInterface::createWidget(const QUrl &startDirOrVariable, + KexiFileFilters::Mode mode, + QWidget *parent) +{ + return KexiFileWidgetInterface::createWidget(startDirOrVariable, mode, QString(), parent); } KexiFileFilters::Mode KexiFileWidgetInterface::mode() const { return d->filters.mode(); } void KexiFileWidgetInterface::setMode(KexiFileFilters::Mode mode) { //delayed d->filters.setMode(mode); d->filtersUpdated = false; updateFilters(); } QStringList KexiFileWidgetInterface::additionalMimeTypes() const { return d->filters.additionalMimeTypes(); } void KexiFileWidgetInterface::setAdditionalMimeTypes(const QStringList &mimeTypes) { d->filters.setAdditionalMimeTypes(mimeTypes); d->filtersUpdated = false; } QStringList KexiFileWidgetInterface::excludedMimeTypes() const { return d->filters.excludedMimeTypes(); } void KexiFileWidgetInterface::setExcludedMimeTypes(const QStringList &mimeTypes) { d->filters.setExcludedMimeTypes(mimeTypes); d->filtersUpdated = false; } QString KexiFileWidgetInterface::defaultExtension() const { return d->defaultExtension; } void KexiFileWidgetInterface::setDefaultExtension(const QString& ext) { d->defaultExtension = ext; } bool KexiFileWidgetInterface::confirmOverwrites() const { return d->confirmOverwrites; } void KexiFileWidgetInterface::setConfirmOverwrites(bool set) { d->confirmOverwrites = set; } QString KexiFileWidgetInterface::currentDir() const { qFatal("Implement it"); return QString(); } void KexiFileWidgetInterface::setFiltersUpdated(bool set) { d->filtersUpdated = set; } bool KexiFileWidgetInterface::filtersUpdated() const { return d->filtersUpdated; } KexiFileFilters* KexiFileWidgetInterface::filters() { return &d->filters; } +const KexiFileFilters* KexiFileWidgetInterface::filters() const +{ + return &d->filters; +} + void KexiFileWidgetInterface::connectFileHighlightedSignal(QObject *receiver, const char *slot) { QObject::connect(widget(), SIGNAL(fileHighlighted(QString)), receiver, slot); } void KexiFileWidgetInterface::connectFileSelectedSignal(QObject *receiver, const char *slot) { QObject::connect(widget(), SIGNAL(fileSelected(QString)), receiver, slot); } bool KexiFileWidgetInterface::checkSelectedFile() { qDebug() << "selectedFile:" << selectedFile(); applyEnteredFileName(); qDebug() << "selectedFile after applyEnteredFileName():" << selectedFile(); if (selectedFile().isEmpty()) { KMessageBox::error(widget(), xi18n("Enter a filename.")); return false; } if (filters()->mode() == KexiFileFilters::SavingFileBasedDB || filters()->mode() == KexiFileFilters::CustomSavingFileBasedDB) { const QStringList currentFilters(this->currentFilters()); if (!currentFilters.isEmpty()) { QString path = selectedFile(); qDebug()<< "filter:" << currentFilters << "path:" << path; QString ext(QFileInfo(path).suffix()); bool hasExtension = false; for (const QString &filter : currentFilters) { const QString f(filter.trimmed()); hasExtension = !f.midRef(2).isEmpty() && ext == f.midRef(2); if (hasExtension) { break; } } if (!hasExtension) { //no extension: add one QString ext(defaultExtension()); if (ext.isEmpty()) { ext = currentFilters.first().trimmed().mid(2); //first one } path += (QLatin1String(".") + ext); qDebug() << "appended extension:" << path; setSelectedFile(path); } qDebug() << "selectedFile after applying extension:" << selectedFile(); } } if (filters()->isExistingFileRequired()) { QFileInfo fi(selectedFile()); if (!fi.exists()) { KMessageBox::error(widget(), xi18nc("@info", "The file %1 does not exist.", QDir::toNativeSeparators(fi.absoluteFilePath()))); return false; } if (!fi.isFile()) { KMessageBox::error(widget(), xi18nc("@info", "Enter a filename.")); return false; } if (!fi.isReadable()) { KMessageBox::error(widget(), xi18nc("@info", "The file %1 is not readable.", QDir::toNativeSeparators(fi.absoluteFilePath()))); return false; } } else if (confirmOverwrites() && !KexiUtils::askForFileOverwriting(selectedFile(), widget())) { return false; } return true; } diff --git a/src/widget/KexiFileWidgetInterface.h b/src/widget/KexiFileWidgetInterface.h index 1353e76c5..33aa95906 100644 --- a/src/widget/KexiFileWidgetInterface.h +++ b/src/widget/KexiFileWidgetInterface.h @@ -1,204 +1,215 @@ /* This file is part of the KDE project - Copyright (C) 2003-2017 Jarosław Staniek + Copyright (C) 2003-2018 Jarosław 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 KEXIFILEWIDGETINTERFACE_H #define KEXIFILEWIDGETINTERFACE_H #include "kexiextwidgets_export.h" #include #include //! @brief An interface for file widget supporting opening/saving files known by Kexi class KEXIEXTWIDGETS_EXPORT KexiFileWidgetInterface { public: virtual ~KexiFileWidgetInterface(); /** * @brief Creates a file widget * @param startDirOrVariable A URL specifying the initial directory and/or filename, * or using the @c kfiledialog:/// syntax to specify a last used location. * Refer to the KFileWidget::KFileWidget() documentation * for the @c kfiledialog:/// URL syntax. * @param mode File widget's mode + * @param fileName Optional file name that is added to the resulting URL. * @param parent File widget's parent widget * * Depending on settings one of two file widget implementations is used: * - if the KEXI_USE_KFILEWIDGET build option is on and KDE Plasma desktop is detected as the * current desktop, KF5's KFileWidget-based widget is created, * - if the KEXI_USE_KFILEWIDGET build option is off or if non-KDE Plasma desktop is detected * as the current desktop, a simple KexiFileRequester widget is created. * * In addition, if the KEXI_USE_KFILEWIDGET build option is on, defaults can be overriden by * "UseKFileWidget" boolean option in the "File Dialogs" group of the application's config file: * - if "UseKFileWidget" is @c true, KF5's KFileWidget-based widget is created, * - if "UseKFileWidget" is @c false a simple KexiFileRequester widget is created. * * To delete the override, delete the "UseKFileWidget" option in the aplication's config file. * * @return the new file widget. * * @todo Share this code with KReport and Kexi */ + static KexiFileWidgetInterface *createWidget(const QUrl &startDirOrVariable, + KexiFileFilters::Mode mode, + const QString &fileName, + QWidget *parent = nullptr) Q_REQUIRED_RESULT; + + /** + * @overload + */ static KexiFileWidgetInterface *createWidget(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, QWidget *parent = nullptr) Q_REQUIRED_RESULT; /** * @brief returns this object casted to QWidget pointer */ inline QWidget *widget() { return dynamic_cast(this); } //! @return mode for filters used in this widget KexiFileFilters::Mode mode() const; //! Sets mode for filters to be used in this widget void setMode(KexiFileFilters::Mode mode); //! @return additional mime types QStringList additionalMimeTypes() const; //! Sets additional mime types, e.g. "text/x-csv" void setAdditionalMimeTypes(const QStringList &mimeTypes); //! @return excluded mime types QStringList excludedMimeTypes() const; //! Set excluded mime types void setExcludedMimeTypes(const QStringList &mimeTypes); /** * Returns the full path of the selected file in the local filesystem. * (Local files only) */ virtual QString selectedFile() const = 0; /** * @brief Sets the file name to preselect to @p name * * This takes absolute URLs and relative file names. */ virtual void setSelectedFile(const QString &name) = 0; /** * @brief Returns @c true if the current URL meets requied constraints, e.g. exists * * Shows appropriate message box if needed. */ bool checkSelectedFile(); /** * @brief Returns the full path of the highlighted file in the local filesystem * * (Local files only) */ virtual QString highlightedFile() const = 0; //! Sets location text //! @todo //virtual void setLocationText(const QString& text) = 0; //! @return default extension QString defaultExtension() const; /** * @brief Sets default extension which will be added after accepting if user didn't provided one * This method is usable when there is more than one filter so there is no rule what extension * should be selected. By default first one is selected. */ void setDefaultExtension(const QString& ext); /** * @return @c true if user should be asked to accept overwriting existing file. * @see setConfirmOverwrites */ bool confirmOverwrites() const; /*! If true, user will be asked to accept overwriting existing file. This is true by default. */ void setConfirmOverwrites(bool set); /** * Sets whether the line edit draws itself with a frame. */ virtual void setWidgetFrame(bool set) = 0; /** * @returns the currently shown directory. * Reimplement it. */ virtual QString currentDir() const; /** * @brief Connects "file hightlighted" signal to specific receiver * * Connects widget's "fileHighlighted(QString)" signal to @a receiver and @a slot. The signal * is emit when a file item is selected or highlighted. * * @note Highlighting happens mostly when user single clicks a file item and * double-click-to-select mode is enabled (see KexiUtils::activateItemsOnSingleClick()). Rather * depend on file delecting than file highlighting. * * @see connectFileSelectedSignal */ void connectFileHighlightedSignal(QObject *receiver, const char *slot); /** * @brief Connects "file selected" signal to specific receiver * * Connects "fileSelected(QString)" signal of widget's returned by widget() to * @a receiver and @a slot. */ void connectFileSelectedSignal(QObject *receiver, const char *slot); protected: - KexiFileWidgetInterface(const QUrl &startDirOrVariable); + KexiFileWidgetInterface(const QUrl &startDirOrVariable, const QString &fileName); /** * @brief Updates filters in the widget based on current filter selection. */ virtual void updateFilters() = 0; /** * @brief Applies filename entered in the location edit * * Matching file item is selected on the files list if possible. */ virtual void applyEnteredFileName() = 0; virtual QStringList currentFilters() const = 0; QUrl startUrl() const; void addRecentDir(const QString &name); KexiFileFilters* filters(); + const KexiFileFilters* filters() const; + void setFiltersUpdated(bool set); bool filtersUpdated() const; void done(); private: class Private; Private * const d; }; #endif // KEXIFILEWIDGETINTERFACE_H