diff --git a/src/formeditor/kfdpixmapedit.cpp b/src/formeditor/kfdpixmapedit.cpp deleted file mode 100644 index 9b937caf8..000000000 --- a/src/formeditor/kfdpixmapedit.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2004 Cedric Pasteur - - 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 "kfdpixmapedit.h" - -#include -#include "form.h" -#include "objecttree.h" - -using namespace KFormDesigner; - -KFDPixmapEdit::KFDPixmapEdit(KProperty *property, QWidget *parent) - : KPropertyPixmapEditor(property, parent) -{ -} - -KFDPixmapEdit::~KFDPixmapEdit() -{} - -void -KFDPixmapEdit::selectPixmap() -{ - KPropertyPixmapEditor::selectPixmap(); -#if 0 //will be reenabled for new image collection - if (!m_manager->activeForm() || !property()) - return; - - ObjectTreeItem *item = m_manager->activeForm()->objectTree()->lookup(m_manager->activeForm()->selectedWidget()->name()); - QString name = item ? item->pixmapName(property()->name()) : ""; - PixmapCollectionChooser dialog(m_manager->activeForm()->pixmapCollection(), name, topLevelWidget()); - if (dialog.exec() == QDialog::Accepted) { - setValue(dialog.pixmap(), true); - item->setPixmapName(property()->name(), dialog.pixmapName()); - } -#endif -} - -//! @todo KEXI3 noi18n # added to disable message extraction in Messages.sh diff --git a/src/formeditor/kfdpixmapedit.h b/src/formeditor/kfdpixmapedit.h deleted file mode 100644 index c51eedea8..000000000 --- a/src/formeditor/kfdpixmapedit.h +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2004 Cedric Pasteur - - 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 KFORMEDITOR_PIXMAPEDIT_H -#define KFORMEDITOR_PIXMAPEDIT_H - -#include "kformdesigner_export.h" - - -namespace KFormDesigner -{ - -class KFORMDESIGNER_EXPORT KFDPixmapEdit : public KPropertyPixmapEditor -{ - Q_OBJECT - -public: - explicit KFDPixmapEdit(KProperty *property, QWidget *parent = 0); - virtual ~KFDPixmapEdit(); - -public Q_SLOTS: - virtual void selectPixmap(); -}; - -} - -#endif diff --git a/src/kexiutils/utils.cpp b/src/kexiutils/utils.cpp index 62db3e8c3..d41ec7db8 100644 --- a/src/kexiutils/utils.cpp +++ b/src/kexiutils/utils.cpp @@ -1,1152 +1,1158 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 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 #ifndef KEXI_MOBILE #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() : startedOrActive(false) { timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), this, SLOT(show())); } void DelayedCursorHandler::start(bool noDelay) { startedOrActive = true; timer.start(noDelay ? 0 : 1000); } void DelayedCursorHandler::stop() { startedOrActive = false; timer.stop(); QApplication::restoreOverrideCursor(); } void DelayedCursorHandler::show() { QApplication::restoreOverrideCursor(); QApplication::setOverrideCursor(QCursor(Qt::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) { setWaitCursor(noDelay); } WaitCursor::~WaitCursor() { 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) { QStringList result; QMetaEnum enumerator(metaProperty.enumerator()); const int count = enumerator.keyCount(); for (int i = 0; i < count; i++) result.append(QString::fromLatin1(enumerator.key(i))); return result; } QString KexiUtils::fileDialogFilterString(const QMimeType &mime, bool kdeFormat) { if (!mime.isValid()) { return QString(); } QString str; if (kdeFormat) { if (mime.globPatterns().isEmpty()) { str = "*"; } else { str = mime.globPatterns().join(" "); } str += "|"; } str += mime.comment(); if (!mime.globPatterns().isEmpty() || !kdeFormat) { str += " ("; if (mime.globPatterns().isEmpty()) str += "*"; else str += mime.globPatterns().join("; "); str += ")"; } if (kdeFormat) str += "\n"; else str += ";;"; return str; } QString KexiUtils::fileDialogFilterString(const QString& mimeName, bool kdeFormat) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeName); return fileDialogFilterString(mime, kdeFormat); } QString KexiUtils::fileDialogFilterStrings(const QStringList& mimeStrings, bool kdeFormat) { QString ret; QStringList::ConstIterator endIt = mimeStrings.constEnd(); for (QStringList::ConstIterator it = mimeStrings.constBegin(); it != endIt; ++it) ret += fileDialogFilterString(*it, kdeFormat); return ret; } //! @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); - dialog->exec(); - return dialog->selectedUrls().value(0); + 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); - dialog->exec(); - return dialog->selectedUrls().value(0); + if (QDialog::Accepted == dialog->exec()) { + return dialog->selectedUrls().value(0); + } else { + return QUrl(); + } } 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) { #ifndef KEXI_MOBILE 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::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::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()) {} const QByteArray name; 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; } //! @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 command = QString::fromLatin1("xfconf-query -c \"%1\" -p \"%2\"") .arg(channel.constData()) .arg(property.constData()); QProcess process; process.start(command, QIODevice::ReadOnly | QIODevice::Text); if (!process.waitForStarted()) { qWarning() << "Count not execute command" << command << "error:" << process.error(); return QByteArray(); } if (!process.waitForFinished() || process.exitStatus() != QProcess::NormalExit) { qWarning() << "Count not finish command" << command << "error:" << process.error() << "exit status:" << process.exitStatus(); return QByteArray(); } if (ok) { *ok = true; } if (process.exitCode() == 0) { result = process.readAll(); result.chop(1); } return result; // !=0 e.g. for "no such property or channel" } #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); } } diff --git a/src/plugins/forms/kexiformmanager.cpp b/src/plugins/forms/kexiformmanager.cpp index cf16c0faf..56aa4f34e 100644 --- a/src/plugins/forms/kexiformmanager.cpp +++ b/src/plugins/forms/kexiformmanager.cpp @@ -1,561 +1,562 @@ /* This file is part of the KDE project Copyright (C) 2005-2011 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 "kexiformmanager.h" #include "kexidbform.h" #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT #include "kexidbautofield.h" #endif #include "kexiformscrollview.h" #include "kexiformview.h" #include "kexidatasourcepage.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 class KexiFormManagerPrivate { public: explicit KexiFormManagerPrivate(KexiFormManager *qq) : part(0) , q(qq) { features = KFormDesigner::Form::NoFeatures; widgetActionGroup = new KFormDesigner::ActionGroup(q); #ifdef KFD_SIGSLOTS dragConnectionAction = 0; #endif widgetTree = 0; collection = 0; } ~KexiFormManagerPrivate() { } KexiFormPart* part; KFormDesigner::WidgetLibrary* lib; KFormDesigner::ActionGroup* widgetActionGroup; KFormDesigner::WidgetTreeWidget *widgetTree; KActionCollection *collection; KFormDesigner::Form::Features features; KToggleAction *pointerAction; #ifdef KFD_SIGSLOTS KToggleAction *dragConnectionAction; #endif KToggleAction *snapToGridAction; KexiFormManager *q; }; Q_GLOBAL_STATIC(KexiFormManager, g_manager) KexiFormManager* KexiFormManager::self() { return g_manager; } KexiFormManager::KexiFormManager() : QObject() , d(new KexiFormManagerPrivate(this)) { + // needed for custom "pixmap" property editor widget KexiCustomPropertyFactory::init(); } KexiFormManager::~KexiFormManager() { delete d; } void KexiFormManager::init(KexiFormPart *part, KFormDesigner::WidgetTreeWidget *widgetTree) { /*! @todo add configuration for supported factory groups */ QStringList supportedFactoryGroups; supportedFactoryGroups += "kexi"; d->lib = new KFormDesigner::WidgetLibrary(this, supportedFactoryGroups); d->lib->setAdvancedPropertiesVisible(false); connect(d->lib, SIGNAL(widgetCreated(QWidget*)), this, SLOT(slotWidgetCreatedByFormsLibrary(QWidget*))); connect(d->lib, SIGNAL(widgetActionToggled(QByteArray)), this, SLOT(slotWidgetActionToggled(QByteArray))); d->part = part; KActionCollection *col = /*tmp*/ new KActionCollection(this); if (col) { createActions( col ); //connect actions provided by widget factories connect(col->action("widget_assign_action"), SIGNAL(triggered()), this, SLOT(slotAssignAction())); } d->widgetTree = widgetTree; if (d->widgetTree) { //! @todo KEXI3 Port this: connect() //! @todo KEXI3 Port code related to KFormDesigner::FormManager::m_treeview here //! @todo connect(m_propSet, SIGNAL(widgetNameChanged(QByteArray,QByteArray)), //! @todo m_treeview, SLOT(renameItem(QByteArray,QByteArray))); } } KFormDesigner::ActionGroup* KexiFormManager::widgetActionGroup() const { return d->widgetActionGroup; } void KexiFormManager::createActions(KActionCollection* collection) { d->collection = collection; d->lib->createWidgetActions(d->widgetActionGroup); //! @todo insertWidget() slot? #ifdef KFD_SIGSLOTS if (d->features & KFormDesigner::Form::EnableConnections) { // nothing } else { d->dragConnectionAction = new KToggleAction( KexiIcon("signalslot"), futureI18n("Connect Signals/Slots"), d->collection); d->dragConnectionAction->setObjectName("drag_connection"); connect(d->dragConnectionAction, SIGNAL(triggered()), this, SLOT(startCreatingConnection())); d->dragConnectionAction->setChecked(false); } #endif d->pointerAction = new KToggleAction( koIcon("tool-pointer"), xi18n("Pointer"), d->collection); d->pointerAction->setObjectName("edit_pointer"); d->widgetActionGroup->addAction(d->pointerAction); connect(d->pointerAction, SIGNAL(triggered()), this, SLOT(slotPointerClicked())); d->pointerAction->setChecked(true); d->snapToGridAction = new KToggleAction( xi18n("Snap to Grid"), d->collection); d->snapToGridAction->setObjectName("snap_to_grid"); //! @todo #if 0 // Create the Style selection action (with a combo box in toolbar and submenu items) KSelectAction *styleAction = new KSelectAction( xi18n("Style"), d->collection); styleAction->setObjectName("change_style"); connect(styleAction, SIGNAL(triggered()), this, SLOT(slotStyle())); styleAction->setEditable(false); QString currentStyle(kapp->style()->objectName().toLower()); const QStringList styles = QStyleFactory::keys(); styleAction->setItems(styles); styleAction->setCurrentItem(0); QStringList::ConstIterator endIt = styles.constEnd(); int idx = 0; for (QStringList::ConstIterator it = styles.constBegin(); it != endIt; ++it, ++idx) { if ((*it).toLower() == currentStyle) { styleAction->setCurrentItem(idx); break; } } styleAction->setToolTip(xi18n("Set the current view style.")); styleAction->setMenuAccelsEnabled(true); #endif d->lib->addCustomWidgetActions(d->collection); #ifdef KEXI_DEBUG_GUI KConfigGroup generalGroup(KSharedConfig::openConfig()->group("General")); if (generalGroup.readEntry("ShowInternalDebugger", false)) { QAction *a = new QAction(koIcon("run-build-file"), xi18n("Show Form UI Code"), this); d->collection->addAction("show_form_ui", a); a->setShortcut(Qt::CTRL + Qt::Key_U); connect(a, SIGNAL(triggered()), this, SLOT(showFormUICode())); } #endif //! @todo move elsewhere { // (from obsolete kexiformpartinstui.rc) QStringList formActions; formActions << "edit_pointer" << QString() //sep #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT << "library_widget_KexiDBAutoField" #endif << "library_widget_KexiDBLabel" << "library_widget_KexiDBLineEdit" << "library_widget_KexiDBTextEdit" << "library_widget_KexiDBComboBox" << "library_widget_KexiDBCheckBox" << "library_widget_KexiDBImageBox" << QString() //sep << "library_widget_KexiDBPushButton" << QString() //sep << "library_widget_KexiFrame" << "library_widget_QGroupBox" << "library_widget_KFDTabWidget" << "library_widget_KexiLineWidget" << QString() //sep #ifdef HAVE_QTWEBKITWIDGETS << "library_widget_WebBrowserWidget" #endif #ifdef HAVE_MARBLE << "library_widget_MapBrowserWidget" #endif << "library_widget_KexiDBSlider" << "library_widget_KexiDBProgressBar" << "library_widget_KexiDBCommandLinkButton" << "library_widget_KexiDBDatePicker" << QString() //sep ; KexiMainWindowIface *win = KexiMainWindowIface::global(); foreach( const QString& actionName_, formActions ) { QAction *a; const QString actionName(actionName_.startsWith(':') ? actionName_.mid(1) : actionName_); if (actionName.isEmpty()) { a = new QAction(this); a->setSeparator(true); } else { a = d->widgetActionGroup->action(actionName); } if (actionName_.startsWith(':')) { // icon only KexiSmallToolButton *btn = new KexiSmallToolButton(a, win->toolBar("form")); btn->setToolButtonStyle(Qt::ToolButtonIconOnly); win->appendWidgetToToolbar("form", btn); } else { win->addToolBarAction("form", a); } } QSet iconOnlyActions; const QList actions( d->collection->actions() ); foreach( QAction *a, actions ) { if (iconOnlyActions.contains(a->objectName())) { // icon only KexiSmallToolButton *btn = new KexiSmallToolButton(a, win->toolBar("form")); btn->setToolButtonStyle(Qt::ToolButtonIconOnly); win->appendWidgetToToolbar("form", btn); win->setWidgetVisibleInToolbar(btn, true); } else { win->addToolBarAction("form", a); } } } } void KexiFormManager::slotWidgetCreatedByFormsLibrary(QWidget* widget) { QList _signals(KexiUtils::methodsForMetaObject( widget->metaObject(), QMetaMethod::Signal)); if (!_signals.isEmpty()) { QByteArray handleDragMoveEventSignal = "handleDragMoveEvent(QDragMoveEvent*)"; QByteArray handleDropEventSignal = "handleDropEvent(QDropEvent*)"; KexiFormView *formView = KDbUtils::findParent(widget); foreach(const QMetaMethod& method, _signals) { if (0 == qstrcmp(method.methodSignature(), handleDragMoveEventSignal)) { //qDebug() << method.methodSignature(); if (formView) { connect(widget, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)), formView, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*))); } } else if (0 == qstrcmp(method.methodSignature(), handleDropEventSignal)) { //qDebug() << method.methodSignature(); if (formView) { connect(widget, SIGNAL(handleDropEvent(QDropEvent*)), formView, SLOT(slotHandleDropEvent(QDropEvent*))); } } } } } void KexiFormManager::slotWidgetActionToggled(const QByteArray& action) { KexiFormView* fv = activeFormViewWidget(); if (fv) { fv->form()->enterWidgetInsertingState(action); } } KFormDesigner::WidgetLibrary* KexiFormManager::library() const { return d->lib; } QAction* KexiFormManager::action(const char* name) { KActionCollection *col = d->part->actionCollectionForMode(Kexi::DesignViewMode); if (!col) return 0; QString n(translateName(name)); QAction *a = col->action(n); if (a) return a; if (activeFormViewWidget()) { a = KexiMainWindowIface::global()->actionCollection()->action(n); if (a) return a; } return d->collection->action(name); } KexiFormView* KexiFormManager::activeFormViewWidget() const { KexiWindow *currentWindow = KexiMainWindowIface::global()->currentWindow(); if (!currentWindow) return 0; KexiFormView *currentView = dynamic_cast(currentWindow->selectedView()); if (!currentView || currentView->viewMode()!=Kexi::DesignViewMode) { return 0; } KFormDesigner::Form *form = currentView->form(); if (!form) { return 0; } KexiDBForm *dbform = dynamic_cast(form->formWidget()); if (!dbform) { return 0; } KexiFormScrollView *scrollViewWidget = dynamic_cast(dbform->dataAwareObject()); if (!scrollViewWidget) return 0; return dynamic_cast(scrollViewWidget->parent()); } void KexiFormManager::enableAction(const char* name, bool enable) { KexiFormView* formViewWidget = activeFormViewWidget(); if (!formViewWidget) return; formViewWidget->setAvailable(translateName(name).toLatin1(), enable); } void KexiFormManager::setFormDataSource(const QString& pluginId, const QString& name) { KexiFormView* formViewWidget = activeFormViewWidget(); if (!formViewWidget) return; KexiDBForm* formWidget = dynamic_cast(formViewWidget->form()->widget()); if (!formWidget) return; QString oldDataSourcePartClass(formWidget->dataSourcePluginId()); QString oldDataSource(formWidget->dataSource()); if (pluginId != oldDataSourcePartClass || name != oldDataSource) { QHash propValues; propValues.insert("dataSource", name); propValues.insert("dataSourcePartClass", pluginId); KFormDesigner::PropertyCommandGroup *group = new KFormDesigner::PropertyCommandGroup( xi18n("Set form's data source to %1", name)); formViewWidget->form()->createPropertyCommandsInDesignMode( formWidget, propValues, group, true /*addToActiveForm*/); } } void KexiFormManager::setDataSourceFieldOrExpression( const QString& string, const QString& caption, KDbField::Type type) { KexiFormView* formViewWidget = activeFormViewWidget(); if (!formViewWidget) return; KPropertySet* set = formViewWidget->form()->propertySet(); if (!set->contains("dataSource")) return; set->property("dataSource").setValue(string); if (set->propertyValue("autoCaption", false).toBool()) { set->changePropertyIfExists("fieldCaptionInternal", caption); } if (set->propertyValue("widgetType").toString() == "Auto") { set->changePropertyIfExists("fieldTypeInternal", type); } } void KexiFormManager::insertAutoFields(const QString& sourcePartClass, const QString& sourceName, const QStringList& fields) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT Q_UNUSED(sourcePartClass); Q_UNUSED(sourceName); Q_UNUSED(fields); #else KexiFormView* formViewWidget = activeFormViewWidget(); if (!formViewWidget || !formViewWidget->form() || !formViewWidget->form()->activeContainer()) return; formViewWidget->insertAutoFields(sourcePartClass, sourceName, fields, formViewWidget->form()->activeContainer()); #endif } void KexiFormManager::slotHistoryCommandExecuted(KFormDesigner::Command *command) { if (command->childCount() == 2) { KexiFormView* formViewWidget = activeFormViewWidget(); if (!formViewWidget) return; KexiDBForm* formWidget = dynamic_cast(formViewWidget->form()->widget()); if (!formWidget) return; const KFormDesigner::PropertyCommand* pc1 = dynamic_cast(command->child(0)); const KFormDesigner::PropertyCommand* pc2 = dynamic_cast(command->child(1)); if (pc1 && pc2 && pc1->propertyName() == "dataSource" && pc2->propertyName() == "dataSourcePartClass") { const QHash::const_iterator it1(pc1->oldValues().constBegin()); const QHash::const_iterator it2(pc2->oldValues().constBegin()); if (it1.key() == formWidget->objectName() && it2.key() == formWidget->objectName()) d->part->dataSourcePage()->setFormDataSource( formWidget->dataSourcePluginId(), formWidget->dataSource()); } } } void KexiFormManager::showFormUICode() { #ifdef KEXI_DEBUG_GUI KexiFormView* formView = activeFormViewWidget(); if (!formView) return; formView->form()->resetInlineEditor(); QString uiCode; const int indent = 2; if (!KFormDesigner::FormIO::saveFormToString(formView->form(), uiCode, indent)) { //! @todo show err? return; } KPageDialog uiCodeDialog; uiCodeDialog.setFaceType(KPageDialog::Tabbed); uiCodeDialog.setModal(true); uiCodeDialog.setWindowTitle(xi18nc("@title:window", "Form's UI Code")); uiCodeDialog.resize(700, 600); KTextEdit *currentUICodeDialogEditor = new KTextEdit(&uiCodeDialog); uiCodeDialog.addPage(currentUICodeDialogEditor, xi18n("Current")); currentUICodeDialogEditor->setReadOnly(true); QFont f(currentUICodeDialogEditor->font()); f.setFamily("courier"); currentUICodeDialogEditor->setFont(f); KTextEdit *originalUICodeDialogEditor = new KTextEdit(&uiCodeDialog); uiCodeDialog.addPage(originalUICodeDialogEditor, xi18n("Original")); originalUICodeDialogEditor->setReadOnly(true); originalUICodeDialogEditor->setFont(f); currentUICodeDialogEditor->setPlainText(uiCode); //indent and set our original doc as well: QDomDocument doc; doc.setContent(formView->form()->m_recentlyLoadedUICode); originalUICodeDialogEditor->setPlainText(doc.toString(indent)); uiCodeDialog.exec(); #endif } void KexiFormManager::slotAssignAction() { KexiFormView* formView = activeFormViewWidget(); if (!formView) return; KFormDesigner::Form *form = formView->form(); KexiDBForm *dbform = 0; if (form->mode() != KFormDesigner::Form::DesignMode || !(dbform = dynamic_cast(form->formWidget()))) { return; } KPropertySet* set = form->propertySet(); KexiFormEventAction::ActionData data; const KProperty &onClickActionProp = set->property("onClickAction"); if (!onClickActionProp.isNull()) data.string = onClickActionProp.value().toString(); const KProperty &onClickActionOptionProp = set->property("onClickActionOption"); if (!onClickActionOptionProp.isNull()) data.option = onClickActionOptionProp.value().toString(); KexiFormScrollView *scrollViewWidget = dynamic_cast(dbform->dataAwareObject()); if (!scrollViewWidget) return; KexiFormView* formViewWidget = dynamic_cast(scrollViewWidget->parent()); if (!formViewWidget) return; KexiActionSelectionDialog dlg(dbform, data, set->property("objectName").value().toString()); if (dlg.exec() == QDialog::Accepted) { data = dlg.currentAction(); //update property value set->changeProperty("onClickAction", data.string); set->changeProperty("onClickActionOption", data.option); } } void KexiFormManager::slotPointerClicked() { KexiFormView* formView = activeFormViewWidget(); if (!formView) return; formView->form()->enterWidgetSelectingState(); } QString KexiFormManager::translateName(const char* name) const { QString n(QString::fromLatin1(name)); // translate to our name space: if (n.startsWith("align_") || n.startsWith("adjust_") || n == "format_raise" || n == "format_lower" || n == "taborder") { n.prepend("formpart_"); } return n; } diff --git a/src/plugins/reports/kexireportpart.cpp b/src/plugins/reports/kexireportpart.cpp index e84a4a9d9..7fcd3359e 100644 --- a/src/plugins/reports/kexireportpart.cpp +++ b/src/plugins/reports/kexireportpart.cpp @@ -1,200 +1,203 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg * Copyright (C) 2011-2015 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kexireportpart.h" #include #include #include #include #include #include "kexireportview.h" #include "kexireportdesignview.h" #include #include "kexisourceselector.h" +#include //! @internal class Q_DECL_HIDDEN KexiReportPart::Private { public: Private() : toolboxActionGroup(0) { sourceSelector = 0; } ~Private() { } KexiSourceSelector *sourceSelector; QActionGroup toolboxActionGroup; QMap toolboxActionsByName; }; KexiReportPart::KexiReportPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "report"), xi18nc("tooltip", "Create new report"), xi18nc("what's this", "Creates new report."), l) , d(new Private) { setInternalPropertyValue("newObjectsAreDirty", true); + // needed for custom "pixmap" property editor widget + KexiCustomPropertyFactory::init(); } KexiReportPart::~KexiReportPart() { delete d; } KLocalizedString KexiReportPart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of report %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Report %1 already exists.")); return Part::i18nMessage(englishMessage, window); } KexiView* KexiReportPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(window); Q_UNUSED(item); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiReportView(parent); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiReportDesignView(parent, d->sourceSelector); connect(d->sourceSelector, &KexiSourceSelector::sourceDataChanged, qobject_cast(view), &KexiReportDesignView::slotSourceDataChanged); connect(view, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); } return view; } void KexiReportPart::initPartActions() { KexiMainWindowIface *win = KexiMainWindowIface::global(); QList reportActions = KReportDesigner::itemActions(&d->toolboxActionGroup); foreach(QAction* action, reportActions) { connect(action, SIGNAL(triggered(bool)), this, SLOT(slotToolboxActionTriggered(bool))); win->addToolBarAction("report", action); d->toolboxActionsByName.insert(action->objectName(), action); } } KDbObject* KexiReportPart::loadSchemaObject( KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { QString layout; if ( !loadDataBlock(window, &layout, "layout") == true && !loadDataBlock(window, &layout, "pgzreport_layout") == true /* compat */) { return 0; } QDomDocument doc; if (!doc.setContent(layout)) { return 0; } KexiReportPartTempData * temp = static_cast(window->data()); const QDomElement root = doc.documentElement(); temp->reportDefinition = root.firstChildElement("report:content"); if (temp->reportDefinition.isNull()) { qWarning() << "no report report:content element found in report" << window->partItem()->name(); return 0; } temp->connectionDefinition = root.firstChildElement("connection"); if (temp->connectionDefinition.isNull()) { qWarning() << "no report report:content element found in report" << window->partItem()->name(); return 0; } return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow); } KexiWindowData* KexiReportPart::createWindowData(KexiWindow* window) { return new KexiReportPartTempData(window); } KexiReportPartTempData::KexiReportPartTempData(QObject* parent) : KexiWindowData(parent) , reportSchemaChangedInPreviousView(true /*to force reloading on startup*/) { } void KexiReportPart::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->sourceSelector) { d->sourceSelector = new KexiSourceSelector(KexiMainWindowIface::global()->project(), tab); } tab->addTab(d->sourceSelector, koIcon("server-database"), QString()); tab->setTabToolTip(tab->indexOf(d->sourceSelector), xi18n("Data Source")); } void KexiReportPart::slotToolboxActionTriggered(bool checked) { if (!checked) return; QObject *theSender = sender(); if (!theSender) return; QString senderName = sender()->objectName(); KexiMainWindowIface *mainwin = KexiMainWindowIface::global(); KexiWindow *win = mainwin->currentWindow(); if (!win) return; KexiView *designView = win->viewForMode(Kexi::DesignViewMode); if (designView) { KexiReportDesignView *dv = dynamic_cast(designView); if (!dv) return; dv->triggerAction(senderName); } } void KexiReportPart::slotItemInserted(const QString& entity) { Q_UNUSED(entity); // uncheck toolbox action after it is used QAction * a = d->toolboxActionGroup.checkedAction(); if (a) { a->setChecked(false); } } diff --git a/src/plugins/tables/kexitabledesignerview.cpp b/src/plugins/tables/kexitabledesignerview.cpp index 48c865bb8..246d5f294 100644 --- a/src/plugins/tables/kexitabledesignerview.cpp +++ b/src/plugins/tables/kexitabledesignerview.cpp @@ -1,1892 +1,1890 @@ /* This file is part of the KDE project Copyright (C) 2004-2012 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 "kexitabledesignerview.h" #include "kexitabledesignerview_p.h" #include "kexilookupcolumnpage.h" #include "kexitabledesignercommands.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! used only for BLOBs #define DEFAULT_OBJECT_TYPE_VALUE "image" //#define KexiTableDesignerView_DEBUG //! @todo remove this when BLOBs are implemented //#define KEXI_NO_BLOB_FIELDS // Defining this removes alter table! #define KEXI_NO_UNDOREDO_ALTERTABLE using namespace KexiTableDesignerCommands; //! @internal Used in tryCastQVariant() anf canCastQVariant() static bool isIntegerQVariant(QVariant::Type t) { return t == QVariant::LongLong || t == QVariant::ULongLong || t == QVariant::Int || t == QVariant::UInt; } //! @internal Used in tryCastQVariant() static bool canCastQVariant(QVariant::Type fromType, QVariant::Type toType) { return (fromType == QVariant::Int && toType == QVariant::UInt) || (fromType == QVariant::ByteArray && toType == QVariant::String) || (fromType == QVariant::LongLong && toType == QVariant::ULongLong) || ((fromType == QVariant::String || fromType == QVariant::ByteArray) && (isIntegerQVariant(toType) || toType == QVariant::Double)); } /*! @internal \return a variant value converted from \a fromVal to \a toType type. Null QVariant is returned if \a fromVal's type and \a toType type are incompatible. */ static QVariant tryCastQVariant(const QVariant& fromVal, QVariant::Type toType) { const QVariant::Type fromType = fromVal.type(); if (fromType == toType) return fromVal; if (canCastQVariant(fromType, toType) || canCastQVariant(toType, fromType) || (isIntegerQVariant(fromType) && toType == QVariant::Double)) { QVariant res(fromVal); if (res.convert(toType)) return res; } return QVariant(); } KexiTableDesignerView::KexiTableDesignerView(QWidget *parent) : KexiDataTableView(parent, false/*not db-aware*/) , KexiTableDesignerInterface() , d(new KexiTableDesignerViewPrivate(this)) { setObjectName("KexiTableDesignerView"); - //needed for custom "identifier" property editor widget - KexiCustomPropertyFactory::init(); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); d->view = dynamic_cast(mainWidget()); d->data = new KDbTableViewData(); if (conn->options()->isReadOnly()) d->data->setReadOnly(true); d->data->setInsertingEnabled(false); KDbTableViewColumn *col = new KDbTableViewColumn("pk", KDbField::Text, QString(), xi18n("Additional information about the field")); col->setIcon(KexiUtils::colorizeIconToTextColor(koSmallIcon("help-about"), d->view->palette())); col->setHeaderTextVisible(false); col->field()->setSubType("QIcon"); col->setReadOnly(true); d->data->addColumn(col); col = new KDbTableViewColumn("caption", KDbField::Text, xi18n("Field Caption"), xi18n("Describes caption for the field")); d->data->addColumn(col); col = new KDbTableViewColumn("type", KDbField::Enum, xi18n("Data Type"), xi18n("Describes data type for the field")); d->data->addColumn(col); #ifdef KEXI_NO_BLOB_FIELDS //! @todo remove this later QVector types(KDbField::LastTypeGroup - 1); //don't show last type (BLOB) #else QVector types(KDbField::LastTypeGroup); #endif d->maxTypeNameTextWidth = 0; QFontMetrics fm(font()); for (int i = KDbField::TextGroup; i <= types.count(); i++) { types[i-1] = KDbField::typeGroupName(KDb::intToFieldTypeGroup(i)); d->maxTypeNameTextWidth = qMax(d->maxTypeNameTextWidth, fm.width(types[i-1])); } col->field()->setEnumHints(types); d->data->addColumn(col = new KDbTableViewColumn("comments", KDbField::Text, xi18n("Comments"), xi18n("Describes additional comments for the field"))); d->view->setSpreadSheetMode(true); connect(d->data, SIGNAL(aboutToChangeCell(KDbRecordData*,int,QVariant*,KDbResultInfo*)), this, SLOT(slotBeforeCellChanged(KDbRecordData*,int,QVariant*,KDbResultInfo*))); connect(d->data, SIGNAL(recordUpdated(KDbRecordData*)), this, SLOT(slotRecordUpdated(KDbRecordData*))); connect(d->data, SIGNAL(aboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool)), this, SLOT(slotAboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool))); setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height()); d->view->setFocus(); d->sets = new KexiDataAwarePropertySet(this, d->view); connect(d->sets, SIGNAL(recordDeleted()), this, SLOT(updateActions())); connect(d->sets, SIGNAL(recordInserted()), this, SLOT(slotRecordInserted())); connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu())); // - setup local actions QList viewActions; QAction* a; viewActions << (d->action_toggle_pkey = new KToggleAction(KexiIcon("database-key"), xi18n("Primary Key"), this)); a = d->action_toggle_pkey; a->setObjectName("tablepart_toggle_pkey"); a->setToolTip(xi18n("Sets or removes primary key")); a->setWhatsThis(xi18n("Sets or removes primary key for currently selected field.")); connect(a, SIGNAL(triggered()), this, SLOT(slotTogglePrimaryKey())); setViewActions(viewActions); d->view->contextMenu()->insertAction( d->view->contextMenu()->actions()[1], d->action_toggle_pkey); //add at the beginning as 2nd d->view->contextMenu()->insertSeparator(d->view->contextMenu()->actions()[2]); //as 3rd setAvailable("tablepart_toggle_pkey", !conn->options()->isReadOnly()); #ifndef KEXI_NO_UNDOREDO_ALTERTABLE plugSharedAction("edit_undo", this, SLOT(slotUndo())); plugSharedAction("edit_redo", this, SLOT(slotRedo())); setAvailable("edit_undo", false); setAvailable("edit_redo", false); #endif #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString()); //to create the tab KexiUtils::connectPushButtonActionForDebugWindow( "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution())); KexiUtils::connectPushButtonActionForDebugWindow( "executeRealAlterTable", this, SLOT(executeRealAlterTable())); #endif // setup main menu actions QList mainMenuActions; a = sharedAction("edit_clear_table"); mainMenuActions << a; setMainMenuActions(mainMenuActions); } KexiTableDesignerView::~KexiTableDesignerView() { delete d; } void KexiTableDesignerView::initData() { //add column data d->data->deleteAllRecords(); int tableFieldCount = 0; d->primaryKeyExists = false; if (tempData()->table()) { tableFieldCount = tempData()->table()->fieldCount(); //recreate table data records for (int i = 0; i < tableFieldCount; i++) { KDbField *field = tempData()->table()->field(i); KDbRecordData *data = d->data->createItem(); if (field->isPrimaryKey()) { (*data)[COLUMN_ID_ICON] = KexiIconName("database-key"); d->primaryKeyExists = true; } else { KDbLookupFieldSchema *lookupFieldSchema = field->table() ? field->table()->lookupFieldSchema(*field) : 0; if (lookupFieldSchema && lookupFieldSchema->recordSource().type() != KDbLookupFieldSchemaRecordSource::NoType && !lookupFieldSchema->recordSource().name().isEmpty()) { (*data)[COLUMN_ID_ICON] = KexiIconName("combobox"); } } (*data)[COLUMN_ID_CAPTION] = field->captionOrName(); (*data)[COLUMN_ID_TYPE] = field->typeGroup() - 1; //-1 because type groups are counted from 1 (*data)[COLUMN_ID_DESC] = field->description(); d->data->append(data); } } //add empty space, at least 2 times more than number of existing fields int fullSize = qMax(d->sets->size(), 2 * tableFieldCount); for (int i = tableFieldCount; i < fullSize; i++) { d->data->append(d->data->createItem()); } //set data for our spreadsheet: this will clear our sets d->view->setData(d->data); //now recreate property sets if (tempData()->table()) { for (int i = 0; i < tableFieldCount; i++) { KDbField *field = tempData()->table()->field(i); createPropertySet(i, *field); } } //column widths d->view->setColumnWidth(COLUMN_ID_ICON, IconSize(KIconLoader::Small) + 10); d->view->setColumnResizeEnabled(COLUMN_ID_ICON, false); d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->recordHeight()); d->view->setStretchLastColumn(true); const int minCaptionColumnWidth = d->view->fontMetrics().width("wwwwwwwwwww"); if (minCaptionColumnWidth > d->view->columnWidth(COLUMN_ID_CAPTION)) d->view->setColumnWidth(COLUMN_ID_CAPTION, minCaptionColumnWidth); setDirty(false); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); //set @ name column propertySetSwitched(); } //! Gets subtype strings and names for type \a fieldType void KexiTableDesignerView::getSubTypeListData(KDbField::TypeGroup fieldTypeGroup, QStringList& stringsList, QStringList& namesList) { /* disabled - "mime" is moved from subType to "objectType" custom property if (fieldTypeGroup==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo hardcoded! stringsList << "image"; namesList << xi18n("Image object type", "Image"); } else {*/ stringsList = KDb::fieldTypeStringsForGroup(fieldTypeGroup); namesList = KDb::fieldTypeNamesForGroup(fieldTypeGroup); // } qDebug() << "subType strings: " << stringsList.join("|") << "\nnames: " << namesList.join("|"); } KPropertySet * KexiTableDesignerView::createPropertySet(int record, const KDbField& field, bool newOne) { KPropertySet *set = new KPropertySet(d->sets); if (KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()) set->setReadOnly(true); KProperty *prop; set->addProperty(prop = new KProperty("uid", d->generateUniqueId(), "")); prop->setVisible(false); //meta-info for property editor set->addProperty(prop = new KProperty("this:classString", xi18nc("Table field", "Field"))); prop->setVisible(false); set->addProperty(prop = new KProperty("this:iconName", //! \todo add table_field icon KexiIconName("lineedit") //"table_field" )); prop->setVisible(false); set->addProperty(prop = new KProperty("this:useCaptionAsObjectName", QVariant(true), QString())); //we want "caption" to be displayed in the header, not name prop->setVisible(false); //name set->addProperty(prop = new KProperty( "name", QVariant(field.name()), xi18n("Name"), QString(), KexiCustomPropertyFactory::Identifier)); //type set->addProperty(prop = new KProperty("type", QVariant(field.type()), xi18n("Type"))); #ifndef KexiTableDesignerView_DEBUG prop->setVisible(false);//always hidden #endif //subtype QStringList typeStringList, typeNameList; getSubTypeListData(field.typeGroup(), typeStringList, typeNameList); /* disabled - "mime" is moved from subType to "objectType" custom property QString subTypeValue; if (field.typeGroup()==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes subTypeValue = slist.first(); } else {*/ QString subTypeValue = field.typeString(); //} set->addProperty(prop = new KProperty("subType", typeStringList, typeNameList, subTypeValue, xi18n("Subtype"))); // objectType QStringList objectTypeStringList, objectTypeNameList; //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes objectTypeStringList << "image"; objectTypeNameList << xi18nc("Image object type", "Image"); QString objectTypeValue(field.customProperty("objectType").toString()); if (objectTypeValue.isEmpty()) objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE; set->addProperty(prop = new KProperty("objectType", objectTypeStringList, objectTypeNameList, objectTypeValue, xi18n("Subtype")/*! @todo other i18n string?*/)); set->addProperty(prop = new KProperty("caption", QVariant(field.caption()), xi18n("Caption"))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("description", QVariant(field.description()))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("unsigned", QVariant(field.isUnsigned()), xi18n("Unsigned Number"))); set->addProperty(prop = new KProperty("maxLength", field.maxLength(), xi18n("Max Length"))); set->addProperty(prop = new KProperty("maxLengthIsDefault", field.maxLengthStrategy() == KDbField::DefaultMaxLength)); prop->setVisible(false); //always hidden set->addProperty(prop = new KProperty("precision", (int)field.precision()/*200?*/, xi18n("Precision"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("visibleDecimalPlaces", field.visibleDecimalPlaces(), xi18n("Visible Decimal Places"))); prop->setOption("min", -1); prop->setOption("minValueText", xi18nc("Auto Decimal Places", "Auto")); //! @todo set reasonable default for column width set->addProperty(prop = new KProperty("defaultWidth", QVariant(0) /*field.width()*//*200?*/, xi18n("Default Width"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("defaultValue", field.defaultValue(), xi18n("Default Value"), QString(), //! @todo use "Variant" type here when supported by KProperty (KProperty::Type)field.variantType())); prop->setOption("3rdState", xi18n("None")); set->addProperty(prop = new KProperty("primaryKey", QVariant(field.isPrimaryKey()), xi18n("Primary Key"))); prop->setIconName(KexiIconName("database-key")); set->addProperty(prop = new KProperty("unique", QVariant(field.isUniqueKey()), xi18n("Unique"))); set->addProperty(prop = new KProperty("notNull", QVariant(field.isNotNull()), xi18n("Required"))); set->addProperty(prop = new KProperty("allowEmpty", QVariant(!field.isNotEmpty()), xi18n("Allow Zero\nSize"))); set->addProperty(prop = new KProperty("autoIncrement", QVariant(field.isAutoIncrement()), xi18n("Autonumber"))); prop->setIconName(koIconName("autonumber")); set->addProperty(prop = new KProperty("indexed", QVariant(field.isIndexed()), xi18n("Indexed"))); //- properties related to lookup columns (used and set by the "lookup column" // tab in the property pane) KDbLookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : 0; set->addProperty(prop = new KProperty("rowSource", lookupFieldSchema ? lookupFieldSchema->recordSource().name() : QString(), xi18n("Record Source"))); prop->setVisible(false); set->addProperty(prop = new KProperty("rowSourceType", lookupFieldSchema ? lookupFieldSchema->recordSource().typeName() : QString(), xi18nc("Record source type (in two records)", "Record Source\nType"))); prop->setVisible(false); set->addProperty(prop = new KProperty("boundColumn", lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, xi18n("Bound Column"))); prop->setVisible(false); //! @todo this is backward-compatible code for "single visible column" implementation //! for multiple columns, only the first is displayed, so there is a data loss is GUI is used //! -- special kproperty editor needed int visibleColumn = -1; if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty()) visibleColumn = lookupFieldSchema->visibleColumns().first(); set->addProperty(prop = new KProperty("visibleColumn", visibleColumn, xi18n("Visible Column"))); prop->setVisible(false); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() //---- d->updatePropertiesVisibility(field.type(), *set); connect(set, SIGNAL(propertyChanged(KPropertySet&,KProperty&)), this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&))); d->sets->set(record, set, newOne); return set; } void KexiTableDesignerView::updateActions(bool activated) { Q_UNUSED(activated); /*! \todo check if we can set pkey for this column type (eg. BLOB?) */ setAvailable("tablepart_toggle_pkey", propertySet() != 0 && !KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()); if (!propertySet()) return; KPropertySet &set = *propertySet(); d->slotTogglePrimaryKeyCalled = true; d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool()); d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::slotUpdateRecordActions(int record) { KexiDataTableView::slotUpdateRecordActions(record); updateActions(); } void KexiTableDesignerView::slotTogglePrimaryKey() { if (d->slotTogglePrimaryKeyCalled) return; d->slotTogglePrimaryKeyCalled = true; if (!propertySet()) return; KPropertySet &set = *propertySet(); bool isSet = !set["primaryKey"].value().toBool(); set.changeProperty("primaryKey", QVariant(isSet)); //this will update all related properties as well d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::switchPrimaryKey(KPropertySet &propertySet, bool set, bool aWasPKey, Command* commandGroup) { const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool(); d->setPropertyValueIfNeeded(propertySet, "primaryKey", QVariant(set), commandGroup); if (&propertySet == this->propertySet()) { //update action and icon @ column 0 (only if we're changing current property set) d->action_toggle_pkey->setChecked(set); if (d->view->selectedRecord()) { //show key in the table d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_ICON, QVariant(set ? KexiIconName("database-key") : QLatin1String(""))); d->view->data()->saveRecordChanges(d->view->selectedRecord(), true); } if (was_pkey || set) //change flag only if we're setting pk or really clearing it d->primaryKeyExists = set; } if (set) { //primary key is set, remove old pkey if exists KPropertySet *s = 0; int i; const int count = (int)d->sets->size(); for (i = 0; i < count; i++) { s = d->sets->at(i); if ( s && s != &propertySet && (*s)["primaryKey"].value().toBool() && i != d->view->currentRecord()) { break; } } if (i < count) {//remove d->setPropertyValueIfNeeded(*s, "autoIncrement", QVariant(false), commandGroup); d->setPropertyValueIfNeeded(*s, "primaryKey", QVariant(false), commandGroup); //remove key from table d->view->data()->clearRecordEditBuffer(); KDbRecordData *data = d->view->recordAt(i); if (data) { d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(data, true); } } //set unsigned big-integer type d->slotBeforeCellChanged_enabled = false; d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); d->view->data()->saveRecordChanges(d->view->selectedRecord(), true); d->setPropertyValueIfNeeded(propertySet, "subType", KDbField::typeString(KDbField::BigInteger), commandGroup); d->setPropertyValueIfNeeded(propertySet, "unsigned", QVariant(true), commandGroup); //! @todo d->slotBeforeCellChanged_enabled = true; } updateActions(); } tristate KexiTableDesignerView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); if (!d->view->acceptRecordEditing()) return false; tristate res = true; if (mode == Kexi::DataViewMode) { if (!isDirty() && window()->neverSaved()) { KMessageBox::sorry(this, xi18n("Cannot switch to data view, because table design is empty.\n" "First, please create your design.")); return cancelled; } // else if (isDirty() && !window()->neverSaved()) { // cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?"))); // KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); bool emptyTable; bool isPhysicalAlteringNeeded = this->isPhysicalAlteringNeeded(); KLocalizedString message( kxi18nc("@info", "Saving changes for existing table design is now required.%1") .subs(d->messageForSavingChanges(&emptyTable, /*skip warning?*/!isPhysicalAlteringNeeded))); if (emptyTable) { isPhysicalAlteringNeeded = false; // eventually, not needed because there's no data } KGuiItem saveItem(KStandardGuiItem::save()); saveItem.setToolTip(QString()); KGuiItem discardItem(KStandardGuiItem::discard()); discardItem.setToolTip(QString()); if (isPhysicalAlteringNeeded) { saveItem.setText(xi18nc("@action:button", "Save Design and Remove Table Data")); discardItem.setText(xi18nc("@action:button", "Discard Design")); } const KMessageBox::ButtonCode r = KMessageBox::warningYesNoCancel(this, message.toString(), QString(), saveItem, discardItem, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (r == KMessageBox::Cancel) res = cancelled; else res = true; *dontStore = (r != KMessageBox::Yes); if (!*dontStore) d->dontAskOnStoreData = true; } // //! @todo return res; } else if (mode == Kexi::TextViewMode) { //! @todo } return res; } tristate KexiTableDesignerView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode || mode == Kexi::DataViewMode) { initData(); } return true; } KPropertySet *KexiTableDesignerView::propertySet() { return d->sets ? d->sets->currentPropertySet() : 0; } void KexiTableDesignerView::slotBeforeCellChanged( KDbRecordData *data, int colnum, QVariant* newValue, KDbResultInfo* /*result*/) { if (!d->slotBeforeCellChanged_enabled) return; // qDebug() << d->view->selectedRecord() << " " << item //<< " " << d->sets->at( d->view->currentRecord() ) << " " << propertySet(); if (colnum == COLUMN_ID_CAPTION) {//'caption' //if 'type' is not filled yet if (data->at(COLUMN_ID_TYPE).isNull()) { //auto select 1st record of 'type' column d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant((int)0)); } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (propertySetForRecord) { d->addHistoryCommand_in_slotPropertyChanged_enabled = false; // because we'll add // the two changes as one group QString oldName(propertySetForRecord->property("name").value().toString()); QString oldCaption(propertySetForRecord->property("caption").value().toString()); //remember this action containing 2 subactions //Parent command is a Command containing 2 child commands Command *changeCaptionAndNameCommand = new Command( kundo2_i18n( "Change %1 field name to %2 " "and caption from %3 to %4", oldName, propertySetForRecord->property("name").value().toString(), oldCaption, newValue->toString()), 0, this ); //we need to create the action now as set["name"] will be changed soon. //Child 1 is the caption /*ChangeFieldPropertyCommand *changeCaptionCommand = */ (void)new ChangeFieldPropertyCommand(changeCaptionAndNameCommand, this, *propertySetForRecord, "caption", oldCaption, *newValue); //update field caption and name propertySetForRecord->changeProperty("caption", *newValue); propertySetForRecord->changeProperty("name", KDb::stringToIdentifier(newValue->toString())); //Child 2 is the name /*ChangeFieldPropertyCommand *changeNameCommand =*/ (void)new ChangeFieldPropertyCommand( changeCaptionAndNameCommand, this, *propertySetForRecord, "name", oldName, propertySetForRecord->property("name").value().toString()); addHistoryCommand(changeCaptionAndNameCommand, false /* !execute */); d->addHistoryCommand_in_slotPropertyChanged_enabled = true; } } else if (colnum == COLUMN_ID_TYPE) {//'type' if (newValue->isNull()) { //'type' col will be cleared: clear all other columns as well d->slotBeforeCellChanged_enabled = false; d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, QVariant(QString())); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, QVariant()); d->slotBeforeCellChanged_enabled = true; return; } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; KPropertySet &set = *propertySetForRecord; //propertySet(); //'type' col is changed (existed before) //-get type group number KDbField::TypeGroup fieldTypeGroup; int i_fieldTypeGroup = newValue->toInt() + 1/*counting from 1*/; if (i_fieldTypeGroup < 1 || i_fieldTypeGroup > #ifdef KEXI_NO_BLOB_FIELDS //! @todo remove this later (int)KDbField::LastTypeGroup - 1) //don't show last (BLOB) type #else (int)KDbField::LastTypeGroup) #endif return; fieldTypeGroup = static_cast(i_fieldTypeGroup); //-get 1st type from this group, and update 'type' property KDbField::Type fieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (fieldType == KDbField::InvalidType) fieldType = KDbField::Text; //-get subtypes for this type: keys (slist) and names (nlist) QStringList slist, nlist; getSubTypeListData(fieldTypeGroup, slist, nlist); QString subTypeValue; /* disabled - "mime" is moved from subType to "objectType" custom property if (fieldType==KDbField::BLOB) { // special case: BLOB type uses "mime-based" subtypes subTypeValue = slist.first(); } else {*/ subTypeValue = KDbField::typeString(fieldType); //} KProperty *subTypeProperty = &set["subType"]; qDebug() << subTypeProperty->value(); // *** this action contains subactions *** Command *changeDataTypeCommand = new Command( kundo2_i18n("Change data type for field %1 to %2", set["name"].value().toString(), KDbField::typeName(fieldType)), 0, this); //qDebug() << "++++++++++" << slist << nlist; //update subtype list and value const bool forcePropertySetReload = KDbField::typeGroup( KDbField::typeForString(subTypeProperty->value().toString())) != fieldTypeGroup; //<-- ????? const bool useListData = slist.count() > 1; if (!useListData) { slist.clear(); //empty list will be passed nlist.clear(); } d->setPropertyValueIfNeeded(set, "type", (int)fieldType, changeDataTypeCommand, false /*!forceAddCommand*/, true /*rememberOldValue*/); // notNull and defaultValue=false is reasonable for boolean type if (fieldType == KDbField::Boolean) { //! @todo maybe this is good for other data types as well? d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); d->setPropertyValueIfNeeded(set, "defaultValue", QVariant(false), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); } if (set["primaryKey"].value().toBool() == true) { //primary keys require big int, so if selected type is not integer- remove PK if (fieldTypeGroup != KDbField::IntegerGroup) { /*not needed, line below will do the work d->view->data()->updateRecordEditBuffer(record, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(record); */ //set["primaryKey"] = QVariant(false); d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), changeDataTypeCommand); //! @todo should we display (passive?) dialog informing about cleared pkey? } } d->setPropertyValueIfNeeded(set, "subType", subTypeValue, changeDataTypeCommand, false, false /*!rememberOldValue*/, &slist, &nlist); if (d->updatePropertiesVisibility(fieldType, set, changeDataTypeCommand) || forcePropertySetReload) { //properties' visiblility changed: refresh prop. set propertySetReloaded(true); } addHistoryCommand(changeDataTypeCommand, false /* !execute */); } else if (colnum == COLUMN_ID_DESC) {//'description' KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; //update field desc. QVariant oldValue((*propertySetForRecord)["description"].value()); qDebug() << oldValue; propertySetForRecord->changeProperty("description", *newValue); } } void KexiTableDesignerView::slotRecordUpdated(KDbRecordData *data) { const int record = d->view->data()->indexOf(data); if (record < 0) return; setDirty(); //-check if the record was empty before updating //if yes: we want to add a property set for this new record (field) QString fieldCaption(data->at(COLUMN_ID_CAPTION).toString()); const bool prop_set_allowed = !data->at(COLUMN_ID_TYPE).isNull(); if (!prop_set_allowed && d->sets->at(record)/*propertySet()*/) { //there is a property set, but it's not allowed - remove it: d->sets->eraseAt(record); //clear 'type' column: d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); d->view->data()->saveRecordChanges(data); } else if (prop_set_allowed && !d->sets->at(record)/*propertySet()*/) { //-- create a new field: KDbField::TypeGroup fieldTypeGroup = static_cast( data->at(COLUMN_ID_TYPE).toInt() + 1/*counting from 1*/); int intFieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (intFieldType == 0) return; QString description(data->at(COLUMN_ID_DESC).toString()); //! @todo check uniqueness: QString fieldName(KDb::stringToIdentifier(fieldCaption)); KDbField::Type fieldType = KDb::intToFieldType(intFieldType); int maxLength = 0; if (fieldType == KDbField::Text) { maxLength = KDbField::defaultMaxLength(); } KDbField field( //tmp fieldName, fieldType, KDbField::NoConstraints, KDbField::NoOptions, maxLength, /*precision*/0, /*defaultValue*/QVariant(), fieldCaption, description); // reasonable case for boolean type: set notNull flag and "false" as default value switch (fieldType) { case KDbField::Boolean: field.setNotNull(true); field.setDefaultValue(QVariant(false)); break; case KDbField::Text: field.setMaxLengthStrategy(KDbField::DefaultMaxLength); break; default:; } qDebug() << field; //create a new property set: KPropertySet *newSet = createPropertySet(record, field, true); //refresh property editor: propertySetSwitched(); if (d->addHistoryCommand_in_slotRecordUpdated_enabled) { addHistoryCommand(new InsertFieldCommand(0, this, record, *newSet /*propertySet()*/), //, field /*will be copied*/ false /* !execute */); } } } void KexiTableDesignerView::updateActions() { updateActions(false); } void KexiTableDesignerView::slotPropertyChanged(KPropertySet& set, KProperty& property) { const QByteArray pname(property.name()); qDebug() << pname << " = " << property.value() << " (oldvalue = " << property.oldValue() << ")"; // true if PK should be altered bool changePrimaryKey = false; // true if PK should be set to true, otherwise unset bool setPrimaryKey = false; if (pname == "primaryKey" && d->slotPropertyChanged_primaryKey_enabled) { changePrimaryKey = true; setPrimaryKey = property.value().toBool(); } // update "lookup column" icon if (pname == "rowSource" || pname == "rowSourceType") { //! @todo indicate invalid definitions of lookup columns as well using a special icon //! (e.g. due to missing data source) const int record = d->sets->findRecordForPropertyValue("uid", set["uid"].value().toInt()); KDbRecordData *data = d->view->recordAt(record); if (data) d->updateIconForRecord(data, &set); } //setting autonumber requires setting PK as well Command *setAutonumberCommand = 0; Command *toplevelCommand = 0; if (pname == "autoIncrement" && property.value().toBool() == true) { if (set["primaryKey"].value().toBool() == false) {//we need PKEY here! QString msg = xi18n("Setting autonumber requires primary key to be set for current field."); if (d->primaryKeyExists) msg += xi18n("Previous primary key will be removed."); msg += xi18n("Do you want to create primary key for current field? " "Click Cancel to cancel setting autonumber."); if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, xi18n("Setting Autonumber Field"), KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel())) { changePrimaryKey = true; setPrimaryKey = true; //switchPrimaryKey(set, true); // this will be toplevel command setAutonumberCommand = new Command( kundo2_i18n("Set autonumber for field %1", set["name"].value().toString()), 0, this); toplevelCommand = setAutonumberCommand; d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setAutonumberCommand); } else { setAutonumberCommand = new Command( kundo2_i18n("Remove autonumber from field %1", set["name"].value().toString()), 0, this); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), setAutonumberCommand, true /*forceAddCommand*/, false/*rememberOldValue*/); addHistoryCommand(setAutonumberCommand, false /* !execute */); return; } } } //clear PK when these properties were set to false: if ((pname == "indexed" || pname == "unique" || pname == "notNull") && set["primaryKey"].value().toBool() && property.value().toBool() == false) { //! @todo perhaps show a hint in help panel telling what happens? changePrimaryKey = true; setPrimaryKey = false; // this will be toplevel command Command *unsetIndexedOrUniquOrNotNullCommand = new Command( kundo2_i18n("Set %1 property for field %2", property.caption(), set["name"].value().toString()), 0, this); toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; d->setPropertyValueIfNeeded(set, pname, QVariant(false), unsetIndexedOrUniquOrNotNullCommand); if (pname == "notNull") { d->setPropertyValueIfNeeded(set, "unique", QVariant(false), unsetIndexedOrUniquOrNotNullCommand); } } if (pname == "defaultValue") { KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); set["defaultValue"].setType(static_cast(KDbField::variantType(type))); } if (pname == "subType" && d->slotPropertyChanged_subType_enabled) { d->slotPropertyChanged_subType_enabled = false; if (set["primaryKey"].value().toBool() == true && property.value().toString() != KDbField::typeString(KDbField::BigInteger)) { qDebug() << "INVALID " << property.value().toString(); // if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, // xi18n("This field has primary key assigned. Setting autonumber field"), // KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel() )) } KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QString typeName; /* disabled - "mime" is moved from subType to "objectType" custom property if (type==KDbField::BLOB) { //special case //find i18n'd text QStringList stringsList, namesList; getSubTypeListData(KDbField::BLOBGroup, stringsList, namesList); const int stringIndex = stringsList.findIndex( property.value().toString() ); if (-1 == stringIndex || stringIndex>=(int)namesList.count()) typeName = property.value().toString(); //for sanity else typeName = namesList[stringIndex]; } else {*/ typeName = KDbField::typeName(KDbField::typeForString(property.value().toString())); Command* changeFieldTypeCommand = new Command( kundo2_i18n( "Change type for field %1 to %2", set["name"].value().toString(), typeName), 0, this); d->setPropertyValueIfNeeded(set, "subType", property.value(), property.oldValue(), changeFieldTypeCommand); qDebug() << set["type"].value(); const KDbField::Type newType = KDbField::typeForString(property.value().toString()); set["type"].setValue(newType); // cast "defaultValue" property value to a new type QVariant oldDefVal(set["defaultValue"].value()); QVariant newDefVal(tryCastQVariant(oldDefVal, KDbField::variantType(type))); if (oldDefVal.type() != newDefVal.type()) set["defaultValue"].setType(newDefVal.type()); d->setPropertyValueIfNeeded(set, "defaultValue", newDefVal, newDefVal, changeFieldTypeCommand); d->updatePropertiesVisibility(newType, set); //properties' visiblility changed: refresh prop. set propertySetReloaded(true); d->slotPropertyChanged_subType_enabled = true; addHistoryCommand(changeFieldTypeCommand, false /* !execute */); return; } //! @todo add command text if ( d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) { addHistoryCommand(new ChangeFieldPropertyCommand(0, this, set, property.name(), property.oldValue() /* ??? */, property.value()), false /* !execute */); } if (changePrimaryKey) { d->slotPropertyChanged_primaryKey_enabled = false; if (setPrimaryKey) { //primary key implies some rules //this action contains subactions Command * setPrimaryKeyCommand = new Command( kundo2_i18n("Set primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(true), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "unique", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "allowEmpty", QVariant(false), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "indexed", QVariant(true), setPrimaryKeyCommand); //! \todo: add setting for this: "Integer PKeys have autonumber set by default" d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setPrimaryKeyCommand); /* set["unique"] = QVariant(true); set["notNull"] = QVariant(true); set["allowEmpty"] = QVariant(false); set["indexed"] = QVariant(true); set["autoIncrement"] = QVariant(true);*/ } else { // set PK to false //remember this action containing 2 subactions Command *setPrimaryKeyCommand = new Command( kundo2_i18n("Unset primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), setPrimaryKeyCommand); } switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand); d->updatePropertiesVisibility( KDbField::typeForString(set["subType"].value().toString()), set, toplevelCommand); addHistoryCommand(toplevelCommand, false /* !execute */); //properties' visiblility changed: refresh prop. set propertySetReloaded(true/*preservePrevSelection*/); d->slotPropertyChanged_primaryKey_enabled = true; } } void KexiTableDesignerView::slotRecordInserted() { updateActions(); if (d->addHistoryCommand_in_slotRecordInserted_enabled) { const int record = d->view->currentRecord(); if (record >= 0) { addHistoryCommand(new InsertEmptyRecordCommand(0, this, record), false /* !execute */); } } //! @todo } void KexiTableDesignerView::slotAboutToDeleteRecord( KDbRecordData* data, KDbResultInfo* result, bool repaint) { Q_UNUSED(result) Q_UNUSED(repaint) if ((*data)[COLUMN_ID_ICON].toString() == KexiIconName("database-key")) d->primaryKeyExists = false; if (d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled) { const int record = d->view->data()->indexOf(data); KPropertySet *set = record >= 0 ? d->sets->at(record) : 0; //set can be 0 here, what means "removing empty record" addHistoryCommand( new RemoveFieldCommand(0, this, record, set), false /* !execute */ ); } } KDbField * KexiTableDesignerView::buildField(const KPropertySet &set) const { //create a map of property values const KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QMap values(set.propertyValues()); //qDebug() << values; //remove internal values, to avoid creating custom field's properties KDbField *field = new KDbField(); for (QMutableMapIterator it(values); it.hasNext();) { it.next(); const QByteArray propName(it.key()); if (d->internalPropertyNames.contains(propName) || propName.startsWith("this:") || (/*sanity*/propName == "objectType" && type != KDbField::BLOB) || (propName == "unsigned" && !KDbField::isIntegerType(type)) || (propName == "maxLength" && type != KDbField::Text) || (propName == "precision" && !KDbField::isFPNumericType(type)) || (propName == "scale" && !KDbField::isFPNumericType(type)) ) { it.remove(); } } //assign properties to the field // (note that "objectType" property will be saved as custom property) if (!KDb::setFieldProperties(field, values)) { delete field; return 0; } return field; } tristate KexiTableDesignerView::buildSchema(KDbTableSchema &schema, bool beSilent) { if (!d->view->acceptRecordEditing()) return cancelled; //check for missing captions KPropertySet *b = 0; bool no_fields = true; int i; QSet names; for (i = 0; i < (int)d->sets->size(); i++) { b = d->sets->at(i); if (b) { no_fields = false; const QString name((*b)["name"].value().toString().toLower()); if (name.isEmpty()) { if (beSilent) { qWarning() << QString("no field caption entered at record %1...").arg(i + 1); } else { d->view->setCursorPosition(i, COLUMN_ID_CAPTION); d->view->startEditCurrentCell(); KMessageBox::information(this, xi18n("You should enter field caption.")); } return cancelled; } if (names.contains(name)) { break; } names.insert(name); //remember } } //check for empty design if (no_fields) { if (beSilent) { qWarning() << "no field defined..."; } else { KMessageBox::sorry(this, xi18n("You have added no fields.\nEvery table should have at least one field.")); } return cancelled; } //check for duplicates if (b && i < (int)d->sets->size()) { if (beSilent) { qWarning() << QString("duplicated field name '%1'") .arg((*b)["name"].value().toString()); } else { d->view->setCursorPosition(i, COLUMN_ID_CAPTION); d->view->startEditCurrentCell(); //! @todo for "names hidden" mode we won't get this error because user is unable to change names KMessageBox::sorry(this, xi18nc("@info", "You have added %1 field name twice." "Field names cannot be repeated. Correct name of the field.", (*b)["name"].value().toString())); } return cancelled; } //check for pkey; automatically add a pkey if user wanted if (!d->primaryKeyExists) { if (beSilent) { qDebug() << "no primay key defined..."; } else { const int questionRes = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "Table %1 has no primary key defined." "Although a primary key is not required, it is needed " "for creating relations between database tables. " "Do you want a primary key to be automatically added now?" "If you want to add a primary key by hand, press Cancel " "to cancel saving table design.", schema.name()), 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()), KStandardGuiItem::cancel(), "autogeneratePrimaryKeysOnTableDesignSaving"); if (questionRes == KMessageBox::Cancel) { return cancelled; } else if (questionRes == KMessageBox::Yes) { //-find unique name, starting with, "id", "id2", .... int i = 0; int idIndex = 1; //means "id" QString pkFieldName("id%1"); KLocalizedString pkFieldCaption(kxi18nc("Identifier%1", "Id%1")); while (i < (int)d->sets->size()) { KPropertySet *set = d->sets->at(i); if (set) { if ( (*set)["name"].value().toString() == pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)) || (*set)["caption"].value().toString() == pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ) { //try next id index i = 0; idIndex++; continue; } } i++; } pkFieldName = pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)); //ok, add PK with such unique name d->view->insertEmptyRecord(0); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_CAPTION, pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); if (!d->view->data()->saveRecordChanges(d->view->selectedRecord(), true)) { return cancelled; } slotTogglePrimaryKey(); } } } //we're ready... for (i = 0;i < (int)d->sets->size();++i) {//for every field create Field definition KPropertySet *s = d->sets->at(i); if (!s) continue; KDbField * f = buildField(*s); if (!f) continue; //hmm? if (!schema.addField(f)) { qWarning() << "!schema.addField(f)"; return false; } if ( !(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) { //add lookup column KDbLookupFieldSchema *lookupFieldSchema = new KDbLookupFieldSchema(); KDbLookupFieldSchemaRecordSource recordSource; recordSource.setTypeByName((*s)["rowSourceType"].value().toString()); recordSource.setName((*s)["rowSource"].value().toString()); lookupFieldSchema->setRecordSource(recordSource); lookupFieldSchema->setBoundColumn((*s)["boundColumn"].value().toInt()); //! @todo this is backward-compatible code for "single visible column" implementation //! for multiple columns, only the first is displayed, so there is a data loss is GUI is used //! -- special kproperty editor needed QList visibleColumns; const int visibleColumn = (*s)["visibleColumn"].value().toInt(); if (visibleColumn >= 0) visibleColumns.append(visibleColumn); lookupFieldSchema->setVisibleColumns(visibleColumns); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) { qWarning() << "!schema.setLookupFieldSchema()"; delete lookupFieldSchema; return false; } } } return true; } //! @internal //! A recursive function for copying alter table actions from undo/redo commands. static void copyAlterTableActions(const KUndo2Command* command, KDbAlterTableHandler::ActionList &actions) { for (int i = 0; i < command->childCount(); ++i) { copyAlterTableActions(command->child(i), actions); } const Command* cmd = dynamic_cast(command); if (!cmd) { qWarning() << "cmd is not of type 'Command'!"; return; } KDbAlterTableHandler::ActionBase* action = cmd->createAction(); //some commands can contain null actions, e.g. "set visibility" command if (action) actions.append(action); } tristate KexiTableDesignerView::buildAlterTableActions( KDbAlterTableHandler::ActionList &actions) { actions.clear(); qDebug() << d->history->count() << " top-level command(s) to process..."; for (int i = 0; i < d->history->count(); ++i) { copyAlterTableActions(d->history->command(i), actions); } return true; } KDbObject* KexiTableDesignerView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); if (tempData()->table() || window()->schemaObject()) //must not be return 0; //create table schema definition tempData()->setTable(new KDbTableSchema(object.name())); tempData()->table()->setName(object.name()); tempData()->table()->setCaption(object.caption()); tempData()->table()->setDescription(object.description()); tristate res = buildSchema(*tempData()->table()); *cancel = ~res; //FINALLY: create table: if (res == true) { //! @todo KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); res = conn->createTable(tempData()->table(), options & KexiView::OverwriteExistingData); if (res == true) { res = KexiMainWindowIface::global()->project()->removeUserDataBlock(tempData()->table()->id()); } else { window()->setStatus(conn, ""); } } if (res == true) { //we've current schema tempData()->tableSchemaChangedInPreviousView = true; d->history->clear(); } else { KDbTableSchema *tableToDelete = tempData()->table(); tempData()->setTable(nullptr); delete tableToDelete; } return tempData()->table(); } KDbObject* KexiTableDesignerView::copyData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); Q_UNUSED(options); Q_UNUSED(cancel); if (!tempData()->table()) { qWarning() << "Cannot copy data without source table (tempData()->table)"; return 0; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *copiedTable = conn->copyTable(*tempData()->table(), object); if (!copiedTable) { return 0; } if (!KexiMainWindowIface::global()->project()->copyUserDataBlock(tempData()->table()->id(), copiedTable->id())) { conn->dropTable(copiedTable); delete copiedTable; return 0; } return copiedTable; } tristate KexiTableDesignerView::storeData(bool dontAsk) { if (!tempData()->table() || !window()->schemaObject()) { d->recentResultOfStoreData = false; return false; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = 0; KDbTableSchema *newTable = 0; //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); //!< @todo this is temporary flag before we switch entirely to real alter table bool realAlterTableCanBeUsed = false; if (res == true) { alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); if (!d->tempStoreDataUsingRealAlterTable) { //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { realAlterTableCanBeUsed = true; } } } if (res == true) { res = KexiTablePart::askForClosingObjectsUsingTableSchema( window(), conn, tempData()->table(), xi18nc("@info", "You are about to change the design of table %1 " "but following objects using this table are opened:", tempData()->table()->name())); } if (res == true) { if (!d->tempStoreDataUsingRealAlterTable && !realAlterTableCanBeUsed) { //! @todo temp; remove this case: delete alterTableHandler; alterTableHandler = 0; // - inform about removing the current table and ask for confirmation if (!d->dontAskOnStoreData && !dontAsk) { bool emptyTable; const QString msg = d->messageForSavingChanges(&emptyTable).toString(); if (!emptyTable) { if (KMessageBox::No == KMessageBox::questionYesNo(this, msg)) res = cancelled; } } d->dontAskOnStoreData = false; //one-time use if (~res) { d->recentResultOfStoreData = res; return res; } // keep old behaviour: newTable = new KDbTableSchema(); // copy the object data static_cast(*newTable) = static_cast(*tempData()->table()); res = buildSchema(*newTable); qDebug() << "BUILD SCHEMA:" << *newTable; res = conn->alterTable(tempData()->table(), newTable); if (res != true) window()->setStatus(conn, ""); } else { KDbAlterTableHandler::ExecutionArguments args; newTable = alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; qDebug() << "ALTER TABLE EXECUTE: " << res.toString(); if (true != res) { qDebug() << alterTableHandler->result(); window()->setStatus(alterTableHandler, ""); } } } if (res == true) { //change current schema tempData()->setTable(newTable); tempData()->tableSchemaChangedInPreviousView = true; d->history->clear(); } else { delete newTable; } delete alterTableHandler; d->recentResultOfStoreData = res; return res; } tristate KexiTableDesignerView::simulateAlterTableExecution(QString *debugTarget) { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI if (KexiMainWindowIface::global()->currentWindow() != window()) { //to avoid executing for multiple alter table views return false; } if (!tempData()->table() || !window()->schemaObject()) return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler::ActionList actions; /*tristate res =*/ buildAlterTableActions(actions); //! @todo result? KDbAlterTableHandler alterTableHandler(conn); alterTableHandler.setActions(actions); KDbAlterTableHandler::ExecutionArguments args; if (debugTarget) { args.debugString = debugTarget; } else { args.simulate = true; } (void)alterTableHandler.execute(tempData()->table()->name(), &args); return args.result; # else Q_UNUSED(debugTarget); return false; # endif #else Q_UNUSED(debugTarget); return false; #endif } void KexiTableDesignerView::slotSimulateAlterTableExecution() { (void)simulateAlterTableExecution(0); } tristate KexiTableDesignerView::executeRealAlterTable() { d->tempStoreDataUsingRealAlterTable = true; d->recentResultOfStoreData = false; // will call KexiMainWindow::slotProjectSaveAs() and thus storeData(): QMetaObject::invokeMethod( KexiMainWindowIface::global()->thisWidget(), "slotProjectSave"); d->tempStoreDataUsingRealAlterTable = false; return d->recentResultOfStoreData; } KexiTablePartTempData* KexiTableDesignerView::tempData() const { return static_cast(window()->data()); } #ifdef KEXI_DEBUG_GUI void KexiTableDesignerView::debugCommand(const KUndo2Command* command, int nestingLevel) { const Command* kexiCommand = dynamic_cast(command); if (kexiCommand) { KDb::alterTableActionDebugGUI(kexiCommand->debugString(), nestingLevel); } else { KDb::alterTableActionDebugGUI(command->text().toString(), nestingLevel); } //show subcommands for (int i = 0; i < command->childCount(); ++i) { debugCommand(command->child(i), nestingLevel + 1); } } #endif void KexiTableDesignerView::addHistoryCommand(KexiTableDesignerCommands::Command* command, bool execute) { #ifdef KEXI_NO_UNDOREDO_ALTERTABLE Q_UNUSED(command); Q_UNUSED(execute); #else # ifdef KEXI_DEBUG_GUI debugCommand(command, 0); # endif if (!execute) { command->blockRedoOnce(); } d->history->push(command); updateUndoRedoActions(); #endif } void KexiTableDesignerView::updateUndoRedoActions() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE setAvailable("edit_undo", d->historyActionCollection->action("edit_undo")->isEnabled()); setAvailable("edit_redo", d->historyActionCollection->action("edit_redo")->isEnabled()); #endif } void KexiTableDesignerView::slotUndo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("UNDO:")); # endif d->history->undo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotRedo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("REDO:")); # endif d->history->redo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotAboutToShowContextMenu() { //update title QString title; if (propertySet()) { const KPropertySet &set = *propertySet(); QString captionOrName(set["caption"].value().toString()); if (captionOrName.isEmpty()) captionOrName = set["name"].value().toString(); title = xi18nc("@info", "Table field %1", captionOrName); } else { title = xi18nc("Empty table row", "Empty Row"); } //! \todo replace lineedit with table_field icon d->view->setContextMenuTitle(KexiIcon("lineedit"), title); } QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result) { KDbTableSchema tempTable; //copy object data static_cast(tempTable) = static_cast(*tempData()->table()); result = buildSchema(tempTable, true /*beSilent*/); if (true != result) { return QString(); } return KDbUtils::debugString(tempTable); } // -- low-level actions used by undo/redo framework void KexiTableDesignerView::clearRecord(int record, bool addCommand) { if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; //clear from prop. set d->sets->eraseAt(record); //clear record in table view (just clear value in COLUMN_ID_TYPE column) if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data, true); } void KexiTableDesignerView::insertField(int record, const QString& caption, bool addCommand) { insertFieldInternal(record, 0, caption, addCommand); } void KexiTableDesignerView::insertField(int record, KPropertySet& set, bool addCommand) { insertFieldInternal(record, &set, QString(), addCommand); } void KexiTableDesignerView::insertFieldInternal(int record, KPropertySet* set, //const KDbField& field, const QString& caption, bool addCommand) { if (set && (!set->contains("type") || !set->contains("caption"))) { qWarning() << "no 'type' or 'caption' property in set!"; return; } if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, set ? (*set)["caption"].value() : QVariant(caption)); KDbField::TypeGroup tg; if (set) { tg = KDbField::typeGroup(KDb::intToFieldType((*set)["type"].value().toInt())); } else { tg = KDbField::TextGroup; // default type } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(tg) - 1); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, set ? (*set)["description"].value() : QVariant()); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } //this will create a new property set: d->view->data()->saveRecordChanges(data); if (set) { KPropertySet *newSet = d->sets->at(record); if (newSet) { *newSet = *set; //deep copy } else { qWarning() << "!newSet, record==" << record; } } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; } d->view->updateRecord(record); propertySetReloaded(true); } void KexiTableDesignerView::insertEmptyRecord(int record, bool addCommand) { if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = false; } d->view->insertEmptyRecord(record); if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = true; } } void KexiTableDesignerView::deleteRecord(int record, bool addCommand) { KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = false; } const bool res = d->view->deleteItem(data); if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = true; } if (!res) return; } void KexiTableDesignerView::changeFieldPropertyForRecord(int record, const QByteArray& propertyName, const QVariant& newValue, KPropertyListData* const listData, bool addCommand) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changeFieldProperty: \"") + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (listData) { if (listData->keys.isEmpty()) property.setListData(0); else property.setListData(new KPropertyListData(*listData)); } if (propertyName != "type") //delayed type update (we need to have subtype set properly) property.setValue(newValue); KDbRecordData *data = d->view->recordAt(record); Q_ASSERT(data); if (propertyName == "type") { d->slotPropertyChanged_subType_enabled = false; d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(KDbField::typeGroup(KDb::intToFieldType(newValue.toInt()))) - 1); d->view->data()->saveRecordChanges(data); d->addHistoryCommand_in_slotRecordUpdated_enabled = true; property.setValue(newValue); //delayed type update (we needed to have subtype set properly) } if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotPropertyChanged_subType_enabled = false; } //special cases: properties displayed within the data grid: if (propertyName == "caption") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, newValue); d->view->data()->saveRecordChanges(data); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } } else if (propertyName == "description") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, newValue); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data); } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->slotPropertyChanged_subType_enabled = true; } d->view->updateRecord(record); } void KexiTableDesignerView::changeFieldProperty(int fieldUID, const QByteArray& propertyName, const QVariant& newValue, KPropertyListData* const listData, bool addCommand) { //find a property by UID const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) { qWarning() << "field with uid=" << fieldUID << " not found!"; return; } changeFieldPropertyForRecord(record, propertyName, newValue, listData, addCommand); } void KexiTableDesignerView::changePropertyVisibility( int fieldUID, const QByteArray& propertyName, bool visible) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changePropertyVisibility: \"") + QString(propertyName) + "\" to \"" + (visible ? "true" : "false") + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; //find a property by name const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (property.isVisible() != visible) { property.setVisible(visible); propertySetReloaded(true); } } void KexiTableDesignerView::propertySetSwitched() { KexiDataTableView::propertySetSwitched(); KexiLookupColumnPage *page = qobject_cast(window()->part())->lookupColumnPage(); if (page) page->assignPropertySet(propertySet()); } bool KexiTableDesignerView::isPhysicalAlteringNeeded() { //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); if (res != true) return true; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; delete alterTableHandler; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { return false; } return true; } diff --git a/src/plugins/tables/kexitablepart.cpp b/src/plugins/tables/kexitablepart.cpp index aebd5397e..b531183d8 100644 --- a/src/plugins/tables/kexitablepart.cpp +++ b/src/plugins/tables/kexitablepart.cpp @@ -1,329 +1,333 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2002, 2003 Joseph Wenninger Copyright (C) 2004-2017 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 "kexitablepart.h" #include #include #include #include +#include #include #include #include "kexitabledesignerview.h" #include "kexitabledesigner_dataview.h" #include "kexilookupcolumnpage.h" #include #include #include #include #include KEXI_PLUGIN_FACTORY(KexiTablePart, "kexi_tableplugin.json") //! @internal class Q_DECL_HIDDEN KexiTablePart::Private { public: Private() { } ~Private() { delete static_cast(lookupColumnPage); } QPointer lookupColumnPage; }; KexiTablePart::KexiTablePart(QObject *parent, const QVariantList& l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "table"), xi18nc("tooltip", "Create new table"), xi18nc("what's this", "Creates new table."), l) , d(new Private) { + // needed for custom "identifier" property editor widget + KexiCustomPropertyFactory::init(); + //! @todo js: also add Kexi::TextViewMode when we'll have SQL ALTER TABLE EDITOR!!! } KexiTablePart::~KexiTablePart() { delete d; } void KexiTablePart::initPartActions() { } void KexiTablePart::initInstanceActions() { } KexiWindowData* KexiTablePart::createWindowData(KexiWindow* window) { KexiMainWindowIface *win = KexiMainWindowIface::global(); return new KexiTablePartTempData(window, win->project()->dbConnection()); } KexiView* KexiTablePart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); KexiMainWindowIface *win = KexiMainWindowIface::global(); if (!win || !win->project() || !win->project()->dbConnection()) return 0; KexiTablePartTempData *temp = static_cast(window->data()); if (!temp->table()) { temp->setTable(win->project()->dbConnection()->tableSchema(item->name())); qDebug() << "schema is " << temp->table(); } if (viewMode == Kexi::DesignViewMode) { KexiTableDesignerView *t = new KexiTableDesignerView(parent); return t; } else if (viewMode == Kexi::DataViewMode) { if (!temp->table()) { return 0; //!< @todo message } //we're not setting table schema here -it will be forced to set // in KexiTableDesigner_DataView::afterSwitchFrom() KexiTableDesigner_DataView *t = new KexiTableDesigner_DataView(parent); return t; } return 0; } tristate KexiTablePart::remove(KexiPart::Item *item) { KexiProject *project = KexiMainWindowIface::global()->project(); if (!project || !project->dbConnection()) return false; KDbConnection *conn = project->dbConnection(); KDbTableSchema *sch = conn->tableSchema(item->identifier()); if (sch) { const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch, xi18n("You are about to remove table %1 but following objects using this table are opened:", sch->name())); if (res != true) { return res; } return conn->dropTable(sch); } //last chance: just remove item return conn->removeObject(item->identifier()); } tristate KexiTablePart::rename(KexiPart::Item *item, const QString& newName) { Q_ASSERT(item); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *schema = conn->tableSchema(item->identifier()); if (!schema) return false; const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, schema, xi18n("You are about to rename table %1 but following objects using this table are opened:", schema->name())); if (res != true) { return res; } return conn->alterTableName(schema, newName); } KDbObject* KexiTablePart::loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { Q_UNUSED(window); Q_UNUSED(viewMode); if (ownedByWindow) *ownedByWindow = false; return KexiMainWindowIface::global()->project()->dbConnection()->tableSchema(object.name()); } //static tristate KexiTablePart::askForClosingObjectsUsingTableSchema( KexiWindow *window, KDbConnection *conn, KDbTableSchema *table, const QString& msg) { Q_ASSERT(conn); Q_ASSERT(table); if (!window) { return true; } QList listeners = KDbTableSchemaChangeListener::listeners(conn, table); KexiTablePartTempData *temp = static_cast(window->data()); // Special case: listener that is equal to window->data() will be silently closed // without asking for confirmation. It is not counted when looking for objects that // are "blocking" changes of the table. const bool tempListenerExists = listeners.removeAll(temp) > 0; // Immediate success if there's no temp-data's listener to close nor other listeners to close if (!tempListenerExists && listeners.isEmpty()) { return true; } if (!listeners.isEmpty()) { QString openedObjectsStr = ""; for(const KDbTableSchemaChangeListener* listener : listeners) { openedObjectsStr += QString("%1").arg(listener->name()); } openedObjectsStr += ""; const int r = KMessageBox::questionYesNo(window, i18nc("@info", "%1%2", msg, openedObjectsStr) + "" + xi18n("Do you want to close all windows for these objects?") + "", QString(), KGuiItem(xi18nc("@action:button Close All Windows", "Close Windows"), koIconName("window-close")), KStandardGuiItem::cancel()); if (r != KMessageBox::Yes) { return cancelled; } } //try to close every window depending on the table (if present) and also the temp-data's listener (if present) const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, table); if (res != true) { //do not expose closing errors twice; just cancel return cancelled; } return true; } KLocalizedString KexiTablePart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of table %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Table %1 already exists.")); if (window->currentViewMode() == Kexi::DesignViewMode && !window->neverSaved() && englishMessage == ":additional message before saving design") return kxi18nc(I18NC_NOOP("@info", "Any data in this table will be removed upon design's saving!")); return Part::i18nMessage(englishMessage, window); } void KexiTablePart::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->lookupColumnPage) { d->lookupColumnPage = new KexiLookupColumnPage(0); connect(d->lookupColumnPage, SIGNAL(jumpToObjectRequested(QString,QString)), KexiMainWindowIface::global()->thisWidget(), SLOT(highlightObject(QString,QString))); //! @todo add "Table" tab /* connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(QCString,QCString)), KFormDesigner::FormManager::self(), SLOT(setFormDataSource(QCString,QCString))); connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(QString,QString,KDbField::Type)), KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(QString,QString,KDbField::Type))); connect(d->dataSourcePage, SIGNAL(insertAutoFields(QString,QString,QStringList)), KFormDesigner::FormManager::self(), SLOT(insertAutoFields(QString,QString,QStringList)));*/ } KexiProject *prj = KexiMainWindowIface::global()->project(); d->lookupColumnPage->setProject(prj); //! @todo add lookup field icon tab->addTab(d->lookupColumnPage, KexiIcon("combobox"), QString()); tab->setTabToolTip(tab->indexOf(d->lookupColumnPage), xi18n("Lookup column")); } KexiLookupColumnPage* KexiTablePart::lookupColumnPage() const { return d->lookupColumnPage; } //---------------- class Q_DECL_HIDDEN KexiTablePartTempData::Private { public: Private() : table(nullptr) { } KDbTableSchema *table; KDbConnection *conn; }; KexiTablePartTempData::KexiTablePartTempData(QObject* parent, KDbConnection *conn) : KexiWindowData(parent) , KDbTableSchemaChangeListener() , tableSchemaChangedInPreviousView(true /*to force reloading on startup*/) , d(new Private) { d->conn = conn; } KexiTablePartTempData::~KexiTablePartTempData() { KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); delete d; } KDbTableSchema* KexiTablePartTempData::table() { return d->table; } KDbConnection* KexiTablePartTempData::connection() { return d->conn; } void KexiTablePartTempData::setTable(KDbTableSchema *table) { if (d->table == table) { return; } if (d->table) { KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this, d->table); } d->table = table; if (d->table) { KDbTableSchemaChangeListener::registerForChanges(d->conn, this, d->table); } } tristate KexiTablePartTempData::closeListener() { KexiWindow* window = static_cast(parent()); if (window->currentViewMode() != Kexi::DataViewMode) { KexiTableDesigner_DataView *dataView = qobject_cast(window->viewForMode(Kexi::DataViewMode)); if (dataView && dataView->tableView()->data()) { dataView->setData(nullptr); } } return true; } #include "kexitablepart.moc" diff --git a/src/widget/properties/KexiCustomPropertyFactory.cpp b/src/widget/properties/KexiCustomPropertyFactory.cpp index cc8d6df52..c7f11992a 100644 --- a/src/widget/properties/KexiCustomPropertyFactory.cpp +++ b/src/widget/properties/KexiCustomPropertyFactory.cpp @@ -1,115 +1,166 @@ /* This file is part of the KDE project Copyright (C) 2005-2008 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 "KexiCustomPropertyFactory.h" #include "KexiCustomPropertyFactory_p.h" +#include +#include #include #include //! @todo KEXI3 #include #include #if 0 //! @internal class PixmapIdCustomProperty : public KCustomProperty { public: explicit PixmapIdCustomProperty(Property *parent) : KCustomProperty(parent) { } virtual ~PixmapIdCustomProperty() {} virtual void setValue(const QVariant &value, bool rememberOldValue) { Q_UNUSED(value); Q_UNUSED(rememberOldValue); } virtual QVariant value() const { return m_property->value(); } virtual bool handleValue() const { return false; } }; //! @internal class IdentifierCustomProperty : public KCustomProperty { public: explicit IdentifierCustomProperty(Property *parent) : KCustomProperty(parent) { } virtual ~IdentifierCustomProperty() {} virtual void setValue(const QVariant &value, bool rememberOldValue) { Q_UNUSED(rememberOldValue); if (!value.toString().isEmpty()) m_value = KDb::stringToIdentifier(value.toString()).toLower(); } virtual QVariant value() const { return m_value; } virtual bool handleValue() const { return true; } QString m_value; }; #endif //! @todo #if 0 //TODO class KexiImagePropertyEditorDelegate : public KPropertyEditorCreatorInterface, public KPropertyValuePainterInterface { public: KexiImagePropertyEditorDelegate() {} virtual QWidget * createEditor( int type, QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const; virtual void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const; }; #endif -class KexiIdentifierPropertyEditorDelegate : public KPropertyEditorCreatorInterface +class KexiIdentifierPropertyEditorDelegate : public KPropertyStringDelegate { public: KexiIdentifierPropertyEditorDelegate() {} virtual QWidget * createEditor( int type, QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const { Q_UNUSED(type); Q_UNUSED(option); Q_UNUSED(index); return new KexiIdentifierPropertyEditor(parent); } }; +class KexiImagePropertyEditorDelegate : public KPropertyPixmapDelegate +{ +public: + KexiImagePropertyEditorDelegate() + { + } + + virtual QWidget *createEditor(int type, QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + Q_UNUSED(type); + Q_UNUSED(option); + KProperty *property = KPropertyUtils::propertyForIndex(index); + if (!property) { + return nullptr; + } + return new KexiImagePropertyEditor(property, parent); + } +}; + +//--------------- + +KexiImagePropertyEditor::KexiImagePropertyEditor(KProperty *prop, QWidget *parent) + : KPropertyPixmapEditor(prop, parent) +{ +} + +KexiImagePropertyEditor::~KexiImagePropertyEditor() +{ +} + +void KexiImagePropertyEditor::selectPixmap() +{ + const QUrl url = KexiUtils::getOpenImageUrl(parentWidget()); + if (!url.isLocalFile()) { + //! @todo err msg + return; + } + QPixmap pixmap; + if (!pixmap.load(url.toLocalFile())) { + //! @todo err msg + return; + } + setValue(pixmap); +} + //--------------- KexiCustomPropertyFactory::KexiCustomPropertyFactory() : KPropertyWidgetsFactory() { -//! @todo addEditor( KexiCustomPropertyFactory::PixmapId, new KexiImagePropertyEditorDelegate ); + addEditor(KexiCustomPropertyFactory::PixmapId, new KexiImagePropertyEditorDelegate); addEditor( KexiCustomPropertyFactory::Identifier, new KexiIdentifierPropertyEditorDelegate ); } void KexiCustomPropertyFactory::init() { - if (KPropertyWidgetsPluginManager::self()->isEditorForTypeAvailable(KexiCustomPropertyFactory::PixmapId)) + if (KPropertyWidgetsPluginManager::self()->isEditorForTypeAvailable( + KexiCustomPropertyFactory::Identifier)) + { return; //already registered + } KPropertyWidgetsPluginManager::self()->registerFactory( new KexiCustomPropertyFactory ); } diff --git a/src/widget/properties/KexiCustomPropertyFactory.h b/src/widget/properties/KexiCustomPropertyFactory.h index 1f431336c..4f4ef4d1c 100644 --- a/src/widget/properties/KexiCustomPropertyFactory.h +++ b/src/widget/properties/KexiCustomPropertyFactory.h @@ -1,42 +1,42 @@ /* This file is part of the KDE project Copyright (C) 2005-2008 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 KEXICUSTOMPROPFACTORY_H #define KEXICUSTOMPROPFACTORY_H #include "kexiextwidgets_export.h" #include //! Kexi-specific custom property factory for the KProperty library class KEXIEXTWIDGETS_EXPORT KexiCustomPropertyFactory : public KPropertyWidgetsFactory { public: enum PropertyType { - PixmapId = KProperty::UserDefined + 0, //!< Shared Kexi pixmap - Identifier = KProperty::UserDefined + 1 //!< string allowing nonempty identifiers + PixmapId = KProperty::Pixmap, //!< Override + Identifier = KProperty::UserDefined + 0 //!< string allowing nonempty identifiers }; //! Called once to register all property and editor types provided by this factory. static void init(); KexiCustomPropertyFactory(); }; #endif diff --git a/src/widget/properties/KexiCustomPropertyFactory_p.h b/src/widget/properties/KexiCustomPropertyFactory_p.h index aa9f18997..4a18f05f1 100644 --- a/src/widget/properties/KexiCustomPropertyFactory_p.h +++ b/src/widget/properties/KexiCustomPropertyFactory_p.h @@ -1,73 +1,88 @@ /* This file is part of the KDE project Copyright (C) 2005-2008 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 KEXICUSTOMPROPFACTORY_P_H #define KEXICUSTOMPROPFACTORY_P_H //! @todo #include #include +#include #if 0 // todo //! Kexi-specific image editor for property editor's item class KexiImagePropertyEdit : public KPropertyPixmapEditor { Q_OBJECT public: explicit KexiImagePropertyEdit(KProperty *property, QWidget *parent = 0); virtual ~KexiImagePropertyEdit(); virtual QVariant value() const; virtual void setValue(const QVariant &value, bool emitChange = true); virtual void drawViewer(QPainter *p, cg, const QRect &r, const QVariant &value); public Q_SLOTS: virtual void selectPixmap(); protected: KexiBLOBBuffer::Id_t m_id; }; #endif /*! Identifier editor based on ordinary string editor but always keeps a valid identifier or empty value. It's line edit has IdentifierValidator::IdentifierValidator set, so user is unable to enter invalid characters. Any chages to a null value or empty string, have no effect. @todo move this to the KPropertyWidgets library (when KexiUtils moves to KProperty) */ class KexiIdentifierPropertyEditor : public KPropertyStringEditor { Q_OBJECT Q_PROPERTY(QString value READ value WRITE setValue USER true) public: explicit KexiIdentifierPropertyEditor(QWidget *parent = 0); virtual ~KexiIdentifierPropertyEditor(); public Q_SLOTS: /*! Reimplemented: sets \a value but it is converted to identifier using KDb::stringToIdentifier(). If \a value is empty string, this method has no effect. */ virtual void setValue(const QString &value); }; + +class KexiImagePropertyEditor: public KPropertyPixmapEditor +{ + Q_OBJECT + +public: + explicit KexiImagePropertyEditor(KProperty *property, QWidget *parent = nullptr); + virtual ~KexiImagePropertyEditor(); + +public Q_SLOTS: + void selectPixmap() override; +}; + + #endif