diff --git a/src/formeditor/widgetlibrary.cpp b/src/formeditor/widgetlibrary.cpp index de0c41a8d..816787dff 100644 --- a/src/formeditor/widgetlibrary.cpp +++ b/src/formeditor/widgetlibrary.cpp @@ -1,784 +1,785 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004-2015 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 "widgetlibrary.h" #include #include "WidgetInfo.h" #include "widgetfactory.h" #include "libactionwidget.h" #include "container.h" #include "form.h" #include "formIO.h" #include "FormWidgetInterface.h" #include "objecttree.h" #include "KexiJsonTrader.h" #include "KexiFormWidgetsPluginMetaData.h" #include "KexiVersion.h" #include #define KEXI_SKIP_SETUPBREEZEICONTHEME #define KEXI_SKIP_REGISTERRESOURCE #include
#include #include #include #include #include namespace KFormDesigner { Q_GLOBAL_STATIC_WITH_ARGS(KexiJsonTrader, KexiFormWidgetsPluginTrader_instance, (KEXI_BASE_PATH "/forms/widgets")) //! @internal class Q_DECL_HIDDEN WidgetLibrary::Private { public: Private(WidgetLibrary *library, const QStringList& supportedFactoryGroups) : showAdvancedProperties(true) , q(library) , m_couldNotFindAnyFormWidgetPluginsErrorDisplayed(false) , m_supportedFactoryGroups(supportedFactoryGroups.toSet()) , m_lookupDone(false) + , m_lookupResult(false) , m_loadFactoriesDone(false) { q->setMessageHandler(&messageHandler); m_advancedProperties.insert("acceptDrops"); m_advancedProperties.insert("accessibleDescription"); m_advancedProperties.insert("accessibleName"); m_advancedProperties.insert("autoMask"); m_advancedProperties.insert("backgroundOrigin"); m_advancedProperties.insert("backgroundMode");//this is rather useless m_advancedProperties.insert("baseSize"); m_advancedProperties.insert("contextMenuEnabled"); m_advancedProperties.insert("contextMenuPolicy"); m_advancedProperties.insert("cursorPosition"); m_advancedProperties.insert("cursorMoveStyle"); m_advancedProperties.insert("dragEnabled"); m_advancedProperties.insert("enableSqueezedText"); m_advancedProperties.insert("layout");// too large risk to break things m_advancedProperties.insert("layoutDirection"); m_advancedProperties.insert("locale"); m_advancedProperties.insert("mouseTracking"); /*! @todo: reenable */ m_advancedProperties.insert("palette"); m_advancedProperties.insert("sizeAdjustPolicy"); //QAbstractScrollArea m_advancedProperties.insert("sizeIncrement"); m_advancedProperties.insert("sizePolicy"); m_advancedProperties.insert("statusTip"); m_advancedProperties.insert("toolTipDuration"); m_advancedProperties.insert("trapEnterKeyEvent"); m_advancedProperties.insert("windowModality"); m_advancedProperties.insert("autoExclusive"); // by providing this in propeditor m_advancedProperties.insert("minimumSize"); m_advancedProperties.insert("maximumSize"); m_advancedProperties.insert("clickMessage"); // for backward compatibility Kexi projects created with Qt < 4.7 m_advancedProperties.insert("showClearButton"); // for backward compatibility Kexi projects created with Qt 4 #ifndef KEXI_SHOW_UNFINISHED /*! @todo reenable */ m_advancedProperties.insert("accel"); m_advancedProperties.insert("icon"); m_advancedProperties.insert("paletteBackgroundPixmap"); m_advancedProperties.insert("pixmap"); m_advancedProperties.insert("shortcut"); // renamed from "accel" in Qt 4 m_advancedProperties.insert("windowIcon"); // renamed from "icon" in Qt 4 #endif } ~Private() { qDeleteAll(m_factories); m_factories.clear(); qDeleteAll(m_pluginsMetaData); m_pluginsMetaData.clear(); } QHash widgets() { KDbMessageGuard mg(q); (void)loadFactories(); return m_widgets; } QHash factories() { KDbMessageGuard mg(q); (void)loadFactories(); return m_factories; } bool isAdvancedProperty(const QByteArray &property) const { return m_advancedProperties.contains(property); } bool showAdvancedProperties; private: //! Performs a form widget plugins lookup. @return true on success. //! @todo This method generates a few warnings, maybe we want to optionally display them somewhere (via the message handler)? bool lookup() { //! @todo Allow refreshing if (m_lookupDone) { return m_lookupResult; } m_lookupDone = true; m_lookupResult = false; q->clearResult(); QStringList serviceTypes; serviceTypes << "Kexi/FormWidget"; QList offers = KexiFormWidgetsPluginTrader_instance->query(serviceTypes); foreach(const QPluginLoader *loader, offers) { QScopedPointer metaData(new KexiFormWidgetsPluginMetaData(*loader)); if (metaData->id().isEmpty()) { qWarning() << "No plugin ID specified for Kexi Form Widgets plugin" << metaData->fileName() << "-- skipping!"; continue; } // check version const QString expectedVersion = KFormDesigner::version(); if (metaData->version() != expectedVersion) { qWarning() << "Kexi Form Widgets plugin" << metaData->id() << "has version" << metaData->majorVersion() << "but required version is" << KFormDesigner::version() << "-- skipping!"; continue; } // skip duplicates if (m_pluginsMetaData.contains(metaData->id())) { qWarning() << "More than one Kexi Form Widgets plugin with ID" << metaData->id() << metaData->fileName() << "-- skipping this one"; continue; } //qDebug() << "found factory:" << ptr->name(); if (!metaData->group().isEmpty() && !m_supportedFactoryGroups.contains(metaData->group())) { qDebug() << "Factory group" << metaData->group() << "for Form Widgets plugin" << metaData->id() << metaData->fileName() << "is not supported -- skipping!"; continue; } if (!setupPrivateIconsResourceWithMessage( QLatin1String(KEXI_BASE_PATH), QString::fromLatin1("icons/%1_%2.rcc") .arg(metaData->id()).arg(supportedIconTheme), QtWarningMsg, QString::fromLatin1(":/icons/%1").arg(metaData->id()))) { continue; } m_pluginsMetaData.insert(metaData->id(), metaData.data()); metaData.take(); } qDeleteAll(offers); offers.clear(); if (m_pluginsMetaData.isEmpty()) { q->m_result = KDbResult(i18n("Could not find any form widget plugins.")); m_couldNotFindAnyFormWidgetPluginsErrorDisplayed = true; return false; } m_lookupResult = true; return true; } //! Loads all factory plugins bool loadFactories() { if (m_loadFactoriesDone) { if (m_couldNotFindAnyFormWidgetPluginsErrorDisplayed) { q->clearResult(); // show the warning only once } return m_loadFactoriesResult; } m_loadFactoriesDone = true; m_loadFactoriesResult = false; if (!lookup()) { return false; } foreach (KexiFormWidgetsPluginMetaData *pluginMetaData, m_pluginsMetaData) { WidgetFactory *factory = loadFactory(pluginMetaData); if (!factory) { continue; } //collect information about classes to be hidden if (factory->hiddenClasses()) { foreach (const QByteArray &c, *factory->hiddenClasses()) { m_hiddenClasses.insert(c); } } } //now we have factories instantiated: load widgets QList loadLater; foreach (WidgetFactory *factory, m_factories) { //ONE LEVEL, FLAT INHERITANCE, but works! //if this factory inherits from something, load its witgets later //! @todo improve if (factory->inheritsFactories()) loadLater.append(factory); else loadFactoryWidgets(factory); } //load now the rest foreach (WidgetFactory* f, loadLater) { loadFactoryWidgets(f); } m_loadFactoriesResult = true; return true; } //! Loads of a single factory. @return true on success WidgetFactory *loadFactory(KexiFormWidgetsPluginMetaData *pluginMetaData) { KPluginFactory *factory = qobject_cast(pluginMetaData->instantiate()); if (!factory) { q->m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT, xi18nc("@info", "Could not load Kexi Form Widgets plugin file %1.", pluginMetaData->fileName())); q->setErrorMessage(pluginMetaData, q->result().message()); qWarning() << q->result().message(); return 0; } WidgetFactory *widgetFactory = factory->create(q); if (!widgetFactory) { q->m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT, xi18nc("@info", "Could not open Kexi Form Widgets plugin %1.", pluginMetaData->fileName())); qWarning() << q->m_result.message(); return 0; } widgetFactory->setLibrary(q); widgetFactory->setObjectName(pluginMetaData->id()); widgetFactory->setAdvancedPropertiesVisible(showAdvancedProperties); //inherit this flag from the library m_factories.insert(pluginMetaData->id().toLatin1(), widgetFactory); return widgetFactory; } //! Loads widgets for factory @a f void loadFactoryWidgets(WidgetFactory *f) { QHash widgetsForFactory(f->classes()); foreach (WidgetInfo *w, widgetsForFactory) { if (m_hiddenClasses.contains( w->className() )) continue; //this class is hidden // check if we want to inherit a widget from a different factory if (!w->parentFactoryName().isEmpty() && !w->inheritedClassName().isEmpty()) { WidgetFactory *parentFactory = m_factories.value(w->parentFactoryName().toLower()); if (!parentFactory) { qWarning() << "class" << w->className() << ": no such parent factory" << w->parentFactoryName(); continue; } WidgetInfo* inheritedClass = parentFactory->widgetInfoForClassName(w->inheritedClassName()); if (!inheritedClass) { qWarning() << "class" << w->inheritedClassName() << " - no such class to inherit in factory" << w->parentFactoryName(); continue; } //ok: inherit properties: w->setInheritedClass( inheritedClass ); if (w->iconName().isEmpty()) w->setIconName(inheritedClass->iconName()); //ok? foreach(const QByteArray& alternateName, inheritedClass->alternateClassNames()) { w->addAlternateClassName( alternateName, inheritedClass->isOverriddenClassName(alternateName)); } if (w->includeFileName().isEmpty()) w->setIncludeFileName(inheritedClass->includeFileName()); if (w->name().isEmpty()) w->setName(inheritedClass->name()); if (w->namePrefix().isEmpty()) w->setNamePrefix(inheritedClass->namePrefix()); if (w->description().isEmpty()) w->setDescription(inheritedClass->description()); } QList cnames( w->alternateClassNames() ); cnames.prepend(w->className()); foreach (const QByteArray &wname, cnames) { WidgetInfo *widgetForClass = widgetsForFactory.value(wname); if (!widgetForClass || (widgetForClass && !widgetForClass->isOverriddenClassName(wname))) { //insert a widgetinfo, if: //1) this class has no alternate class assigned yet, or //2) this class has alternate class assigned but without 'override' flag m_widgets.insert(wname, w); } } } } WidgetLibrary *q; KexiGUIMessageHandler messageHandler; //! A map which associates a class name with a Widget class QHash m_pluginsMetaData; //!< owner bool m_couldNotFindAnyFormWidgetPluginsErrorDisplayed; QSet m_supportedFactoryGroups; QHash m_factories; //!< owner QHash m_widgets; //!< owner QSet m_advancedProperties; QSet m_hiddenClasses; bool m_lookupDone; bool m_lookupResult; bool m_loadFactoriesDone; bool m_loadFactoriesResult; }; } using namespace KFormDesigner; //------------------------------------------- WidgetLibrary::WidgetLibrary(QObject *parent, const QStringList& supportedFactoryGroups) : QObject(parent) , KDbResultable() , d(new Private(this, supportedFactoryGroups)) { } WidgetLibrary::~WidgetLibrary() { delete d; } void WidgetLibrary::createWidgetActions(ActionGroup *group) { foreach (WidgetInfo *winfo, d->widgets()) { LibActionWidget *a = new LibActionWidget(group, winfo); connect(a, SIGNAL(toggled(QByteArray)), this, SIGNAL(widgetActionToggled(QByteArray))); } } void WidgetLibrary::addCustomWidgetActions(KActionCollection *col) { if (!col) return; foreach (WidgetFactory *factory, d->factories()) { factory->createCustomActions(col); } } QWidget* WidgetLibrary::createWidget(const QByteArray &classname, QWidget *parent, const char *name, Container *c, WidgetFactory::CreateWidgetOptions options) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return 0; QWidget *widget = wclass->factory()->createWidget(wclass->className(), parent, name, c, options); if (!widget) { //try to instantiate from inherited class if (wclass->inheritedClass()) widget = wclass->inheritedClass()->factory()->createWidget( wclass->className(), parent, name, c, options); if (!widget) return 0; } widget->setAcceptDrops(true); if (options & WidgetFactory::DesignViewMode) { FormWidgetInterface* fwiface = dynamic_cast(widget); if (fwiface) fwiface->setDesignMode(true); } emit widgetCreated(widget); return widget; } bool WidgetLibrary::createMenuActions(const QByteArray &c, QWidget *w, QMenu *menu, KFormDesigner::Container *container) { WidgetInfo *wclass = d->widgets().value(c); if (!wclass) return false; if (wclass->factory()->createMenuActions(c, w, menu, container)) { return true; } //try from inherited class if (wclass->inheritedClass()) { return wclass->inheritedClass()->factory()->createMenuActions( wclass->className(), w, menu, container); } return false; } bool WidgetLibrary::startInlineEditing(const QByteArray &classname, QWidget *w, Container *container) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return false; FormWidgetInterface* fwiface = dynamic_cast(w); { KFormDesigner::WidgetFactory::InlineEditorCreationArguments args(classname, w, container); if (wclass->factory()->startInlineEditing(args)) { args.container->form()->createInlineEditor(args); if (fwiface) fwiface->setEditingMode(true); return true; } } if (wclass->inheritedClass()) { //try from inherited class KFormDesigner::WidgetFactory::InlineEditorCreationArguments args(wclass->className(), w, container); if (wclass->inheritedClass()->factory()->startInlineEditing(args)) { args.container->form()->createInlineEditor(args); if (fwiface) fwiface->setEditingMode(true); return true; } } return false; } bool WidgetLibrary::previewWidget(const QByteArray &classname, QWidget *widget, Container *container) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return false; FormWidgetInterface* fwiface = dynamic_cast(widget); if (fwiface) fwiface->setDesignMode(false); if (wclass->factory()->previewWidget(classname, widget, container)) return true; //try from inherited class if (wclass->inheritedClass()) return wclass->inheritedClass()->factory()->previewWidget(wclass->className(), widget, container); return false; } bool WidgetLibrary::clearWidgetContent(const QByteArray &classname, QWidget *w) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return false; if (wclass->factory()->clearWidgetContent(classname, w)) return true; //try from inherited class if (wclass->inheritedClass()) return wclass->inheritedClass()->factory()->clearWidgetContent(wclass->className(), w); return false; } QString WidgetLibrary::displayName(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (wi) return wi->name(); return classname; } QString WidgetLibrary::savingName(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (wi && !wi->savingName().isEmpty()) return wi->savingName(); return classname; } QString WidgetLibrary::namePrefix(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (wi) return wi->namePrefix(); return classname; } QString WidgetLibrary::textForWidgetName(const QByteArray &name, const QByteArray &className) { WidgetInfo *widget = d->widgets().value(className); if (!widget) return QString(); QString newName = name; newName.remove(widget->namePrefix()); newName = widget->name() + (newName.isEmpty() ? QString() : (QLatin1String(" ") + newName)); return newName; } QByteArray WidgetLibrary::classNameForAlternate(const QByteArray &classname) { if (d->widgets().value(classname)) return classname; WidgetInfo *wi = d->widgets().value(classname); if (wi) { return wi->className(); } // widget not supported return "CustomWidget"; } QString WidgetLibrary::includeFileName(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (wi) return wi->includeFileName(); return QString(); } QString WidgetLibrary::iconName(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (wi) return wi->iconName(); return KexiIconName("unknown-widget"); } bool WidgetLibrary::saveSpecialProperty(const QByteArray &classname, const QString &name, const QVariant &value, QWidget *w, QDomElement &parentNode, QDomDocument &parent) { WidgetInfo *wi = d->widgets().value(classname); if (!wi) return false; if (wi->factory()->saveSpecialProperty(classname, name, value, w, parentNode, parent)) return true; //try from inherited class if (wi->inheritedClass()) return wi->inheritedClass()->factory()->saveSpecialProperty(wi->className(), name, value, w, parentNode, parent); return false; } bool WidgetLibrary::readSpecialProperty(const QByteArray &classname, QDomElement &node, QWidget *w, ObjectTreeItem *item) { WidgetInfo *wi = d->widgets().value(classname); if (!wi) return false; if (wi->factory()->readSpecialProperty(classname, node, w, item)) return true; //try from inherited class if (wi->inheritedClass()) return wi->inheritedClass()->factory()->readSpecialProperty(wi->className(), node, w, item); return false; } void WidgetLibrary::setAdvancedPropertiesVisible(bool set) { d->showAdvancedProperties = set; } bool WidgetLibrary::advancedPropertiesVisible() const { return d->showAdvancedProperties; } bool WidgetLibrary::isPropertyVisible(const QByteArray &classname, QWidget *w, const QByteArray &property, bool multiple, bool isTopLevel) { if (isTopLevel) { // no focus policy for top-level form widget... if (!d->showAdvancedProperties && property == "focusPolicy") return false; } WidgetInfo *wi = d->widgets().value(classname); if (!wi) return false; if (!d->showAdvancedProperties && d->isAdvancedProperty(property)) { //this is advanced property, should we hide it? if (!wi->internalProperty("forceShowAdvancedProperty:" + property).toBool() && (!wi->inheritedClass() || !wi->inheritedClass()->internalProperty("forceShowAdvancedProperty:" + property).toBool())) { return false; //hide it } } if (!wi->factory()->isPropertyVisible(classname, w, property, multiple, isTopLevel)) return false; //try from inherited class if (wi->inheritedClass() && !wi->inheritedClass()->factory()->isPropertyVisible(wi->className(), w, property, multiple, isTopLevel)) return false; return true; } QList WidgetLibrary::autoSaveProperties(const QByteArray &classname) { WidgetInfo *wi = d->widgets().value(classname); if (!wi) return QList(); return wi->autoSaveProperties(); } WidgetInfo* WidgetLibrary::widgetInfoForClassName(const char* classname) { return d->widgets().value(classname); } WidgetFactory* WidgetLibrary::factoryForClassName(const char* classname) { WidgetInfo *wi = widgetInfoForClassName(classname); return wi ? wi->factory() : 0; } QString WidgetLibrary::propertyDescForName(WidgetInfo *winfo, const QByteArray& propertyName) { if (!winfo || !winfo->factory()) return QString(); QString desc(winfo->factory()->propertyDescription(propertyName)); if (!desc.isEmpty()) return desc; if (winfo->parentFactoryName().isEmpty()) return QString(); //try in parent factory, if exists WidgetFactory *parentFactory = d->factories().value(winfo->parentFactoryName()); if (!parentFactory) return QString(); return parentFactory->propertyDescription(propertyName); } QString WidgetLibrary::propertyDescForValue(WidgetInfo *winfo, const QByteArray& name) { if (!winfo->factory()) return QString(); QString desc(winfo->factory()->valueDescription(name)); if (!desc.isEmpty()) return desc; if (winfo->parentFactoryName().isEmpty()) return QString(); //try in parent factory, if exists WidgetFactory *parentFactory = d->factories().value(winfo->parentFactoryName()); if (!parentFactory) return QString(); return parentFactory->valueDescription(name); } void WidgetLibrary::setPropertyOptions(KPropertySet& set, const WidgetInfo& winfo, QWidget* w) { if (!winfo.factory()) return; winfo.factory()->setPropertyOptions(set, winfo, w); if (winfo.parentFactoryName().isEmpty()) return; WidgetFactory *parentFactory = d->factories().value(winfo.parentFactoryName()); if (!parentFactory) return; parentFactory->setPropertyOptions(set, winfo, w); } WidgetFactory* WidgetLibrary::factory(const char* factoryName) const { return d->factories().value(factoryName); } QVariant WidgetLibrary::internalProperty(const QByteArray& classname, const QByteArray& property) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return QString(); QVariant value(wclass->internalProperty(property)); if (value.isNull() && wclass->inheritedClass()) return wclass->inheritedClass()->internalProperty(property); return value; } WidgetFactory::CreateWidgetOption WidgetLibrary::showOrientationSelectionPopup( const QByteArray &classname, QWidget* parent, const QPoint& pos) { WidgetInfo *wclass = d->widgets().value(classname); if (!wclass) return WidgetFactory::AnyOrientation; //get custom icons and strings QIcon iconHorizontal, iconVertical; QString iconName(wclass->internalProperty("orientationSelectionPopup:horizontalIcon").toString()); if (iconName.isEmpty() && wclass->inheritedClass()) iconName = wclass->inheritedClass()->internalProperty("orientationSelectionPopup:horizontalIcon").toString(); if (!iconName.isEmpty()) iconHorizontal = QIcon::fromTheme(iconName); iconName = wclass->internalProperty("orientationSelectionPopup:verticalIcon").toString(); if (iconName.isEmpty() && wclass->inheritedClass()) iconName = wclass->inheritedClass()->internalProperty("orientationSelectionPopup:verticalIcon").toString(); if (!iconName.isEmpty()) iconVertical = QIcon::fromTheme(iconName); QString textHorizontal = wclass->internalProperty("orientationSelectionPopup:horizontalText").toString(); if (textHorizontal.isEmpty() && wclass->inheritedClass()) iconName = wclass->inheritedClass()->internalProperty("orientationSelectionPopup:horizontalText").toString(); if (textHorizontal.isEmpty()) //default textHorizontal = xi18nc("Insert Horizontal Widget", "Insert Horizontal"); QString textVertical = wclass->internalProperty("orientationSelectionPopup:verticalText").toString(); if (textVertical.isEmpty() && wclass->inheritedClass()) iconName = wclass->inheritedClass()->internalProperty("orientationSelectionPopup:verticalText").toString(); if (textVertical.isEmpty()) //default textVertical = xi18nc("Insert Vertical Widget", "Insert Vertical"); QMenu popup(parent); popup.setObjectName("orientationSelectionPopup"); popup.addSection(QIcon::fromTheme(wclass->iconName()), xi18n("Insert Widget: %1", wclass->name())); QAction* horizAction = popup.addAction(iconHorizontal, textHorizontal); QAction* vertAction = popup.addAction(iconVertical, textVertical); popup.addSeparator(); popup.addAction(koIcon("dialog-cancel"), xi18n("Cancel")); QAction *a = popup.exec(pos); if (a == horizAction) return WidgetFactory::HorizontalOrientation; else if (a == vertAction) return WidgetFactory::VerticalOrientation; return WidgetFactory::AnyOrientation; //means "cancelled" } bool WidgetLibrary::propertySetShouldBeReloadedAfterPropertyChange( const QByteArray& classname, QWidget *w, const QByteArray& property) { WidgetInfo *winfo = widgetInfoForClassName(classname); if (!winfo) return false; return winfo->factory()->propertySetShouldBeReloadedAfterPropertyChange(classname, w, property); } ObjectTreeItem* WidgetLibrary::selectableItem(ObjectTreeItem* item) { //qDebug() << item->widget()->metaObject()->className(); WidgetInfo *wi = d->widgets().value(item->widget()->metaObject()->className()); if (!wi) return item; return wi->factory()->selectableItem(item); } void WidgetLibrary::setErrorMessage(KexiFormWidgetsPluginMetaData *pluginMetaData, const QString& errorMessage) { pluginMetaData->setErrorMessage(errorMessage); } diff --git a/src/kexiutils/KexiCloseButton.cpp b/src/kexiutils/KexiCloseButton.cpp index 40fc3dbb1..d2379001c 100644 --- a/src/kexiutils/KexiCloseButton.cpp +++ b/src/kexiutils/KexiCloseButton.cpp @@ -1,102 +1,102 @@ /* This file is part of the KDE project Copyright (C) 2014 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 "KexiCloseButton.h" #include "utils.h" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiCloseButton::Private { public: Private() { } - bool dummy; + bool dummy = true; }; KexiCloseButton::KexiCloseButton(QWidget* parent) : QToolButton(parent), d(new Private) { init(); } KexiCloseButton::~KexiCloseButton() { delete d; } void KexiCloseButton::init() { setToolTip(KStandardGuiItem::close().plainText()); setAutoRaise(true); setText(QString()); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setFocusPolicy(Qt::NoFocus); setMarginEnabled(true); } void KexiCloseButton::setMarginEnabled(bool set) { QStyleOptionButton option; option.initFrom(this); int m = set ? style()->pixelMetric(QStyle::PM_ButtonMargin, &option, this) : 0; const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, &option, this); setFixedSize(QSize(iconSize, iconSize) + QSize(m*2, m*2)); update(); } void KexiCloseButton::paintEvent(QPaintEvent *e) { Q_UNUSED(e); if (style()->objectName() != "breeze" && QApplication::style()->objectName() != "breeze") { // Draw frames. Breeze's close button has no frames. QToolButton::paintEvent(e); } QStyleOptionButton option; option.initFrom(this); QIcon icon(style()->standardIcon(QStyle::SP_TitleBarCloseButton, &option, this)); QPainter p(this); const int metric = style()->pixelMetric(QStyle::PM_SmallIconSize, &option, this); QSize iconSize(metric, metric); const QSize margin = (size() - iconSize) / 2; QRect iconRect(QPoint(margin.width(), margin.height()), iconSize); const bool enabled(option.state & QStyle::State_Enabled); QIcon::Mode mode; if (option.state & QStyle::State_MouseOver) { mode = QIcon::Active; } else if (enabled) { mode = QIcon::Normal; } else { mode = QIcon::Disabled; } const QIcon::State iconState((isDown() & Qt::LeftButton) ? QIcon::On : QIcon::Off); const QPixmap pixmap = icon.pixmap(iconSize, mode, iconState); style()->drawItemPixmap(&p, iconRect, Qt::AlignCenter, pixmap); } diff --git a/src/main/KexiMainWindow_p.h b/src/main/KexiMainWindow_p.h index 76cd22701..fedfa4189 100644 --- a/src/main/KexiMainWindow_p.h +++ b/src/main/KexiMainWindow_p.h @@ -1,698 +1,698 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2016 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 KEXIMAINWINDOW_P_H #define KEXIMAINWINDOW_P_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KexiMainWindow.h" #include "KexiSearchLineEdit.h" #include "KexiUserFeedbackAgent.h" #include "KexiMenuWidget.h" #include "kexifinddialog.h" #include "KexiStartup.h" #include #include #include #include #include #include #include #define KEXI_NO_PROCESS_EVENTS #ifdef KEXI_NO_PROCESS_EVENTS # define KEXI_NO_PENDING_DIALOGS #endif #define PROJECT_NAVIGATOR_TABBAR_ID 0 #define PROPERTY_EDITOR_TABBAR_ID 1 #define KEXITABBEDTOOLBAR_SPACER_TAB_INDEX 1 class QPainter; class KexiProjectNavigator; //! @short Main application's tabbed toolbar class KexiTabbedToolBar : public QTabWidget { Q_OBJECT public: explicit KexiTabbedToolBar(QWidget *parent); virtual ~KexiTabbedToolBar(); KToolBar *createWidgetToolBar() const; KToolBar *toolBar(const QString& name) const; void appendWidgetToToolbar(const QString& name, QWidget* widget); void setWidgetVisibleInToolbar(QWidget* widget, bool visible); //! @todo replace with the final Actions API void addAction(const QString& toolBarName, QAction *action); bool mainMenuVisible() const; QRect tabRect(int index) const; KHelpMenu *helpMenu() const; void addSearchableModel(KexiSearchableModel *model); KToolBar *createToolBar(const char *name, const QString& caption); void setCurrentTab(const QString& name); //! Sets current tab to @a index, counting from first visible (non-Kexi) tab. //! In non-user mode, the first visible tab is "create" tab. void setCurrentTab(int index); void hideTab(const QString& name); void showTab(const QString& name); bool isTabVisible(const QString& name) const; bool isRolledUp(); public Q_SLOTS: void setMainMenuContent(QWidget *w); void selectMainMenuItem(const char *actionName); void showMainMenu(const char* actionName = 0); void hideMainMenu(); void toggleMainMenu(); void activateSearchLineEdit(); void toggleRollDown(); protected: virtual void mouseMoveEvent(QMouseEvent* event); virtual void leaveEvent(QEvent* event); virtual bool eventFilter(QObject* watched, QEvent* event); protected Q_SLOTS: void slotCurrentChanged(int index); void slotDelayedTabRaise(); void slotSettingsChanged(int category); //! Used for delayed loading of the "create" toolbar. Called only once. void setupCreateWidgetToolbar(); void slotTabDoubleClicked(int index); void tabBarAnimationFinished(); private: void addAction(KToolBar *tbar, const char* actionName); void addSeparatorAndAction(KToolBar *tbar, const char* actionName); class Private; Private * const d; }; //! @internal window container created to speedup opening new tabs class KexiWindowContainer : public QWidget { Q_OBJECT public: explicit KexiWindowContainer(QWidget* parent); virtual ~KexiWindowContainer(); void setWindow(KexiWindow* w); QPointer window; private: QVBoxLayout *lyr; }; class EmptyMenuContentWidget : public QWidget { Q_OBJECT public: explicit EmptyMenuContentWidget(QWidget* parent = 0); void alterBackground(); virtual void changeEvent(QEvent *e); }; //! @todo KEXI3 is KexiMenuWidgetStyle needed? #if 0 //! A style proxy for KexiMenuWidget class KexiMenuWidgetStyle : public KexiUtils::StyleProxy { public: explicit KexiMenuWidgetStyle(QStyle *style, QObject *parent = 0); virtual ~KexiMenuWidgetStyle(); virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; }; #endif //! Main menu class KexiMainMenu : public QWidget { Q_OBJECT public: explicit KexiMainMenu(KexiTabbedToolBar *toolBar, QWidget* parent = 0); ~KexiMainMenu(); virtual bool eventFilter(QObject * watched, QEvent* event); void setContent(QWidget *contentWidget); const QWidget *contentWidget() const; void setPersistentlySelectedAction(KexiMenuWidgetAction* action, bool set); /* void setActiveAction(QAction* action = 0);*/ void selectFirstItem(); tristate showProjectMigrationWizard( const QString& mimeType, const QString& databaseName, const KDbConnectionData *cdata); Q_SIGNALS: void contentAreaPressed(); void hideContentsRequested(); protected Q_SLOTS: //void contentWidgetDestroyed(); protected: virtual void showEvent(QShowEvent * event); private: QPointer m_menuWidget; KexiTabbedToolBar* m_toolBar; bool m_initialized; EmptyMenuContentWidget *m_content; - QStackedLayout *m_contentLayout; + QStackedLayout *m_contentLayout = nullptr; QPointer m_contentWidget; - QVBoxLayout* m_mainContentLayout; + QVBoxLayout* m_mainContentLayout = nullptr; QPointer m_persistentlySelectedAction; bool m_selectFirstItem; }; class KexiTabbedToolBarTabBar; //! @internal class Q_DECL_HIDDEN KexiTabbedToolBar::Private : public QObject { Q_OBJECT public: explicit Private(KexiTabbedToolBar *t); KToolBar *createToolBar(const char *name, const QString& caption); int tabIndex; public Q_SLOTS: void showMainMenu(const char* actionName = 0); void hideMainMenu(); void hideContentsOrMainMenu(); void toggleMainMenu(); void updateMainMenuGeometry(); public: KexiTabbedToolBarTabBar *customTabBar; QPointer mainMenu; KexiTabbedToolBar *q; KActionCollection *ac; int createId; KToolBar *createWidgetToolBar; #ifdef KEXI_AUTORISE_TABBED_TOOLBAR //! Used for delayed tab raising int tabToRaise; //! Used for delayed tab raising QTimer tabRaiseTimer; #endif //! Toolbars for name QHash toolbarsForName; QHash toolbarsIndexForName; QHash toolbarsCaptionForName; QVector toolbarsVisibleForIndex; QHash extraActions; bool rolledUp; QPropertyAnimation tabBarAnimation; QGraphicsOpacityEffect tabBarOpacityEffect; int rolledUpIndex; KHelpMenu *helpMenu; KexiSearchLineEdit *searchLineEdit; void setCurrentTab(const QString& name); void hideTab(const QString& name); void showTab(const QString& name); bool isTabVisible(const QString& name) const; #ifndef NDEBUG void debugToolbars() const; #endif int lowestIndex; }; class KexiTabbedToolBarStyle; //! Tab bar reimplementation for KexiTabbedToolBar. /*! The main its purpose is to alter the width of "Kexi" tab. */ class KexiTabbedToolBarTabBar : public QTabBar { Q_OBJECT public: explicit KexiTabbedToolBarTabBar(QWidget *parent = 0); virtual QSize originalTabSizeHint(int index) const; virtual QSize tabSizeHint(int index) const; KexiTabbedToolBarStyle* customStyle; }; //! Style proxy for KexiTabbedToolBar, to get the "Kexi" tab style right. class KexiTabbedToolBarStyle : public QProxyStyle { Q_OBJECT public: explicit KexiTabbedToolBarStyle(const QString &baseStyleName); virtual ~KexiTabbedToolBarStyle(); virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; virtual int pixelMetric(PixelMetric metric, const QStyleOption* option = 0, const QWidget* widget = 0) const; }; //! Style proxy for KexiTabbedToolBar, to fix the hardcoded margins (e.g. for Breeze). class KexiDockWidgetStyle : public QProxyStyle { Q_OBJECT public: explicit KexiDockWidgetStyle(const QString &baseStyleName); virtual ~KexiDockWidgetStyle(); using QProxyStyle::polish; void polish(QWidget* widget) Q_DECL_OVERRIDE; }; class KexiMainWidget; //! @internal tab widget acting as central widget for KexiMainWindow class KexiMainWindowTabWidget : public QTabWidget { Q_OBJECT public: KexiMainWindowTabWidget(QWidget *parent, KexiMainWidget *mainWidget); virtual ~KexiMainWindowTabWidget(); public Q_SLOTS: void closeTab(); tristate closeAllTabs(); protected: //! Shows context menu for tab at @a index at point @a point. //! If @a index is -1, context menu for empty area is requested. void showContextMenuForTab(int index, const QPoint& point); //! Reimplemented to hide frame when no tabs are displayed virtual void paintEvent(QPaintEvent * event); virtual void mousePressEvent(QMouseEvent *event); KexiMainWidget *m_mainWidget; QAction *m_closeAction; QAction *m_closeAllTabsAction; private: int m_tabIndex; void setTabIndexFromContextMenu(int clickedIndex); }; //! @short A widget being main part of KexiMainWindow class KexiMainWidget : public KMainWindow { Q_OBJECT public: KexiMainWidget(); virtual ~KexiMainWidget(); void setParent(KexiMainWindow* mainWindow); KexiMainWindowTabWidget* tabWidget() const; protected: virtual bool queryClose(); protected Q_SLOTS: void slotCurrentTabIndexChanged(int index); Q_SIGNALS: void currentTabIndexChanged(int index); private: void setupCentralWidget(); KexiMainWindowTabWidget* m_tabWidget; KexiMainWindow *m_mainWindow; QPointer m_previouslyActiveWindow; friend class KexiMainWindow; friend class KexiMainWindowTabWidget; }; //------------------------------------------ //! @internal Dock widget with floating disabled but still collapsible class KexiDockWidget : public QDockWidget { Q_OBJECT public: KexiDockWidget(const QString &tabText, QWidget *parent); virtual ~KexiDockWidget(); virtual void setSizeHint(const QSize& hint); virtual QSize sizeHint() const; const QString tabText; //!< for tab bar tabs protected: virtual void paintEvent(QPaintEvent *pe); private: class Private; Private * const d; }; //------------------------------------------ //! @internal safer dictionary typedef QMap< int, KexiWindow* > KexiWindowDict; //! @internal class Q_DECL_HIDDEN KexiMainWindow::Private { public: explicit Private(KexiMainWindow* w); ~Private(); #ifndef KEXI_NO_PENDING_DIALOGS //! Job type. Currently used for marking items as being opened or closed. enum PendingJobType { NoJob = 0, WindowOpeningJob, WindowClosingJob }; KexiWindow *openedWindowFor(const KexiPart::Item* item, PendingJobType &pendingType); KexiWindow *openedWindowFor(int identifier, PendingJobType &pendingType); void addItemToPendingWindows(const KexiPart::Item* item, PendingJobType jobType); bool pendingWindowsExist(); void removePendingWindow(int identifier); #else KexiWindow *openedWindowFor(int identifier); #endif void insertWindow(KexiWindow *window); bool windowContainerExistsFor(int identifier) const; void setWindowContainerExistsFor(int identifier, bool set); void updateWindowId(KexiWindow *window, int oldItemID); void removeWindow(int identifier); int openedWindowsCount(); //! Used in KexiMainWindowe::closeProject() void clearWindows(); void showStartProcessMsg(const QStringList& args); //! Updates Property Editor Pane's visibility for the current window and the @a viewMode view mode. /*! @a info can be provided to hadle cases when current window is not yet defined (in openObject()). */ void updatePropEditorVisibility(Kexi::ViewMode viewMode, KexiPart::Info *info = 0); void setTabBarVisible(KMultiTabBar::KMultiTabBarPosition position, int id, KexiDockWidget *dockWidget, bool visible); void setPropertyEditorTabBarVisible(bool visible); QObject *openedCustomObjectsForItem(KexiPart::Item* item, const char* name); void addOpenedCustomObjectForItem(KexiPart::Item* item, QObject* object, const char* name); KexiFindDialog *findDialog(); /*! Updates the find/replace dialog depending on the active view. Nothing is performed if the dialog is not instantiated yet or is invisible. */ void updateFindDialogContents(bool createIfDoesNotExist = false); //! \return the current view if it supports \a actionName, otherwise returns 0. KexiView *currentViewSupportingAction(const char* actionName) const; //! \return the current view if it supports KexiSearchAndReplaceViewInterface. KexiSearchAndReplaceViewInterface* currentViewSupportingSearchAndReplaceInterface() const; tristate showProjectMigrationWizard( const QString& mimeType, const QString& databaseName, const KDbConnectionData *cdata); KexiMainWindow *wnd; KexiMainWidget *mainWidget; KActionCollection *actionCollection; KHelpMenu *helpMenu; KexiProject *prj; KSharedConfig::Ptr config; #ifdef KEXI_SHOW_CONTEXT_HELP KexiContextHelp *ctxHelp; #endif KexiProjectNavigator *navigator; KexiTabbedToolBar *tabbedToolBar; QMap tabsToActivateOnShow; KexiDockWidget *navDockWidget; QTabWidget *propEditorTabWidget; KexiDockWidget *propEditorDockWidget; QPointer propEditorDockableWidget; //! poits to kexi part which has been previously used to setup proppanel's tabs using //! KexiPart::setupCustomPropertyPanelTabs(), in updateCustomPropertyPanelTabs(). QPointer partForPreviouslySetupPropertyPanelTabs; QMap recentlySelectedPropertyPanelPages; QPointer propEditor; QPointer propertySet; KexiNameDialog *nameDialog; QTimer timer; //!< helper timer QString appCaptionPrefix; // focus_before_popup; //! Set to true only in destructor, used by closeWindow() to know if //! user can cancel window closing. If true user even doesn't see any messages //! before closing a window. This is for extremely sanity... and shouldn't be even needed. bool forceWindowClosing; //! Indicates that we're inside closeWindow() method - to avoid inf. recursion //! on window removing bool insideCloseWindow; #ifndef KEXI_NO_PENDING_DIALOGS //! Used in executeActionWhenPendingJobsAreFinished(). enum ActionToExecuteWhenPendingJobsAreFinished { NoAction, QuitAction, CloseProjectAction }; ActionToExecuteWhenPendingJobsAreFinished actionToExecuteWhenPendingJobsAreFinished; void executeActionWhenPendingJobsAreFinished(); #endif //! Used for delayed windows closing for 'close all' QList windowsToClose; #ifdef KEXI_QUICK_PRINTING_SUPPORT //! Opened page setup dialogs, used by printOrPrintPreviewForItem(). QHash pageSetupWindows; /*! A map from Kexi dialog to "print setup" part item's ID of the data item used by closeWindow() to find an ID of the data item, so the entry can be removed from pageSetupWindows dictionary. */ QMap pageSetupWindowItemID2dataItemID_map; #endif //! Indicates if project is started in User Mode bool userMode; //! Indicates if project navigator should be visible bool isProjectNavigatorVisible; //! Indicates if the main menu should be visible bool isMainMenuVisible; //! Set in restoreSettings() and used in initNavigator() //! to customize navigator visibility on startup bool forceShowProjectNavigatorOnCreation; bool forceHideProjectNavigatorOnCreation; bool navWasVisibleBeforeProjectClosing; bool saveSettingsForShowProjectNavigator; //! Used by openedCustomObjectsForItem() and addOpenedCustomObjectForItem() QHash m_openedCustomObjectsForItem; int propEditorDockSeparatorPos, navDockSeparatorPos; bool wasAutoOpen; bool windowExistedBeforeCloseProject; QMap multiTabBars; bool propertyEditorCollapsed; bool enable_slotPropertyEditorVisibilityChanged; KexiUserFeedbackAgent userFeedback; KexiMigrateManagerInterface* migrateManager; private: //! @todo move to KexiProject KexiWindowDict windows; //! A set of item identifiers for whose there are KexiWindowContainer instances already. //! This lets to verify that KexiWindow is about to be constructed and opened so multiple //! opening can be avoided. QSet windowContainers; #ifndef KEXI_NO_PROCESS_EVENTS QHash pendingWindows; //!< part item identifiers for windows whoose opening has been started //! @todo QMutex dialogsMutex; //!< used for locking windows and pendingWindows dicts #endif KexiFindDialog *m_findDialog; }; //------------------------------------------ //! Action shortcut used by KexiMainWindow::setupMainMenuActionShortcut(QAction *) //! Activates action only if enabled. class KexiMainMenuActionShortcut : public QShortcut { Q_OBJECT public: KexiMainMenuActionShortcut(const QKeySequence& key, QAction *action, QWidget *parent); virtual ~KexiMainMenuActionShortcut(); protected Q_SLOTS: //! Triggers associated action only when this action is enabled void slotActivated(); private: QPointer m_action; }; #endif diff --git a/src/main/startup/KexiWelcomeStatusBar.cpp b/src/main/startup/KexiWelcomeStatusBar.cpp index c920f52a2..d486ba885 100644 --- a/src/main/startup/KexiWelcomeStatusBar.cpp +++ b/src/main/startup/KexiWelcomeStatusBar.cpp @@ -1,1215 +1,1215 @@ /* This file is part of the KDE project Copyright (C) 2011-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 "KexiWelcomeStatusBar.h" #include "KexiWelcomeStatusBar_p.h" #include #include #include #include #include #include "KexiUserFeedbackAgent.h" #define KEXI_SKIP_SETUPPRIVATEICONSRESOURCE #define KEXI_SKIP_SETUPBREEZEICONTHEME #define KEXI_SKIP_REGISTERICONSRESOURCE #define KEXI_SKIP_REGISTERRESOURCE #include "KexiRegisterResource_p.h" #include #include #include #include #include #include #include #include #if defined(Q_OS_WIN) || defined(Q_OS_MAC) # include # include # include # include #else # define USE_KIO_COPY # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const int GUI_UPDATE_INTERVAL = 60; // update interval for GUI, in minutes static const int DONATION_INTERVAL = 10; // donation interval, in days static const int UPDATE_FILES_LIST_SIZE_LIMIT = 1024 * 128; static const int UPDATE_FILES_COUNT_LIMIT = 128; //! @return x.y.0 static QString stableVersionStringDot0() { return QString::number(Kexi::stableVersionMajor()) + '.' + QString::number(Kexi::stableVersionMinor()) + ".0"; } static QString uiPath(const QString &fname) { KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); return f->serviceUrl() + QString("/ui/%1/").arg(stableVersionStringDot0()) + fname; } static QString basePath() { return QString(KEXI_BASE_PATH "/status"); } static QString findFilename(const QString &guiFileName) { QString result = locateFile(QString(), basePath() + '/' + guiFileName, QStandardPaths::GenericDataLocation, QString()); if (result.isEmpty()) { // last chance: file from the source tree result = QFileInfo(QFile::decodeName(CMAKE_CURRENT_SOURCE_DIR "/status/") + guiFileName) .canonicalFilePath(); } //qDebug() << result; return result; } // --- class Q_DECL_HIDDEN KexiWelcomeStatusBarGuiUpdater::Private : public QObject { Q_OBJECT public: Private() : configGroup(KConfigGroup(KSharedConfig::openConfig()->group("User Feedback"))) { } KConfigGroup configGroup; public Q_SLOTS: void sendRequestListFilesFinished(KJob* job) { if (job->error()) { qWarning() << "Error while receiving .list file - no files will be updated"; //! @todo error... return; } KIO::StoredTransferJob* sendJob = qobject_cast(job); QString result = sendJob->data(); if (result.length() > UPDATE_FILES_LIST_SIZE_LIMIT) { // anti-DOS protection qWarning() << "Too large .list file (" << result.length() << "); the limit is" << UPDATE_FILES_LIST_SIZE_LIMIT << "- no files will be updated"; return; } //qDebug() << result; QStringList data = result.split('\n', QString::SkipEmptyParts); result.clear(); m_fileNamesToUpdate.clear(); if (data.count() > UPDATE_FILES_COUNT_LIMIT) { // anti-DOS protection qWarning() << "Too many files to update (" << data.count() << "); the limit is" << UPDATE_FILES_COUNT_LIMIT << "- no files will be updated"; return; } // OK, try to update (stage 1: check, stage 2: checking) for (int stage = 1; stage <= 2; stage++) { int i = 0; for (QStringList::ConstIterator it(data.constBegin()); it!=data.constEnd(); ++it, i++) { const QByteArray hash((*it).left(32).toLatin1()); const QString remoteFname((*it).mid(32 + 2)); if (stage == 1) { if (hash.length() != 32) { qWarning() << "Invalid hash" << hash << "in line" << i+1 << "- no files will be updated"; return; } if ((*it).mid(32, 2) != " ") { qWarning() << "Two spaces expected but found" << (*it).mid(32, 2) << "in line" << i+1 << "- no files will be updated"; return; } if (remoteFname.contains(QRegularExpression("\\s"))) { qWarning() << "Filename expected without whitespace but found" << remoteFname << "in line" << i+1 << "- no files will be updated"; return; } } else if (stage == 2) { checkFile(hash, remoteFname, &m_fileNamesToUpdate); } } } if (m_fileNamesToUpdate.isEmpty()) { qDebug() << "No files to update."; return; } // update files QList sourceFiles; foreach (const QString &fname, m_fileNamesToUpdate) { sourceFiles.append(QUrl(uiPath(fname))); } m_tempDir.reset(new QTemporaryDir(QDir::tempPath() + "/kexi-status")); //qDebug() << m_tempDir->path(); #ifdef USE_KIO_COPY KIO::CopyJob *copyJob = KIO::copy(sourceFiles, QUrl::fromLocalFile(m_tempDir->path()), KIO::HideProgressInfo | KIO::Overwrite); connect(copyJob, &KIO::CopyJob::result, this, &Private::filesCopyFinished); #else if (!m_downloadManager) { m_downloadManager = new QNetworkAccessManager(this); connect(m_downloadManager.data(), &QNetworkAccessManager::finished, this, &Private::fileDownloadFinished); } m_sourceFilesToDownload = sourceFiles; downloadNextFile(); #endif //qDebug() << "copying from" << QUrl(uiPath(fname)) << "to" // << (dir + fname); } private Q_SLOTS: #ifdef USE_KIO_COPY void filesCopyFinished(KJob* job) { if (job->error()) { //! @todo error... qDebug() << "ERROR:" << job->errorString(); m_tempDir.reset(); return; } KIO::CopyJob* copyJob = qobject_cast(job); Q_UNUSED(copyJob) //qDebug() << "DONE" << copyJob->destUrl(); (void)copyFilesToDestinationDir(); } #else #define DOWNLOAD_BUFFER_SIZE 1024 * 50 void fileDownloadFinished(QNetworkReply* reply) { const bool ok = copyFile(reply); reply->deleteLater(); if (!ok) { qWarning() << "Error downloading file" << m_sourceFilesToDownload.first(); delete m_downloadManager; m_sourceFilesToDownload.clear(); m_tempDir.reset(); } m_sourceFilesToDownload.removeFirst(); downloadNextFile(); } bool copyFile(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { return false; } const QString filename(m_sourceFilesToDownload.first().fileName()); QString path(m_tempDir->path() + '/' + filename); QSaveFile f(path); if (!f.open(QIODevice::WriteOnly)) { return false; } QByteArray buf(DOWNLOAD_BUFFER_SIZE, Qt::Uninitialized); while (!reply->atEnd()) { const qint64 size = reply->read(buf.data(), buf.size()); if (size < 0) { return false; } if (f.write(buf.data(), size) != size) { return false; } } if (!f.commit()) { return false; } return true; } private: void downloadNextFile() { if (m_sourceFilesToDownload.isEmpty()) { // success (void)copyFilesToDestinationDir(); return; } m_downloadManager->get(QNetworkRequest(m_sourceFilesToDownload.first())); } #endif private: bool copyFilesToDestinationDir() { const QString dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + basePath() + '/'); bool ok = true; if (!QDir(dir).exists()) { if (!QDir().mkpath(dir)) { ok = false; qWarning() << "Could not create" << dir; } } if (ok) { foreach (const QString &fname, m_fileNamesToUpdate) { const QByteArray oldName(QFile::encodeName(m_tempDir->path() + '/' + fname)), newName(QFile::encodeName(dir + fname)); if (0 != ::rename(oldName.constData(), newName.constData())) { qWarning() << "cannot move" << (m_tempDir->path() + '/' + fname) << "to" << (dir + fname); } } } QDir(m_tempDir->path()).removeRecursively(); m_tempDir.reset(); m_fileNamesToUpdate.clear(); return ok; } void checkFile(const QByteArray &hash, const QString &remoteFname, QStringList *fileNamesToUpdate) { QString localFname = findFilename(remoteFname); if (localFname.isEmpty()) { fileNamesToUpdate->append(remoteFname); qDebug() << "missing filename" << remoteFname << "- download it"; return; } QFile file(localFname); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "could not open file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); return; } QCryptographicHash md5(QCryptographicHash::Md5); if (!md5.addData(&file)) { qWarning() << "could not check MD5 for file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); return; } if (md5.result().toHex() != hash) { qDebug() << "not matching file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); } } QStringList m_fileNamesToUpdate; QScopedPointer m_tempDir; #ifndef USE_KIO_COPY QList m_sourceFilesToDownload; QPointer m_downloadManager; #endif }; KexiWelcomeStatusBarGuiUpdater::KexiWelcomeStatusBarGuiUpdater() : QObject() , d(new Private) { } KexiWelcomeStatusBarGuiUpdater::~KexiWelcomeStatusBarGuiUpdater() { delete d; } void KexiWelcomeStatusBarGuiUpdater::update() { QDateTime lastStatusBarUpdate = d->configGroup.readEntry("LastStatusBarUpdate", QDateTime()); if (lastStatusBarUpdate.isValid()) { int minutes = lastStatusBarUpdate.secsTo(QDateTime::currentDateTime()) / 60; if (minutes < GUI_UPDATE_INTERVAL) { qDebug() << "gui updated" << minutes << "min. ago, next auto-update in" << (GUI_UPDATE_INTERVAL - minutes) << "min."; return; } } d->configGroup.writeEntry("LastStatusBarUpdate", QDateTime::currentDateTime()); KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); f->waitForRedirect(this, SLOT(slotRedirectLoaded())); } void KexiWelcomeStatusBarGuiUpdater::slotRedirectLoaded() { QByteArray postData = stableVersionStringDot0().toLatin1(); KIO::Job* sendJob = KIO::storedHttpPost(postData, QUrl(uiPath(".list")), KIO::HideProgressInfo); connect(sendJob, SIGNAL(result(KJob*)), d, SLOT(sendRequestListFilesFinished(KJob*))); sendJob->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded"); } // --- //! @internal class ScrollArea : public QScrollArea { Q_OBJECT public: explicit ScrollArea(QWidget *parent = 0) : QScrollArea(parent) { setFrameShape(QFrame::NoFrame); setBackgroundRole(QPalette::Base); setWidgetResizable(true); } void setEnabled(bool set) { if (set != isEnabled()) { QScrollArea::setEnabled(set); updateColors(); } } protected: virtual void changeEvent(QEvent* event) { switch (event->type()) { case QEvent::EnabledChange: case QEvent::PaletteChange: updateColors(); break; default:; } QScrollArea::changeEvent(event); } void updateColors() { if (!widget()) return; KColorScheme scheme(palette().currentColorGroup()); QColor linkColor = scheme.foreground(KColorScheme::LinkText).color(); //qDebug() << "_____________" << isEnabled(); foreach(QLabel* lbl, widget()->findChildren()) { QString t = lbl->text(); QRegularExpression re("", QRegularExpression::InvertedGreedinessOption); int pos = 0; int oldPos = 0; QString newText; QRegularExpressionMatch match = re.match(t); //qDebug() << "t:" << t; while ((pos = match.capturedStart(pos)) != -1) { //qDebug() << "pos:" << pos; //qDebug() << "newText += t.mid(oldPos, pos - oldPos)" // << t.mid(oldPos, pos - oldPos); newText += t.midRef(oldPos, pos - oldPos); //qDebug() << "newText1:" << newText; //qDebug() << lbl->objectName() << "~~~~" << t.mid(pos, re.matchedLength()); QString a = t.mid(pos, match.capturedLength()); //qDebug() << "a:" << a; int colPos = a.indexOf("color:"); if (colPos == -1) { // add color a.insert(a.length() - 1, " style=\"color:" + linkColor.name() + ";\""); } else { // replace color colPos += qstrlen("color:"); for (;colPos < a.length() && a[colPos] == ' '; colPos++) { } if (colPos < a.length() && a[colPos] == '#') { colPos++; int i = colPos; for (;i < a.length(); i++) { if (a[i] == ';' || a[i] == ' ' || a[i] == '"' || a[i] == '\'') break; } //qDebug() << "******" << a.mid(colPos, i - colPos); a.replace(colPos, i - colPos, linkColor.name().mid(1)); } } //qDebug() << "a2:" << a; newText += a; //qDebug() << "newText2:" << newText; pos += match.capturedLength(); oldPos = pos; //qDebug() << "pos2:" << pos; } //qDebug() << "oldPos:" << oldPos; newText += t.midRef(oldPos); //qDebug() << "newText3:" << newText; lbl->setText(newText); } #if 0 QString text; text = QString("%3") .arg(link).arg(linkColor.name()).arg(linkText); if (!format.isEmpty()) { text = QString(format).replace("%L", text); } q->setText(text); #endif } }; // --- class Q_DECL_HIDDEN KexiWelcomeStatusBar::Private { public: explicit Private(KexiWelcomeStatusBar* _q) : statusWidget(0), helpAction(0), shareAction(0), cancelAction(0), q(_q) { rccFname = findFilename("status.rcc"); if (!rccFname.isEmpty()) { QResource::registerResource(rccFname); } scores.insert(KexiUserFeedbackAgent::BasicArea, 4); scores.insert(KexiUserFeedbackAgent::SystemInfoArea, 4); scores.insert(KexiUserFeedbackAgent::ScreenInfoArea, 2); scores.insert(KexiUserFeedbackAgent::RegionalSettingsArea, 2); totalFeedbackScore = 0; foreach (int s, scores.values()) { totalFeedbackScore += s; } donationScore = 20; donated = false; //qDebug() << "totalFeedbackScore:" << totalFeedbackScore; } ~Private() { delete msgWidget; if (!rccFname.isEmpty()) { QResource::unregisterResource(rccFname); } } int currentFeedbackScore() const { int score = 0; KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); KexiUserFeedbackAgent::Areas areas = f->enabledAreas(); for (QMap::ConstIterator it(scores.constBegin()); it!=scores.constEnd(); ++it) { if (areas & it.key()) { score += it.value(); } } //qDebug() << score; return score; } template T widgetOfClass(T parent, const char *widgetName) const { T w = parent->template findChild(widgetName); if (!w) { qWarning() << "NO SUCH widget" << widgetName << "in" << parent; } return w; } QWidget* widget(QWidget *parent, const char *widgetName) const { return widgetOfClass(parent, widgetName); } QObject* object(QObject *parent, const char *objectName) const { QObject *o = parent->findChild(objectName); if (!o) { qWarning() << "NO SUCH object" << objectName << "in" << parent; } return o; } void setProperty(QWidget *parent, const char *widgetName, const char *propertyName, const QVariant &value) { QWidget *w = widget(parent, widgetName); if (w) { w->setProperty(propertyName, value); } } QVariant property(QWidget *parent, const char *widgetName, const char *propertyName) const { QWidget *w = widget(parent, widgetName); return w ? w->property(propertyName) : QVariant(); } void connect(QWidget *parent, const char *widgetName, const char *signalName, QObject *receiver, const char *slotName) { QWidget *w = widget(parent, widgetName); if (w) { QObject::connect(w, signalName, receiver, slotName); } } void animatedHide(QWidget *parent, const char *widgetName) { QWidget *w = widget(parent, widgetName); if (!w) return; KexiFadeWidgetEffect *animation = new KexiFadeWidgetEffect(w); QObject::connect(animation, SIGNAL(destroyed()), w, SLOT(hide())); animation->start(); } QWidget* loadGui(const QString &guiFileName, QWidget *parentWidget = 0) { QString fname = findFilename(guiFileName); if (fname.isEmpty()) { qWarning() << "filename" << fname << "not found"; return 0; } QFile file(fname); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "could not open file" << fname; return 0; } QUiLoader loader; QWidget* widget = loader.load(&file, parentWidget); if (!widget) { qWarning() << "could load ui from file" << fname; } file.close(); return widget; } void updateStatusWidget() { QWidget *widget = loadGui("status.ui", statusScrollArea); if (!widget) { return; } int smallFontSize = qFloor((QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSizeF() + q->font().pointSizeF()) / 2.0); smallFont = q->font(); smallFont.setPointSizeF(smallFontSize); widget->setFont(smallFont); //delete statusWidget; statusWidget = widget; statusScrollArea->setWidget(statusWidget); setProperty(statusWidget, "contribution_progress", "minimumHeight", q->fontMetrics().height()); setProperty(statusWidget, "contribution_progress", "maximumHeight", q->fontMetrics().height()); label_involved_text_mask = property(statusWidget, "label_involved", "text").toString(); setProperty(statusWidget, "link_share_usage_info", "text", property(statusWidget, "link_share_usage_info", "text").toString().arg(totalFeedbackScore)); link_share_more_usage_info_mask = property(statusWidget, "link_share_more_usage_info", "text").toString(); setProperty(statusWidget, "link_donate", "text", property(statusWidget, "link_donate", "text").toString().arg(donationScore)); updateDonationInfo(); updateUserProgress(); updateContributionLinksVisibility(); // do not alter background palette QPalette pal(widget->palette()); pal.setColor(QPalette::Disabled, QPalette::Base, pal.color(QPalette::Normal, QPalette::Base)); widget->setPalette(pal); connect(statusWidget, "link_contribute_show_help", SIGNAL(linkActivated(QString)), q, SLOT(showContributionHelp())); connect(statusWidget, "link_share_usage_info", SIGNAL(linkActivated(QString)), q, SLOT(showShareUsageInfo())); connect(statusWidget, "link_share_more_usage_info", SIGNAL(linkActivated(QString)), q, SLOT(showShareUsageInfo())); connect(statusWidget, "link_show_contribution_details", SIGNAL(linkActivated(QString)), q, SLOT(showContributionDetails())); setProperty(statusWidget, "donation_url", "visible", false); connect(statusWidget, "link_donate", SIGNAL(linkActivated(QString)), q, SLOT(showDonation())); } void setUserProgress(int progress) { setProperty(statusWidget, "contribution_progress", "value", progress); setProperty(statusWidget, "label_involved", "text", label_involved_text_mask.arg(progress)); } void updateUserProgress() { int progress = 0; progress += currentFeedbackScore(); if (donated) { progress += donationScore; } setUserProgress(progress); } void updateContributionLinksVisibility() { KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); int availableLinks = 0; bool noneEnabled = f->enabledAreas() == KexiUserFeedbackAgent::NoAreas; bool allEnabled = f->enabledAreas() == KexiUserFeedbackAgent::AllAreas; setProperty(statusWidget, "share_usage_info", "visible", noneEnabled); if (noneEnabled) { availableLinks++; } setProperty(statusWidget, "share_more_usage_info", "visible", !noneEnabled && !allEnabled); if (!noneEnabled && !allEnabled) { availableLinks++; } setProperty(statusWidget, "link_share_more_usage_info", "text", link_share_more_usage_info_mask.arg(totalFeedbackScore - currentFeedbackScore())); setProperty(statusWidget, "lbl_contribute", "visible", availableLinks > 0); } void updateDonationInfo() { KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); QDateTime lastDonation = configGroup.readEntry("LastDonation", QDateTime()); if (lastDonation.isValid()) { int days = lastDonation.secsTo(QDateTime::currentDateTime()) / 60 / 60 / 24; if (days >= DONATION_INTERVAL) { donated = false; qDebug() << "last donation declared" << days << "days ago, next in" << (DONATION_INTERVAL - days) << "days."; } else if (days >= 0) { donated = true; } } //show always: setProperty(statusWidget, "donate", "visible", !donated); } enum CalloutAlignment { AlignToBar, AlignToWidget }; //! Aligns callout pointer position of msgWidget to widget named @a alignToWidgetName void setMessageWidgetCalloutPointerPosition( const QString& alignToWidgetName, CalloutAlignment calloutAlignment = AlignToBar) { //qDebug() << q->pos() << q->mapToGlobal(QPoint(0, 100)); QPoint p(q->mapToGlobal(QPoint(0, 100))); QWidget *alignToWidget = this->widget(statusWidget, alignToWidgetName.toLatin1()); if (alignToWidget) { p.setY( alignToWidget->mapToGlobal( QPoint(-5, alignToWidget->height() / 2)).y()); if (calloutAlignment == AlignToWidget) { p.setX(alignToWidget->mapToGlobal(QPoint(-5, 0)).x()); //qDebug() << p; } } else { qWarning() << alignToWidgetName << "not found!"; } msgWidget->setCalloutPointerPosition(p, alignToWidget); } //! Shows message widget taking maximum space within the welcome page //! Returns created layout for further use into @a layout. //! Created widge is assigned to msgWidget. //! Calls slot @a slotToCallAfterShow after animated showing, if provided. //! Call msgWidget->animatedShow() afterwards. void showMaximizedMessageWidget(const QString &alignToWidgetName, QPointer *layout, const char* slotToCallAfterShow, CalloutAlignment calloutAlignment = AlignToBar) { QWidget *alignToWidget = this->widget(statusWidget, alignToWidgetName.toLatin1()); int msgWidth; if (alignToWidget && calloutAlignment == AlignToWidget) { msgWidth = q->parentWidget()->width() - alignToWidget->width() - 10; } else { msgWidth = q->parentWidget()->width() - q->width(); } QWidget *widget = new QWidget; *layout = new QGridLayout(widget); if (msgWidth > 100) { // nice text margin (*layout)->setColumnMinimumWidth(0, 50); } //qDebug() << (q->parentWidget()->width() - q->width()) << "***"; KexiContextMessage msg(widget); if (msgWidget) { delete static_cast(msgWidget); } msgWidget = new KexiContextMessageWidget(q->parentWidget()->parentWidget(), 0, 0, msg); msgWidget->setCalloutPointerDirection(KMessageWidget::Right); msgWidget->setMessageType(KMessageWidget::Information); msgWidget->setCloseButtonVisible(true); int offset_y = 0; if (alignToWidget) { offset_y = alignToWidget->mapToGlobal(QPoint(0, 0)).y() - q->parentWidget()->mapToGlobal(QPoint(0, 0)).y(); } else { qWarning() << alignToWidgetName << "not found!"; } msgWidget->resize(msgWidth, q->parentWidget()->height() - offset_y); setMessageWidgetCalloutPointerPosition(alignToWidgetName, calloutAlignment); msgWidget->setResizeTrackingPolicy(Qt::Horizontal | Qt::Vertical); statusScrollArea->setEnabled(false); // async show to for speed up if (slotToCallAfterShow) { QObject::connect(msgWidget, SIGNAL(animatedShowFinished()), q, slotToCallAfterShow); } QObject::connect(msgWidget, SIGNAL(animatedHideFinished()), q, SLOT(slotMessageWidgetClosed())); } ScrollArea *statusScrollArea; QWidget *statusWidget; QVBoxLayout *lyr; QPointer msgWidget; QFont smallFont; QAction *helpAction; QAction *shareAction; QAction *cancelAction; QString label_involved_text_mask; QString link_share_more_usage_info_mask; QPointer contributionHelpLayout; QPointer contributionDetailsLayout; QPointer contributionDetailsWidget; QMap scores; QString countryMask; QString languageMask; - bool detailsDataVisible; + bool detailsDataVisible = false; int totalFeedbackScore; int donationScore; bool donated; KexiWelcomeStatusBarGuiUpdater guiUpdater; private: QString rccFname; KexiWelcomeStatusBar *q; QMap dict; }; KexiWelcomeStatusBar::KexiWelcomeStatusBar(QWidget* parent) : QWidget(parent), d(new Private(this)) { d->lyr = new QVBoxLayout(this); init(); } KexiWelcomeStatusBar::~KexiWelcomeStatusBar() { delete d; } void KexiWelcomeStatusBar::init() { d->statusScrollArea = new ScrollArea(this); d->lyr->addWidget(d->statusScrollArea); d->updateStatusWidget(); QTimer::singleShot(10, &d->guiUpdater, SLOT(update())); } void KexiWelcomeStatusBar::showContributionHelp() { d->showMaximizedMessageWidget("link_contribute_show_help", &d->contributionHelpLayout, SLOT(slotShowContributionHelpContents())); d->msgWidget->animatedShow(); } void KexiWelcomeStatusBar::slotShowContributionHelpContents() { QWidget *helpWidget = d->loadGui("contribution_help.ui"); d->contributionHelpLayout->addWidget(helpWidget, 1, 1); d->msgWidget->setPaletteInherited(); } void KexiWelcomeStatusBar::slotMessageWidgetClosed() { d->statusScrollArea->setEnabled(true); d->updateDonationInfo(); d->updateUserProgress(); d->updateContributionLinksVisibility(); } void KexiWelcomeStatusBar::showShareUsageInfo() { if (!sender()) { return; } QWidget *widget = d->loadGui("status_strings.ui"); if (!widget) { return; } QLabel *lbl = widget->findChild("question"); if (!lbl) { return; } KexiContextMessage msg(lbl->text()); delete widget; if (!d->helpAction) { d->helpAction = new QAction(KStandardGuiItem::help().icon(), KStandardGuiItem::help().text(), this); connect(d->helpAction, SIGNAL(triggered()), this, SLOT(showContributionHelp())); } if (!d->shareAction) { d->shareAction = new QAction(KStandardGuiItem::yes().icon(), xi18n("Share"), this); connect(d->shareAction, SIGNAL(triggered()), this, SLOT(slotShareFeedback())); } if (!d->cancelAction) { d->cancelAction = new QAction(KStandardGuiItem::cancel().icon(), KStandardGuiItem::cancel().text(), this); QObject::connect(d->cancelAction, SIGNAL(triggered()), this, SLOT(slotCancelled())); } msg.addAction(d->helpAction, KexiContextMessage::AlignLeft); msg.addAction(d->shareAction); msg.addAction(d->cancelAction); if (d->msgWidget) { delete static_cast(d->msgWidget); } d->msgWidget = new KexiContextMessageWidget(parentWidget(), 0, 0, msg); d->msgWidget->setMessageType(KMessageWidget::Information); d->msgWidget->setCalloutPointerDirection(KMessageWidget::Right); d->setMessageWidgetCalloutPointerPosition(sender()->objectName()); d->statusScrollArea->setEnabled(false); d->msgWidget->setMaximumWidth(parentWidget()->width() - width()); d->msgWidget->setResizeTrackingPolicy(Qt::Horizontal); d->msgWidget->animatedShow(); } void KexiWelcomeStatusBar::showDonation() { if (!sender()) { return; } if (KMessageBox::Yes != KMessageBox::questionYesNo(this, xi18nc("@info donate to the project", "Kexi may be totally free, but its development is costly." "Power, hardware, office space, internet access, traveling for meetings - everything costs." "Direct donation is the easiest and fastest way to efficiently support the Kexi Project. " "Everyone, regardless of any degree of involvement can do so." "What do you receive for your donation? Kexi will become more feature-full and stable as " "contributors will be able to devote more time to Kexi. Not only you can " "expect new features, but you can also have an influence on what features are added!" "Currently we are accepting donations through BountySource (a funding platform " "for open-source software) using secure PayPal, Bitcoin and Google Wallet transfers." "Contact us at https://community.kde.org/Kexi/Contact " "for more information." "Thanks for your support!"), xi18n("Donate to the Project"), KGuiItem(xi18nc("@action:button Go to Donation", "Proceed to the Donation Web Page"), QIcon(":/icons/heart.png")), KGuiItem(xi18nc("Do not donate now", "Not Now")), QString(), KMessageBox::Notify | KMessageBox::AllowLink)) { return; } QUrl donationUrl(d->property(this, "donation_url", "text").toString()); if (donationUrl.isValid()) { QDesktopServices::openUrl(donationUrl); d->donated = true; d->updateStatusWidget(); KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); int donationsCount = configGroup.readEntry("DonationsCount", 0); configGroup.writeEntry("LastDonation", QDateTime::currentDateTime()); configGroup.writeEntry("DonationsCount", donationsCount + 1); } else { qWarning() << "Invalid donation URL" << donationUrl; } } void KexiWelcomeStatusBar::slotShareFeedback() { d->statusScrollArea->setEnabled(true); d->msgWidget->animatedHide(); KexiMainWindowIface::global()->userFeedbackAgent() ->setEnabledAreas(KexiUserFeedbackAgent::AllAreas); d->animatedHide(d->statusWidget, "share_usage_info"); d->animatedHide(d->statusWidget, "share_more_usage_info"); d->animatedHide(d->statusWidget, "lbl_contribute"); d->updateUserProgress(); } void KexiWelcomeStatusBar::slotCancelled() { d->statusScrollArea->setEnabled(true); } // Contribution Details BEGIN void KexiWelcomeStatusBar::showContributionDetails() { d->showMaximizedMessageWidget("link_show_contribution_details", &d->contributionDetailsLayout, 0, KexiWelcomeStatusBar::Private::AlignToWidget); d->contributionDetailsLayout->setColumnMinimumWidth(0, 6); // smaller d->contributionDetailsWidget = d->loadGui("contribution_details.ui"); KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); d->setProperty(d->contributionDetailsWidget, "group_share", "checked", f->enabledAreas() != KexiUserFeedbackAgent::NoAreas); d->setProperty(d->contributionDetailsWidget, "group_basic", "title", d->property(d->contributionDetailsWidget, "group_basic", "title") .toString().arg(d->scores.value(KexiUserFeedbackAgent::BasicArea))); updateContributionGroupCheckboxes(); d->setProperty(d->contributionDetailsWidget, "group_system", "title", d->property(d->contributionDetailsWidget, "group_system", "title") .toString().arg(d->scores.value(KexiUserFeedbackAgent::SystemInfoArea))); d->connect(d->contributionDetailsWidget, "group_system", SIGNAL(toggled(bool)), this, SLOT(slotShareContributionDetailsGroupToggled(bool))); d->setProperty(d->contributionDetailsWidget, "group_screen", "title", d->property(d->contributionDetailsWidget, "group_screen", "title") .toString().arg(d->scores.value(KexiUserFeedbackAgent::ScreenInfoArea))); d->connect(d->contributionDetailsWidget, "group_screen", SIGNAL(toggled(bool)), this, SLOT(slotShareContributionDetailsGroupToggled(bool))); d->setProperty(d->contributionDetailsWidget, "group_regional_settings", "title", d->property(d->contributionDetailsWidget, "group_regional_settings", "title") .toString().arg(d->scores.value(KexiUserFeedbackAgent::RegionalSettingsArea))); d->connect(d->contributionDetailsWidget, "group_regional_settings", SIGNAL(toggled(bool)), this, SLOT(slotShareContributionDetailsGroupToggled(bool))); d->detailsDataVisible = false; slotShareContributionDetailsToggled( d->property(d->contributionDetailsWidget, "group_share", "checked").toBool()); d->detailsDataVisible = true; // to switch off slotToggleContributionDetailsDataVisibility(); d->connect(d->contributionDetailsWidget, "group_share", SIGNAL(toggled(bool)), this, SLOT(slotShareContributionDetailsToggled(bool))); d->connect(d->contributionDetailsWidget, "link_show_shared_info", SIGNAL(linkActivated(QString)), this, SLOT(slotToggleContributionDetailsDataVisibility())); d->setProperty(d->contributionDetailsWidget, "label_where_is_info_sent", "visible", false); ScrollArea *contributionDetailsArea = new ScrollArea(d->msgWidget); d->contributionDetailsLayout->addWidget(contributionDetailsArea, 1, 1); contributionDetailsArea->setWidget(d->contributionDetailsWidget); d->msgWidget->animatedShow(); d->msgWidget->setPaletteInherited(); } void KexiWelcomeStatusBar::updateContributionGroupCheckboxes() { KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); d->setProperty(d->contributionDetailsWidget, "group_system", "checked", bool(f->enabledAreas() & KexiUserFeedbackAgent::SystemInfoArea)); d->setProperty(d->contributionDetailsWidget, "group_screen", "checked", bool(f->enabledAreas() & KexiUserFeedbackAgent::ScreenInfoArea)); d->setProperty(d->contributionDetailsWidget, "group_regional_settings", "checked", bool(f->enabledAreas() & KexiUserFeedbackAgent::RegionalSettingsArea)); } void KexiWelcomeStatusBar::slotShareContributionDetailsToggled(bool on) { //qDebug() << sender(); QWidget* group_share = d->widget(d->contributionDetailsWidget, "group_share"); KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); if (sender() == group_share) { f->setEnabledAreas(on ? KexiUserFeedbackAgent::AllAreas : KexiUserFeedbackAgent::NoAreas); updateContributionGroupCheckboxes(); } if (!group_share) { return; } for (int i=0; i < group_share->layout()->count(); i++) { QWidget *w = group_share->layout()->itemAt(i)->widget(); if (w) { w->setVisible(on); } } if (d->detailsDataVisible) { slotToggleContributionDetailsDataVisibility(); } // fill shared values QLocale locale; foreach(QLabel* lbl, d->contributionDetailsWidget->findChildren()) { if (lbl->objectName().startsWith(QLatin1String("value_"))) { QString name = lbl->objectName().mid(6); // cut "value_" QVariant value; if (name == QLatin1String("screen_size")) { value = QString("%1 x %2").arg(f->value("screen_width").toString()) .arg(f->value("screen_height").toString()); } else if (name == QLatin1String("country")) { if (d->countryMask.isEmpty()) { d->countryMask = lbl->text(); } value = d->countryMask .arg(f->value(name).toString() /*!< @todo KEXI3 port KLocale::global()->countryCodeToName(f->value(name).toString()) */) .arg(f->value(name).toString()); } else if (name == QLatin1String("language")) { if (d->languageMask.isEmpty()) { d->languageMask = lbl->text(); } value = d->languageMask .arg(f->value(name).toString() /*!< @todo KEXI3 port KLocale::global()->languageCodeToName(f->value(name).toString()) */) .arg(f->value(name).toString()); } else { value = f->value(name); } if (value.type() == QVariant::Bool) { value = value.toBool() ? KStandardGuiItem::yes().plainText() : KStandardGuiItem::no().plainText(); } if (!value.isNull()) { lbl->setText(value.toString()); } } else if (lbl->objectName().startsWith(QLatin1String("desc_"))) { lbl->setFont(d->smallFont); } } QLabel* lbl; KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); if ((lbl = d->contributionDetailsWidget->findChild("value_recent_donation"))) { QDateTime lastDonation = configGroup.readEntry("LastDonation", QDateTime()); QString recentDonation = "-"; if (lastDonation.isValid()) { int days = lastDonation.secsTo(QDateTime::currentDateTime()) / 60 / 60 / 24; if (days == 0) { recentDonation = xi18nc("Donation today", "today"); } else if (days > 0) { recentDonation = xi18ncp("Recent donation date (xx days)", "%1 (1 day)", "%1 (%2 days)", locale.toString(lastDonation), days); } } lbl->setText(recentDonation); } if ((lbl = d->contributionDetailsWidget->findChild("value_donations_count"))) { int donationsCount = configGroup.readEntry("DonationsCount", 0); if (donationsCount == 0) { lbl->setText(QString::number(donationsCount)); } else { lbl->setText(xi18nc("donations count", "%1 (thanks!)", donationsCount)); } } } static void setArea(KexiUserFeedbackAgent::Areas *areas, KexiUserFeedbackAgent::Area area, bool on) { *areas |= area; if (!on) { *areas ^= area; } } void KexiWelcomeStatusBar::slotShareContributionDetailsGroupToggled(bool on) { if (!sender()) { return; } const QString name = sender()->objectName(); KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); KexiUserFeedbackAgent::Areas areas = f->enabledAreas(); //qDebug() << areas; if (name == "group_system") { setArea(&areas, KexiUserFeedbackAgent::SystemInfoArea, on); } else if (name == "group_screen") { setArea(&areas, KexiUserFeedbackAgent::ScreenInfoArea, on); } else if (name == "group_regional_settings") { setArea(&areas, KexiUserFeedbackAgent::RegionalSettingsArea, on); } if (areas) { areas |= KexiUserFeedbackAgent::AnonymousIdentificationArea; } f->setEnabledAreas(areas); //qDebug() << f->enabledAreas(); } void KexiWelcomeStatusBar::slotToggleContributionDetailsDataVisibility() { QWidget* value_app_ver = d->widget(d->contributionDetailsWidget, "value_app_ver"); if (!value_app_ver) { return; } d->detailsDataVisible = !d->detailsDataVisible; if (d->detailsDataVisible) { d->setProperty(d->contributionDetailsWidget, "link_show_shared_info", "visible", false); d->setProperty(d->contributionDetailsWidget, "label_where_is_info_sent", "visible", true); } bool show = d->contributionDetailsWidget->isVisible(); QList list; d->contributionDetailsWidget->hide(); QWidget* group_basic = d->widget(d->contributionDetailsWidget, "group_basic"); if (group_basic) { list += group_basic->findChildren(); } QWidget* group_system = d->widget(d->contributionDetailsWidget, "group_system"); if (group_system) { list += group_system->findChildren(); } QWidget* group_screen = d->widget(d->contributionDetailsWidget, "group_screen"); if (group_screen) { list += group_screen->findChildren(); } QWidget* group_regional_settings = d->widget(d->contributionDetailsWidget, "group_regional_settings"); if (group_regional_settings) { list += group_regional_settings->findChildren(); } foreach (QWidget* w, list) { if (qobject_cast(w) && !w->objectName().startsWith(QLatin1String("desc_"))) { //qDebug() << "+++" << w; w->setVisible(d->detailsDataVisible); } } if (show) { d->contributionDetailsWidget->show(); } } // Contribution Details END #include "KexiWelcomeStatusBar.moc" diff --git a/src/plugins/forms/kexidbtextwidgetinterface.cpp b/src/plugins/forms/kexidbtextwidgetinterface.cpp index ee17aa59e..72a2fea75 100644 --- a/src/plugins/forms/kexidbtextwidgetinterface.cpp +++ b/src/plugins/forms/kexidbtextwidgetinterface.cpp @@ -1,78 +1,78 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 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 "kexidbtextwidgetinterface.h" #include #include #include #include KexiDBTextWidgetInterface::KexiDBTextWidgetInterface() : m_autonumberDisplayParameters(0) { } KexiDBTextWidgetInterface::~KexiDBTextWidgetInterface() { delete m_autonumberDisplayParameters; } void KexiDBTextWidgetInterface::setColumnInfo(KDbQueryColumnInfo* cinfo, QWidget *w) { if (cinfo->field()->isAutoIncrement()) { if (!m_autonumberDisplayParameters) m_autonumberDisplayParameters = new KexiDisplayUtils::DisplayParameters(); - KexiDisplayUtils::initDisplayForAutonumberSign(*m_autonumberDisplayParameters, w); + KexiDisplayUtils::initDisplayForAutonumberSign(m_autonumberDisplayParameters, w); } } void KexiDBTextWidgetInterface::paint( QWidget *w, QPainter* p, bool textIsEmpty, Qt::Alignment alignment, bool hasFocus) { KexiFormDataItemInterface *dataItemIface = dynamic_cast(w); KDbQueryColumnInfo *columnInfo = dataItemIface ? dataItemIface->columnInfo() : 0; if (columnInfo && columnInfo->field() && dataItemIface->cursorAtNewRecord() && textIsEmpty) { int addMargin = 0; if (dynamic_cast(w)) addMargin += dynamic_cast(w)->lineWidth() + dynamic_cast(w)->midLineWidth(); if (columnInfo->field()->isAutoIncrement() && m_autonumberDisplayParameters) { if (w->hasFocus()) { p->setPen( KexiUtils::blendedColors( m_autonumberDisplayParameters->textColor, w->palette().color(QPalette::Base), 1, 3)); } QMargins margins(w->contentsMargins()); KexiDisplayUtils::paintAutonumberSign(*m_autonumberDisplayParameters, p, 2 + addMargin + margins.left(), addMargin + margins.top(), w->width() - margins.left() - margins.right() - 2 - 2, w->height() - margins.top() - margins.bottom() - 2, alignment, hasFocus); } } } void KexiDBTextWidgetInterface::event(QEvent * e, QWidget *w, bool textIsEmpty) { if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { if (m_autonumberDisplayParameters && textIsEmpty) w->repaint(); } } diff --git a/src/plugins/importexport/csv/kexicsvexport.cpp b/src/plugins/importexport/csv/kexicsvexport.cpp index c7b825c93..54744dec3 100644 --- a/src/plugins/importexport/csv/kexicsvexport.cpp +++ b/src/plugins/importexport/csv/kexicsvexport.cpp @@ -1,282 +1,284 @@ /* This file is part of the KDE project Copyright (C) 2005,2006 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 "kexicsvexport.h" #include "kexicsvwidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KexiCSVExport; Options::Options() - : mode(File), itemId(0), addColumnNames(true) + : mode(File), itemId(0), addColumnNames(true), useTempQuery(false) { } bool Options::assign(QMap *args) { + bool result = true; mode = (args->value("destinationType") == "file") ? KexiCSVExport::File : KexiCSVExport::Clipboard; if (args->contains("delimiter")) delimiter = args->value("delimiter"); else delimiter = (mode == File) ? KEXICSV_DEFAULT_FILE_DELIMITER : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; if (args->contains("textQuote")) textQuote = args->value("textQuote"); else textQuote = (mode == File) ? KEXICSV_DEFAULT_FILE_TEXT_QUOTE : KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; bool ok; itemId = args->value("itemId").toInt(&ok); - if (!ok || itemId == 0) //neverSaved items are supported - return false; + if (!ok || itemId == 0) { + result = false; //neverSaved items are supported + } if (args->contains("forceDelimiter")) forceDelimiter = args->value("forceDelimiter"); if (args->contains("addColumnNames")) addColumnNames = (args->value("addColumnNames") == "1"); useTempQuery = (args->value("useTempQuery") == "1"); - return true; + return result; } //------------------------------------ bool KexiCSVExport::exportData(KDbTableOrQuerySchema *tableOrQuery, const Options& options, int recordCount, QTextStream *predefinedTextStream) { KDbConnection* conn = tableOrQuery->connection(); if (!conn) return false; KDbQuerySchema* query = tableOrQuery->query(); QList queryParams; if (!query) { query = tableOrQuery->table()->query(); } else { queryParams = KexiMainWindowIface::global()->currentParametersForQuery(query->id()); } if (recordCount == -1) recordCount = KDb::recordCount(tableOrQuery, queryParams); if (recordCount == -1) return false; //! @todo move this to non-GUI location so it can be also used via command line //! @todo add a "finish" page with a progressbar. //! @todo look at recordCount whether the data is really large; //! if so: avoid copying to clipboard (or ask user) because of system memory //! @todo OPTIMIZATION: use fieldsExpanded(true /*UNIQUE*/) //! @todo OPTIMIZATION? (avoid multiple data retrieving) look for already fetched data within KexiProject.. KDbQueryColumnInfo::Vector fields(query->fieldsExpanded(KDbQuerySchema::WithInternalFields)); QString buffer; QScopedPointer kSaveFile; QTextStream *stream = 0; QScopedPointer kSaveFileTextStream; const bool copyToClipboard = options.mode == Clipboard; if (copyToClipboard) { //! @todo (during exporting): enlarge bufSize by factor of 2 when it became too small int bufSize = qMin((recordCount < 0 ? 10 : recordCount) * fields.count() * 20, 128000); buffer.reserve(bufSize); if (buffer.capacity() < bufSize) { qWarning() << "Cannot allocate memory for " << bufSize << " characters"; return false; } } else { if (predefinedTextStream) { stream = predefinedTextStream; } else { if (options.fileName.isEmpty()) {//sanity qWarning() << "Fname is empty"; return false; } kSaveFile.reset(new QSaveFile(options.fileName)); qDebug() << "QSaveFile Filename:" << kSaveFile->fileName(); if (kSaveFile->open(QIODevice::WriteOnly)) { kSaveFileTextStream.reset(new QTextStream(kSaveFile.data())); stream = kSaveFileTextStream.data(); qDebug() << "have a stream"; } if (QFileDevice::NoError != kSaveFile->error() || !stream) {//sanity qWarning() << "Status != 0 or stream == 0"; //! @todo show error return false; } } } //! @todo escape strings #define _ERR \ if (kSaveFile) { kSaveFile->cancelWriting(); } \ return false #define APPEND(what) \ if (copyToClipboard) buffer.append(what); else (*stream) << (what) // use native line ending for copying, RFC 4180 one for saving to file #define APPEND_EOLN \ if (copyToClipboard) { APPEND('\n'); } else { APPEND("\r\n"); } qDebug() << 0 << "Columns: " << query->fieldsExpanded().count(); // 0. Cache information const int fieldsCount = query->fieldsExpanded().count(); //real fields count without internals const QChar delimiter(options.delimiter.at(0)); const bool hasTextQuote = !options.textQuote.isEmpty(); const QString textQuote(options.textQuote.at(0)); const QByteArray escapedTextQuote((textQuote + textQuote).toLatin1()); //ok? //cache for faster checks QScopedArrayPointer isText(new bool[fieldsCount]); QScopedArrayPointer isDateTime(new bool[fieldsCount]); QScopedArrayPointer isTime(new bool[fieldsCount]); QScopedArrayPointer isBLOB(new bool[fieldsCount]); QScopedArrayPointer visibleFieldIndex(new int[fieldsCount]); // bool isInteger[fieldsCount]; //cache for faster checks // bool isFloatingPoint[fieldsCount]; //cache for faster checks for (int i = 0; i < fieldsCount; i++) { KDbQueryColumnInfo* ci; const int indexForVisibleLookupValue = fields[i]->indexForVisibleLookupValue(); if (-1 != indexForVisibleLookupValue) { ci = query->expandedOrInternalField(indexForVisibleLookupValue); visibleFieldIndex[i] = indexForVisibleLookupValue; } else { ci = fields[i]; visibleFieldIndex[i] = i; } const KDbField::Type t = ci->field()->type(); // cache: evaluating type of expressions can be expensive isText[i] = KDbField::isTextType(t); isDateTime[i] = t == KDbField::DateTime; isTime[i] = t == KDbField::Time; isBLOB[i] = t == KDbField::BLOB; // isInteger[i] = KDbField::isIntegerType(t) // || t == KDbField::Boolean; // isFloatingPoint[i] = KDbField::isFPNumericType(t); } // 1. Output column names if (options.addColumnNames) { for (int i = 0; i < fieldsCount; i++) { //qDebug() << "Adding column names"; if (i > 0) { APPEND(delimiter); } if (hasTextQuote) { APPEND(textQuote + fields[i]->captionOrAliasOrName().replace(textQuote, escapedTextQuote) + textQuote); } else { APPEND(fields[i]->captionOrAliasOrName()); } } APPEND_EOLN } KexiGUIMessageHandler handler; KDbCursor *cursor = conn->executeQuery(query, queryParams); if (!cursor) { handler.showErrorMessage(conn->result()); _ERR; } for (cursor->moveFirst(); !cursor->eof() && !cursor->result().isError(); cursor->moveNext()) { //qDebug() << "Adding records"; const int realFieldCount = qMin(cursor->fieldCount(), fieldsCount); for (int i = 0; i < realFieldCount; i++) { const int real_i = visibleFieldIndex[i]; if (i > 0) { APPEND(delimiter); } if (cursor->value(real_i).isNull()) { continue; } if (isText[real_i]) { if (hasTextQuote) APPEND(textQuote + QString(cursor->value(real_i).toString()).replace(textQuote, escapedTextQuote) + textQuote); else APPEND(cursor->value(real_i).toString()); } else if (isDateTime[real_i]) { //avoid "T" in ISO DateTime APPEND(cursor->value(real_i).toDateTime().date().toString(Qt::ISODate) + " " + cursor->value(real_i).toDateTime().time().toString(Qt::ISODate)); } else if (isTime[real_i]) { //time is temporarily stored as null date + time... APPEND(cursor->value(real_i).toTime().toString(Qt::ISODate)); } else if (isBLOB[real_i]) { //BLOB is escaped in a special way if (hasTextQuote) //! @todo add options to suppport other types from KDbBLOBEscapingType enum... APPEND(textQuote + KDb::escapeBLOB(cursor->value(real_i).toByteArray(), KDb::BLOBEscapeHex) + textQuote); else APPEND(KDb::escapeBLOB(cursor->value(real_i).toByteArray(), KDb::BLOBEscapeHex)); } else {//other types APPEND(cursor->value(real_i).toString()); } } APPEND_EOLN } if (copyToClipboard) buffer.squeeze(); if (!conn->deleteCursor(cursor)) { handler.showErrorMessage(conn->result()); _ERR; } if (copyToClipboard) QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); qDebug() << "Done"; if (kSaveFile) { stream->flush(); if (!kSaveFile->commit()) { qWarning() << "Error commiting the file" << kSaveFile->fileName(); } } return true; } diff --git a/src/plugins/importexport/csv/kexicsvexportwizard.h b/src/plugins/importexport/csv/kexicsvexportwizard.h index 08ad0c775..f5b6738bf 100644 --- a/src/plugins/importexport/csv/kexicsvexportwizard.h +++ b/src/plugins/importexport/csv/kexicsvexportwizard.h @@ -1,109 +1,109 @@ /* This file is part of the KDE project Copyright (C) 2012 Oleg Kukharchuk Copyright (C) 2005-2013 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 KEXI_CSVEXPORTWIZARD_H #define KEXI_CSVEXPORTWIZARD_H #include #include #include #include "kexicsvexport.h" class QCheckBox; class QGroupBox; class QPushButton; class KexiFileWidget; class KexiCSVDelimiterWidget; class KexiCSVTextQuoteComboBox; class KexiCSVInfoLabel; class KexiCharacterEncodingComboBox; class KPageWidgetItem; class KDbTableOrQuerySchema; /*! @short Kexi CSV export wizard Supports exporting to a file and to a clipboard. */ class KexiCSVExportWizard : public KAssistantDialog { Q_OBJECT public: explicit KexiCSVExportWizard(const KexiCSVExport::Options& options, QWidget * parent = 0); virtual ~KexiCSVExportWizard(); bool canceled() const; protected Q_SLOTS: virtual void next(); virtual void done(int result); void slotShowOptionsButtonClicked(); void slotDefaultsButtonClicked(); void slotCurrentPageChanged(KPageWidgetItem*, KPageWidgetItem*); protected: //! \return default delimiter depending on mode. QString defaultDelimiter() const; //! \return default text quote depending on mode. QString defaultTextQuote() const; //! Helper, works like KSharedConfig::openConfig()->readBoolEntry(const char*, bool) but if mode is Clipboard, //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" //! and "CSVFiles" is replaced with "CSVToClipboard" //! in \a key, to keep the setting separate. bool readBoolEntry(const char *key, bool defaultValue); //! Helper like \ref readBoolEntry(const char *, bool), but for QString values. QString readEntry(const char *key, const QString& defaultValue = QString()); //! Helper, works like KSharedConfig::openConfig()->writeEntry(const char*,bool) but if mode is Clipboard, //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" //! and "CSVFiles" is replaced with "CSVToClipboard" //! in \a key, to keep the setting separate. void writeEntry(const char *key, bool value); //! Helper like \ref writeEntry(const char *, bool), but for QString values. void writeEntry(const char *key, const QString& value); //! Helper like \ref writeEntry(const char *, bool), but for deleting config entry. void deleteEntry(const char *key); KexiCSVExport::Options m_options; - KexiFileWidget* m_fileSaveWidget; + KexiFileWidget* m_fileSaveWidget = nullptr; QWidget* m_exportOptionsWidget; KPageWidgetItem *m_fileSavePage; KPageWidgetItem *m_exportOptionsPage; QPushButton *m_showOptionsButton; QPushButton *m_defaultsBtn; QGroupBox* m_exportOptionsSection; KexiCSVInfoLabel *m_infoLblFrom, *m_infoLblTo; KexiCSVDelimiterWidget* m_delimiterWidget; KexiCSVTextQuoteComboBox* m_textQuote; KexiCharacterEncodingComboBox *m_characterEncodingCombo; QCheckBox* m_addColumnNamesCheckBox, *m_alwaysUseCheckBox; KDbTableOrQuerySchema* m_tableOrQuery; KConfigGroup m_importExportGroup; bool m_canceled; }; #endif diff --git a/src/widget/dataviewcommon/kexiformdataiteminterface.cpp b/src/widget/dataviewcommon/kexiformdataiteminterface.cpp index 7ed4a0f27..877d36391 100644 --- a/src/widget/dataviewcommon/kexiformdataiteminterface.cpp +++ b/src/widget/dataviewcommon/kexiformdataiteminterface.cpp @@ -1,89 +1,89 @@ /* This file is part of the KDE project Copyright (C) 2005-2009 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 "kexiformdataiteminterface.h" #include #include #include KexiFormDataItemInterface::KexiFormDataItemInterface() : KexiDataItemInterface() , m_columnInfo(0) , m_displayParametersForEnteredValue(0) , m_displayParametersForDefaultValue(0) , m_displayDefaultValue(false) { } KexiFormDataItemInterface::~KexiFormDataItemInterface() { delete m_displayParametersForEnteredValue; delete m_displayParametersForDefaultValue; } void KexiFormDataItemInterface::undoChanges() { // m_disable_signalValueChanged = true; setValueInternal(QString(), false); // m_disable_signalValueChanged = false; } KDbField* KexiFormDataItemInterface::field() const { return m_columnInfo ? m_columnInfo->field() : nullptr; } void KexiFormDataItemInterface::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) { m_displayDefaultValue = displayDefaultValue; if (!m_displayParametersForDefaultValue) { m_displayParametersForEnteredValue = new KexiDisplayUtils::DisplayParameters(widget); m_displayParametersForDefaultValue = new KexiDisplayUtils::DisplayParameters(); - KexiDisplayUtils::initDisplayForDefaultValue(*m_displayParametersForDefaultValue, widget); + KexiDisplayUtils::initDisplayForDefaultValue(m_displayParametersForDefaultValue, widget); } } #if 0 void KexiFormDataItemInterface::cancelEditor() { QWidget *parentWidget = dynamic_cast(this)->parentWidget(); KexiFormScrollView* view = KDbUtils::findParent(parentWidget); if (view) view->cancelEditor(); } #endif void KexiFormDataItemInterface::cancelEditor() { QWidget *thisWidget = dynamic_cast(this); if (!thisWidget) { return; } QWidget *parentWidget = thisWidget->parentWidget(); KexiDataAwareObjectInterface *dataAwareObject = KexiUtils::findParentByType(parentWidget); if (dataAwareObject) dataAwareObject->cancelEditor(); } void KexiFormDataItemInterface::selectAllOnFocusIfNeeded() { moveCursorToEnd(); selectAll(); } diff --git a/src/widget/tableview/KexiTableScrollArea.cpp b/src/widget/tableview/KexiTableScrollArea.cpp index 23ecc4800..73b18f5bd 100644 --- a/src/widget/tableview/KexiTableScrollArea.cpp +++ b/src/widget/tableview/KexiTableScrollArea.cpp @@ -1,2496 +1,2496 @@ /* This file is part of the KDE project Copyright (C) 2002 Till Busch Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2016 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. Original Author: Till Busch Original Project: buX (www.bux.at) */ #include "KexiTableScrollArea.h" #include "KexiTableScrollArea_p.h" #include "KexiTableScrollAreaWidget.h" #include "KexiTableScrollAreaHeader.h" #include "KexiTableScrollAreaHeaderModel.h" #include "kexitableedit.h" #include #include #include "kexicelleditorfactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KEXI_TABLE_PRINT_SUPPORT #include #endif //#define KEXITABLEVIEW_DEBUG //#define KEXITABLEVIEW_DEBUG_PAINT const int MINIMUM_ROW_HEIGHT = 17; KexiTableScrollArea::Appearance::Appearance(QWidget *widget) { //set defaults if (qApp) { baseColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color()/*QPalette::Base*/; textColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color()/*QPalette::Base*/; QStyleOptionViewItemV4 option; option.initFrom(widget); const int gridHint = widget->style()->styleHint(QStyle::SH_Table_GridLineColor, &option, widget); gridColor = static_cast(gridHint); emptyAreaColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color()/*QPalette::Base*/; alternateBaseColor = widget->palette().color(QPalette::AlternateBase); recordHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, baseColor, 34, 66); recordMouseOverHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, baseColor, 10, 90); recordMouseOverAlternateHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, alternateBaseColor, 10, 90); recordHighlightingTextColor = textColor; recordMouseOverHighlightingTextColor = textColor; } backgroundAltering = true; recordMouseOverHighlightingEnabled = true; recordHighlightingEnabled = true; persistentSelections = true; navigatorEnabled = true; fullRecordSelection = false; verticalGridEnabled = true; horizontalGridEnabled = !backgroundAltering || baseColor == alternateBaseColor; } //----------------------------------------- //! @todo KEXI3 KexiTableViewCellToolTip /* TODO KexiTableViewCellToolTip::KexiTableViewCellToolTip( KexiTableView * tableView ) : QToolTip() , m_tableView(tableView) { } KexiTableViewCellToolTip::~KexiTableViewCellToolTip() { } void KexiTableViewCellToolTip::maybeTip( const QPoint & p ) { const QPoint cp( m_tableView->viewportToContents( p ) ); const int row = m_tableView->recordAt( cp.y(), true ); const int col = m_tableView->columnAt( cp.x() ); //show tooltip if needed if (col>=0 && row>=0) { KexiTableEdit *editor = m_tableView->tableEditorWidget( col ); const bool insertRowSelected = m_tableView->isInsertingEnabled() && row==m_tableView->rowCount(); KDbRecordData *data = insertRowSelected ? m_tableView->m_insertItem : m_tableView->itemAt( row ); if (editor && record && (col < (int)record->count())) { int w = m_tableView->columnWidth( col ); int h = m_tableView->rowHeight(); int x = 0; int y_offset = 0; int align = SingleLine | AlignVCenter; QString txtValue; QVariant cellValue; KDbTableViewColumn *tvcol = m_tableView->column(col); if (!m_tableView->getVisibleLookupValue(cellValue, editor, record, tvcol)) cellValue = insertRowSelected ? editor->displayedField()->defaultValue() : record->at(col); //display default value if available const bool focused = m_tableView->selectedRecord() == record && col == m_tableView->currentColumn(); editor->setupContents( 0, focused, cellValue, txtValue, align, x, y_offset, w, h ); QRect realRect(m_tableView->columnPos(col)-m_tableView->horizontalScrollBar()->value(), m_tableView->recordPos(row)-m_tableView->verticalScrollBar()->value(), w, h); if (editor->showToolTipIfNeeded( txtValue.isEmpty() ? record->at(col) : QVariant(txtValue), realRect, m_tableView->fontMetrics(), focused)) { QString squeezedTxtValue; if (txtValue.length() > 50) squeezedTxtValue = txtValue.left(100) + "..."; else squeezedTxtValue = txtValue; tip( realRect, squeezedTxtValue ); } } } } */ //TODO //----------------------------------------- KexiTableScrollArea::KexiTableScrollArea(KDbTableViewData* data, QWidget* parent) : QScrollArea(parent) , KexiRecordNavigatorHandler() , KexiSharedActionClient() , KexiDataAwareObjectInterface() , d(new Private(this)) { setAttribute(Qt::WA_StaticContents, true); setAttribute(Qt::WA_CustomWhatsThis, true); d->scrollAreaWidget = new KexiTableScrollAreaWidget(this); setWidget(d->scrollAreaWidget); m_data = new KDbTableViewData(); //to prevent crash because m_data==0 m_owner = true; //-this will be deleted if needed viewport()->setFocusPolicy(Qt::WheelFocus); setFocusPolicy(Qt::WheelFocus); //<--- !!!!! important (was NoFocus), // otherwise QApplication::setActiveWindow() won't activate // this widget when needed! viewport()->installEventFilter(this); d->scrollAreaWidget->installEventFilter(this); d->diagonalGrayPattern = QBrush(d->appearance.gridColor, Qt::BDiagPattern); setLineWidth(1); horizontalScrollBar()->installEventFilter(this); //context menu m_contextMenu = new QMenu(this); m_contextMenu->setObjectName("m_contextMenu"); //! \todo replace lineedit with table_field icon //setContextMenuTitle(KexiIcon("lineedit"), xi18n("Record")); // the default // cannot display anything here - most actions in the context menu // are related to a single cell (Cut, Copy..) and others to entire row (Delete Row): setContextMenuEnabled(false); d->pUpdateTimer = new QTimer(this); d->pUpdateTimer->setSingleShot(true); // Create headers d->headerModel = new KexiTableScrollAreaHeaderModel(this); d->horizontalHeader = new KexiTableScrollAreaHeader(Qt::Horizontal, this); d->horizontalHeader->setObjectName("horizontalHeader"); d->horizontalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); d->verticalHeader = new KexiTableScrollAreaHeader(Qt::Vertical, this); d->verticalHeader->setObjectName("verticalHeader"); d->verticalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); setupNavigator(); if (data) { setData(data); } setAcceptDrops(true); viewport()->setAcceptDrops(true); // Connect header, table and scrollbars connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), d->horizontalHeader, SLOT(setOffset(int))); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), d->verticalHeader, SLOT(setOffset(int))); connect(d->horizontalHeader, SIGNAL(sectionResized(int,int,int)), this, SLOT(slotColumnWidthChanged(int,int,int))); connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotSectionHandleDoubleClicked(int))); connect(d->horizontalHeader, SIGNAL(sectionClicked(int)), this, SLOT(sortColumnInternal(int))); connect(d->pUpdateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate())); setAppearance(d->appearance); //refresh d->setSpreadSheetMode(false); //! @todo KEXI3 d->cellToolTip = new KexiTableViewCellToolTip(this); } KexiTableScrollArea::~KexiTableScrollArea() { cancelRecordEditing(); KDbTableViewData *data = m_data; m_data = 0; if (m_owner) { if (data) data->deleteLater(); } delete d; } void KexiTableScrollArea::clearVariables() { KexiDataAwareObjectInterface::clearVariables(); d->clearVariables(); } void KexiTableScrollArea::setupNavigator() { m_navPanel = new KexiRecordNavigator(*this, this); navPanelWidget()->setObjectName("navPanel"); m_navPanel->setRecordHandler(this); } void KexiTableScrollArea::initDataContents() { updateWidgetContentsSize(); KexiDataAwareObjectInterface::initDataContents(); m_navPanel->showEditingIndicator(false); } void KexiTableScrollArea::updateWidgetContentsSize() { updateScrollAreaWidgetSize(); d->horizontalHeader->setFixedSize(d->horizontalHeader->sizeHint()); d->verticalHeader->setFixedSize(d->verticalHeader->sizeHint()); //qDebug() << d->horizontalHeader->sizeHint() << d->verticalHeader->sizeHint(); //qDebug() << d->horizontalHeader->geometry() << d->verticalHeader->geometry(); } void KexiTableScrollArea::updateScrollAreaWidgetSize() { QSize s(tableSize()); const int colOffset = d->columnOffset(); s.setWidth(qMax(s.width() + colOffset, viewport()->width())); s.setHeight(qMax(s.height() + colOffset, viewport()->height())); d->scrollAreaWidget->resize(s); } void KexiTableScrollArea::updateVerticalHeaderSection(int row) { d->verticalHeader->updateSection(row); } void KexiTableScrollArea::slotRecordsDeleted(const QList &records) { viewport()->repaint(); updateWidgetContentsSize(); setCursorPosition(qMax(0, (int)m_curRecord - (int)records.count()), -1, ForceSetCursorPosition); } void KexiTableScrollArea::setFont(const QFont &font) { QScrollArea::setFont(font); #ifdef Q_OS_WIN //! @todo KEXI3 WIN32 test this d->recordHeight = fontMetrics().lineSpacing() + 4; #else d->recordHeight = fontMetrics().lineSpacing() + 1; #endif if (d->appearance.fullRecordSelection) { d->recordHeight -= 1; } if (d->recordHeight < MINIMUM_ROW_HEIGHT) { d->recordHeight = MINIMUM_ROW_HEIGHT; } - KexiDisplayUtils::initDisplayForAutonumberSign(d->autonumberSignDisplayParameters, this); - KexiDisplayUtils::initDisplayForDefaultValue(d->defaultValueDisplayParameters, this); + KexiDisplayUtils::initDisplayForAutonumberSign(&d->autonumberSignDisplayParameters, this); + KexiDisplayUtils::initDisplayForDefaultValue(&d->defaultValueDisplayParameters, this); update(); } void KexiTableScrollArea::updateAllVisibleRecordsBelow(int record) { //get last visible row // int r = recordAt(viewport()->height() + verticalScrollBar()->value()); // if (r == -1) { // r = rowCount() + 1 + (isInsertingEnabled() ? 1 : 0); // } //update all visible rows below int leftcol = d->horizontalHeader->visualIndexAt(d->horizontalHeader->offset()); d->scrollAreaWidget->update(columnPos(leftcol), recordPos(record), viewport()->width(), viewport()->height() - (recordPos(record) - verticalScrollBar()->value())); } void KexiTableScrollArea::clearColumnsInternal(bool /*repaint*/) { } void KexiTableScrollArea::slotUpdate() { // qDebug() << m_navPanel; updateScrollAreaWidgetSize(); d->scrollAreaWidget->update(); updateWidgetContentsSize(); } Qt::SortOrder KexiTableScrollArea::currentLocalSortOrder() const { return d->horizontalHeader->sortIndicatorOrder(); } void KexiTableScrollArea::setLocalSortOrder(int column, Qt::SortOrder order) { d->horizontalHeader->setSortIndicator(column, order); } int KexiTableScrollArea::currentLocalSortColumn() const { return d->horizontalHeader->sortIndicatorSection(); } void KexiTableScrollArea::updateGUIAfterSorting(int previousRow) { int prevRowVisibleOffset = recordPos(previousRow) - verticalScrollBar()->value(); verticalScrollBar()->setValue(recordPos(m_curRecord) - prevRowVisibleOffset); d->scrollAreaWidget->update(); selectCellInternal(m_curRecord, m_curColumn); } QSizePolicy KexiTableScrollArea::sizePolicy() const { // this widget is expandable return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } QSize KexiTableScrollArea::sizeHint() const { QSize ts = tableSize(); int w = qMax(ts.width() + leftMargin() + verticalScrollBar()->sizeHint().width() + 2 * 2, (navPanelWidgetVisible() ? navPanelWidget()->width() : 0)); int h = qMax(ts.height() + topMargin() + horizontalScrollBar()->sizeHint().height(), minimumSizeHint().height()); w = qMin(w, qApp->desktop()->availableGeometry(this).width() * 3 / 4); //stretch h = qMin(h, qApp->desktop()->availableGeometry(this).height() * 3 / 4); //stretch #ifdef KEXITABLEVIEW_DEBUG qDebug() << w << h; #endif return QSize(w, h); } QSize KexiTableScrollArea::minimumSizeHint() const { return QSize( leftMargin() + ((columnCount() > 0) ? columnWidth(0) : KEXI_DEFAULT_DATA_COLUMN_WIDTH) + 2*2, d->recordHeight*5 / 2 + topMargin() + (navPanelWidgetVisible() ? navPanelWidget()->height() : 0) ); } QRect KexiTableScrollArea::viewportGeometry() const { return viewport()->geometry(); } //internal inline void KexiTableScrollArea::paintRow(KDbRecordData *data, QPainter *pb, int r, int rowp, int cx, int cy, int colfirst, int collast, int maxwc) { Q_UNUSED(cx); Q_UNUSED(cy); if (!data) return; //qDebug() << "r" << r << "rowp" << rowp << "cx" << cx << "cy" << cy // << "colfirst" << colfirst << "collast" << collast << "maxwc" << maxwc; // Go through the columns in the row r // if we know from where to where, go through [colfirst, collast], // else go through all of them if (colfirst == -1) colfirst = 0; if (collast == -1) collast = columnCount() - 1; int transly = rowp; if (d->appearance.recordHighlightingEnabled && r == m_curRecord && !d->appearance.fullRecordSelection) { pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordHighlightingColor); } else if (d->appearance.recordMouseOverHighlightingEnabled && r == d->highlightedRecord) { if (d->appearance.backgroundAltering && (r % 2 != 0)) pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordMouseOverAlternateHighlightingColor); else pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordMouseOverHighlightingColor); } else { if (d->appearance.backgroundAltering && (r % 2 != 0)) pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.alternateBaseColor); else pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.baseColor); } for (int c = colfirst; c <= collast; c++) { // get position and width of column c int colp = columnPos(c); if (colp == -1) continue; //invisible column? int colw = columnWidth(c); // qDebug() << "c:" << c << "colp:" << colp << "cx:" << cx << "contentsX():" << horizontalScrollBar()->value() << "colw:" << colw; //(js #2010-01-05) breaks rendering: int translx = colp - cx + horizontalScrollBar()->value(); int translx = colp; // Translate painter and draw the cell const QTransform oldTr( pb->worldTransform() ); pb->translate(translx, transly); paintCell(pb, data, r, c, QRect(colp, rowp, colw, d->recordHeight)); pb->setWorldTransform(oldTr); } if (m_dragIndicatorLine >= 0) { int y_line = -1; if (r == (recordCount() - 1) && m_dragIndicatorLine == recordCount()) { y_line = transly + d->recordHeight - 3; //draw at last line } if (m_dragIndicatorLine == r) { y_line = transly; } if (y_line >= 0) { if (!d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand = new QRubberBand(QRubberBand::Line, viewport()); } d->dragIndicatorRubberBand->setGeometry(0, y_line, maxwc, 3); d->dragIndicatorRubberBand->show(); } else { if (d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand->hide(); } } } else { if (d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand->hide(); } } } void KexiTableScrollArea::drawContents(QPainter *p) { int cx = p->clipBoundingRect().x(); int cy = p->clipBoundingRect().y(); int cw = p->clipBoundingRect().width(); int ch = p->clipBoundingRect().height(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << "disable" << d->disableDrawContents << "cx" << cx << "cy" << cy << "cw" << cw << "ch" << ch << "contentsRect" << contentsRect() << "geo" << geometry(); #endif if (d->disableDrawContents) return; bool paintOnlyInsertRow = false; bool inserting = isInsertingEnabled(); bool plus1row = false; //true if we should show 'inserting' row at the end int colfirst = columnNumberAt(cx); int rowfirst = recordNumberAt(cy); int collast = columnNumberAt(cx + cw - 1); int rowlast = recordNumberAt(cy + ch - 1); if (rowfirst == -1 && (cy / d->recordHeight) == recordCount()) { // make the insert row paint too when requested #ifdef KEXITABLEVIEW_DEBUG qDebug() << "rowfirst == -1 && (cy / d->rowHeight) == rowCount()"; #endif rowfirst = m_data->count(); rowlast = rowfirst; paintOnlyInsertRow = true; plus1row = inserting; } /* qDebug() << "cx" << cx << "cy" << cy << "cw" << cw << "ch" << ch << "colfirst" << colfirst << "rowfirst" << rowfirst << "collast" << collast << "rowlast" << rowlast;*/ if (rowlast == -1) { rowlast = recordCount() - 1; plus1row = inserting; if (rowfirst == -1) { if (recordNumberAt(cy - d->recordHeight) != -1) { //paintOnlyInsertRow = true; // qDebug() << "-- paintOnlyInsertRow --"; } } } // qDebug() << "rowfirst="<fillRect(cx, cy, cw, ch, d->appearance.baseColor); int rowp = 0; int r = 0; if (paintOnlyInsertRow) { r = recordCount(); rowp = recordPos(r); // 'insert' row's position } else { if (rowfirst >= 0) { QList::ConstIterator it(m_data->constBegin()); it += rowfirst;//move to 1st row rowp = recordPos(rowfirst); // row position for (r = rowfirst;r <= rowlast; r++, ++it, rowp += d->recordHeight) { // qDebug() << *it; paintRow(*it, p, r, rowp, cx, cy, colfirst, collast, maxwc); } } } if (plus1row && rowfirst >= 0) { //additional - 'insert' row paintRow(m_insertRecord, p, r, rowp, cx, cy, colfirst, collast, maxwc); } paintEmptyArea(p, cx, cy, cw, ch); } bool KexiTableScrollArea::isDefaultValueDisplayed(KDbRecordData *data, int col, QVariant* value) { const bool cursorAtInsertRowOrEditingNewRow = (data == m_insertRecord || (m_newRecordEditing && m_currentRecord == data)); KDbTableViewColumn *tvcol; if (cursorAtInsertRowOrEditingNewRow && (tvcol = m_data->column(col)) && hasDefaultValueAt(*tvcol) && !tvcol->field()->isAutoIncrement()) { if (value) *value = tvcol->field()->defaultValue(); return true; } return false; } void KexiTableScrollArea::paintCell(QPainter* p, KDbRecordData *data, int record, int column, const QRect &cr, bool print) { Q_UNUSED(print); //qDebug() << "col/row:" << col << row << "rect:" << cr; p->save(); int w = cr.width(); int h = cr.height(); int x2 = w - 1; int y2 = h - 1; // Draw our lines QPen pen(p->pen()); if (d->appearance.horizontalGridEnabled) { p->setPen(d->appearance.gridColor); p->drawLine(0, y2, x2, y2); // bottom } if (d->appearance.verticalGridEnabled) { p->setPen(d->appearance.gridColor); p->drawLine(x2, 0, x2, y2); // right } p->setPen(pen); if (m_editor && record == m_curRecord && column == m_curColumn //don't paint contents of edited cell && m_editor->hasFocusableWidget() //..if it's visible ) { p->restore(); return; } KexiTableEdit *edit = tableEditorWidget(column, /*ignoreMissingEditor=*/true); int x = edit ? edit->leftMargin() : 0; int y_offset = 0; int align = Qt::TextSingleLine | Qt::AlignVCenter; QString txt; //text to draw if (data == m_insertRecord) { //qDebug() << "we're at INSERT row..."; } KDbTableViewColumn *tvcol = m_data->column(column); QVariant cellValue; if (column < (int)data->count()) { if (m_currentRecord == data) { if (m_editor && record == m_curRecord && column == m_curColumn && !m_editor->hasFocusableWidget()) { //we're over editing cell and the editor has no widget // - we're displaying internal values, not buffered cellValue = m_editor->value(); } else { //we're displaying values from edit buffer, if available // this assignment will also get default value if there's no actual value set cellValue = *bufferedValueAt(record, column); } } else { cellValue = data->at(column); } } bool defaultValueDisplayed = isDefaultValueDisplayed(data, column); if (data == m_insertRecord && cellValue.isNull()) { if (!tvcol->field()->isAutoIncrement() && !tvcol->field()->defaultValue().isNull()) { //display default value in the "insert record", if available //(but not if there is autoincrement flag set) cellValue = tvcol->field()->defaultValue(); defaultValueDisplayed = true; } } const bool columnReadOnly = tvcol->isReadOnly(); const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted = d->appearance.recordHighlightingEnabled && !d->appearance.persistentSelections && m_curRecord >= 0 && record != m_curRecord; // setup default pen QPen defaultPen; const bool usesSelectedTextColor = edit && edit->usesSelectedTextColor(); if (defaultValueDisplayed){ if (column == m_curColumn && record == m_curRecord && usesSelectedTextColor) defaultPen = d->defaultValueDisplayParameters.selectedTextColor; else defaultPen = d->defaultValueDisplayParameters.textColor; } else if (d->appearance.fullRecordSelection && (record == d->highlightedRecord || (record == m_curRecord && d->highlightedRecord == -1)) && usesSelectedTextColor) { defaultPen = d->appearance.recordHighlightingTextColor; //special case: highlighted record } else if (d->appearance.fullRecordSelection && record == m_curRecord && usesSelectedTextColor) { defaultPen = d->appearance.textColor; //special case for full record selection } else if ( m_currentRecord == data && column == m_curColumn && !columnReadOnly && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = palette().color(QPalette::HighlightedText); //selected text } else if ( d->appearance.recordHighlightingEnabled && record == m_curRecord && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = d->appearance.recordHighlightingTextColor; } else if ( d->appearance.recordMouseOverHighlightingEnabled && record == d->highlightedRecord && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = d->appearance.recordMouseOverHighlightingTextColor; } else { defaultPen = d->appearance.textColor; } if (edit) { if (defaultValueDisplayed) p->setFont(d->defaultValueDisplayParameters.font); p->setPen(defaultPen); //get visible lookup value if available getVisibleLookupValue(cellValue, edit, data, tvcol); /*qDebug() << "edit->setupContents()" << (m_currentRecord == record && col == m_curColumn) << cellValue << txt << align << x << y_offset << w << h;*/ edit->setupContents(p, m_currentRecord == data && column == m_curColumn, cellValue, txt, align, x, y_offset, w, h); } if (!d->appearance.horizontalGridEnabled) y_offset++; //correction because we're not drawing cell borders if (d->appearance.fullRecordSelection) { } if (m_currentRecord == data && (column == m_curColumn || d->appearance.fullRecordSelection)) { if (edit && ( (d->appearance.recordHighlightingEnabled && !d->appearance.fullRecordSelection) || (record == m_curRecord && d->highlightedRecord == -1 && d->appearance.fullRecordSelection)) ) { edit->paintSelectionBackground(p, isEnabled(), txt, align, x, y_offset, w, h, isEnabled() ? palette().color(QPalette::Highlight) : QColor(200, 200, 200),//d->grayColor, p->fontMetrics(), columnReadOnly, d->appearance.fullRecordSelection); } } if (!edit) { p->fillRect(0, 0, x2, y2, d->diagonalGrayPattern); } // If we are in the focus cell, draw indication if ( m_currentRecord == data && column == m_curColumn //js: && !d->recordIndicator) && !d->appearance.fullRecordSelection) { // qDebug() << ">>> CURRENT CELL ("<field()->isAutoIncrement()) { // "autonumber" column KexiDisplayUtils::paintAutonumberSign(d->autonumberSignDisplayParameters, p, x, y_offset, w - x - x - ((align & Qt::AlignLeft) ? 2 : 0), h, (Qt::Alignment)align); } } // draw text if (!txt.isEmpty()) { if (defaultValueDisplayed) p->setFont(d->defaultValueDisplayParameters.font); p->setPen(defaultPen); p->drawText(x, y_offset, w - (x + x) - ((align & Qt::AlignLeft) ? 2 : 0)/*right space*/, h, align, txt); } #ifdef KEXITABLEVIEW_DEBUG_PAINT p->setPen(QPen(QColor(255, 0, 0, 150), 1, Qt::DashLine)); p->drawRect(x, y_offset, w - 1, h - 1); qDebug() << cellValue << "x:" << x << "y:" << y_offset << "w:" << w << "h:" << h; #endif p->restore(); } QPoint KexiTableScrollArea::contentsToViewport2(const QPoint &p) { return QPoint(p.x() - horizontalScrollBar()->value(), p.y() - verticalScrollBar()->value()); } void KexiTableScrollArea::contentsToViewport2(int x, int y, int& vx, int& vy) { const QPoint v = contentsToViewport2(QPoint(x, y)); vx = v.x(); vy = v.y(); } QPoint KexiTableScrollArea::viewportToContents2(const QPoint& vp) { return QPoint(vp.x() + horizontalScrollBar()->value(), vp.y() + verticalScrollBar()->value()); } void KexiTableScrollArea::paintEmptyArea(QPainter *p, int cx, int cy, int cw, int ch) { //qDebug() << cx << cy << cw << ch; // Regions work with shorts, so avoid an overflow and adjust the // table size to the visible size QSize ts(tableSize()); //qDebug() << ts; /* qDebug() << QString(" (cx:%1 cy:%2 cw:%3 ch:%4)") .arg(cx).arg(cy).arg(cw).arg(ch); qDebug() << QString(" (w:%3 h:%4)") .arg(ts.width()).arg(ts.height());*/ // Region of the rect we should draw, calculated in viewport // coordinates, as a region can't handle bigger coordinates contentsToViewport2(cx, cy, cx, cy); QRegion reg(QRect(cx, cy, cw, ch)); //qDebug() << "---cy-- " << verticalScrollBar()->value(); // Subtract the table from it reg = reg.subtracted(QRect(QPoint(0, 0), ts - QSize(0, + verticalScrollBar()->value()))); // And draw the rectangles (transformed inc contents coordinates as needed) const QVector rects(reg.rects()); foreach(const QRect& rect, rects) { QRect realRect(viewportToContents2(rect.topLeft()), rect.size()); // qDebug() << QString("- pEA: p->fillRect(x:%1 y:%2 w:%3 h:%4)") // .arg(rect.x()).arg(rect.y()) // .arg(rect.width()).arg(rect.height()) // << viewportGeometry(); p->fillRect(realRect, d->appearance.emptyAreaColor); } } void KexiTableScrollArea::contentsMouseDoubleClickEvent(QMouseEvent *e) { // qDebug(); m_contentsMousePressEvent_dblClick = true; contentsMousePressEvent(e); m_contentsMousePressEvent_dblClick = false; if (m_currentRecord) { if (d->editOnDoubleClick && columnEditable(m_curColumn) && columnType(m_curColumn) != KDbField::Boolean) { KexiTableEdit *edit = tableEditorWidget(m_curColumn, /*ignoreMissingEditor=*/true); if (edit && edit->handleDoubleClick()) { //nothing to do: editors like BLOB editor has custom handling of double clicking } else { startEditCurrentCell(); // createEditor(m_curRecord, m_curColumn, QString()); } } emit itemDblClicked(m_currentRecord, m_curRecord, m_curColumn); } } void KexiTableScrollArea::contentsMousePressEvent(QMouseEvent* e) { setFocus(); if (m_data->isEmpty() && !isInsertingEnabled()) { return; } //qDebug() << e->pos(); const int x = e->pos().x(); if (columnNumberAt(x) == -1) { //outside a column return; } if (!d->moveCursorOnMouseRelease) { if (!handleContentsMousePressOrRelease(e, false)) return; } // qDebug()<< "by now the current items should be set, if not -> error + crash"; if (e->button() == Qt::RightButton) { showContextMenu(e->globalPos()); } else if (e->button() == Qt::LeftButton) { if (columnType(m_curColumn) == KDbField::Boolean && columnEditable(m_curColumn)) { //only accept clicking on the [x] rect (copied from KexiBoolTableEdit::setupContents()) int s = qMax(d->recordHeight - 5, 12); s = qMin(d->recordHeight - 3, s); s = qMin(columnWidth(m_curColumn) - 3, s); //avoid too large box const QRect r( columnPos(m_curColumn) + qMax(columnWidth(m_curColumn) / 2 - s / 2, 0), recordPos(m_curRecord) + d->recordHeight / 2 - s / 2 /*- 1*/, s, s); //qDebug() << r; if (r.contains(e->pos())) { // qDebug() << "e->x:" << e->x() << " e->y:" << e->y() << " " << recordPos(m_curRecord) << // " " << columnPos(m_curColumn); boolToggled(); } } //! @todo #if 0 else if (columnType(m_curColumn) == QVariant::StringList && columnEditable(m_curColumn)) { createEditor(m_curRecord, m_curColumn); } #endif } } void KexiTableScrollArea::contentsMouseReleaseEvent(QMouseEvent* e) { if (m_data->count() == 0 && !isInsertingEnabled()) return; if (d->moveCursorOnMouseRelease) handleContentsMousePressOrRelease(e, true); int col = columnNumberAt(e->pos().x()); int row = recordNumberAt(e->pos().y()); if (!m_currentRecord || col == -1 || row == -1 || col != m_curColumn || row != m_curRecord)//outside a current cell return; emit itemMouseReleased(m_currentRecord, m_curRecord, m_curColumn); } bool KexiTableScrollArea::handleContentsMousePressOrRelease(QMouseEvent* e, bool release) { Q_UNUSED(release); //qDebug() << "oldRow=" << m_curRecord << " oldCol=" << m_curColumn; int newrow, newcol; //compute clicked row nr const int x = e->pos().x(); if (isInsertingEnabled()) { if (recordNumberAt(e->pos().y()) == -1) { newrow = recordNumberAt(e->pos().y() - d->recordHeight); if (newrow == -1 && m_data->count() > 0) { return false; } newrow++; qDebug() << "Clicked just on 'insert' record."; } else { // get new focus cell newrow = recordNumberAt(e->pos().y()); } } else { if (recordNumberAt(e->pos().y()) == -1 || columnNumberAt(x) == -1) { return false; //clicked outside a grid } // get new focus cell newrow = recordNumberAt(e->pos().y()); } newcol = columnNumberAt(x); if (e->button() != Qt::NoButton) { setCursorPosition(newrow, newcol); } return true; } void KexiTableScrollArea::showContextMenu(const QPoint& _pos) { if (!d->contextMenuEnabled || m_contextMenu->isEmpty()) return; QPoint pos(_pos); if (pos == QPoint(-1, -1)) { pos = viewport()->mapToGlobal(QPoint(columnPos(m_curColumn), recordPos(m_curRecord) + d->recordHeight)); } //show own context menu if configured selectRecord(m_curRecord); m_contextMenu->exec(pos); } void KexiTableScrollArea::contentsMouseMoveEvent(QMouseEvent *e) { int row; const int col = columnNumberAt(e->x()); if (col < 0) { row = -1; } else { row = recordNumberAt(e->y(), true /*ignoreEnd*/); if (row > (recordCount() - 1 + (isInsertingEnabled() ? 1 : 0))) row = -1; //no row to paint } // qDebug() << " row="<modifiers() == Qt::ShiftModifier; if (action_name == "edit_delete_row") return k == Qt::Key_Delete && e->modifiers() == Qt::ControlModifier; if (action_name == "edit_delete") return k == Qt::Key_Delete && e->modifiers() == Qt::NoModifier; if (action_name == "edit_edititem") return k == Qt::Key_F2 && e->modifiers() == Qt::NoModifier; if (action_name == "edit_insert_empty_row") return k == Qt::Key_Insert && e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier); return false; } void KexiTableScrollArea::contentsContextMenuEvent(QContextMenuEvent* e) { const bool nobtn = e->modifiers() == Qt::NoModifier; if (nobtn && e->reason() == QContextMenuEvent::Keyboard) { showContextMenu(); } } void KexiTableScrollArea::keyPressEvent(QKeyEvent* e) { #ifdef KEXITABLEVIEW_DEBUG qDebug() << e; #endif if (!hasData()) return; // qDebug() << "key=" <key() << " txt=" <text(); const int k = e->key(); const bool ro = isReadOnly(); QWidget *w = focusWidget(); if (!w || (w != viewport() && w != this && (!m_editor || !KDbUtils::hasParent(dynamic_cast(m_editor), w)))) { //don't process stranger's events e->ignore(); return; } if (d->skipKeyPress) { d->skipKeyPress = false; e->ignore(); return; } if (m_currentRecord == 0 && (m_data->count() > 0 || isInsertingEnabled())) { setCursorPosition(0, 0); } else if (m_data->count() == 0 && !isInsertingEnabled()) { e->accept(); return; } if (m_editor) {// if a cell is edited, do some special stuff if (k == Qt::Key_Escape) { cancelEditor(); emit updateSaveCancelActions(); e->accept(); return; } else if (k == Qt::Key_Return || k == Qt::Key_Enter) { if (columnType(m_curColumn) == KDbField::Boolean) { boolToggled(); } else { acceptEditor(); } e->accept(); return; } } else if (recordEditing() >= 0) {// if a row is in edit mode, do some special stuff if (shortCutPressed(e, "data_save_row")) { qDebug() << "shortCutPressed!!!"; acceptRecordEditing(); return; } } if (k == Qt::Key_Return || k == Qt::Key_Enter) { emit itemReturnPressed(m_currentRecord, m_curRecord, m_curColumn); } int curRow = m_curRecord; int curCol = m_curColumn; const bool nobtn = e->modifiers() == Qt::NoModifier; bool printable = false; //check shared shortcuts if (!ro) { if (shortCutPressed(e, "edit_delete_row")) { deleteCurrentRecord(); e->accept(); return; } else if (shortCutPressed(e, "edit_delete")) { deleteAndStartEditCurrentCell(); e->accept(); return; } else if (shortCutPressed(e, "edit_insert_empty_row")) { insertEmptyRecord(); e->accept(); return; } } if (k == Qt::Key_Shift || k == Qt::Key_Alt || k == Qt::Key_Control || k == Qt::Key_Meta) { e->ignore(); } else if (KexiDataAwareObjectInterface::handleKeyPress( e, &curRow, &curCol, d->appearance.fullRecordSelection)) { if (e->isAccepted()) return; } else if (k == Qt::Key_Backspace && nobtn) { if (!ro && columnType(curCol) != KDbField::Boolean && columnEditable(curCol)) { const CreateEditorFlags flags = DefaultCreateEditorFlags | ReplaceOldValue; createEditor(curRow, curCol, QString(), flags); } } else if (k == Qt::Key_Space) { if (nobtn && !ro && columnEditable(curCol)) { if (columnType(curCol) == KDbField::Boolean) { boolToggled(); } else printable = true; //just space key } } else if (k == Qt::Key_Escape) { if (nobtn && recordEditing() >= 0) { cancelRecordEditing(); return; } } else { //others: if ((nobtn && k == Qt::Key_Tab) || k == Qt::Key_Right) { //! \todo add option for stopping at 1st column for Qt::Key_left //tab if (acceptEditor()) { if (curCol == (columnCount() - 1)) { if (curRow < (recordCount() - 1 + (isInsertingEnabled() ? 1 : 0))) {//skip to next row curRow++; curCol = 0; } } else curCol++; } } else if ((e->modifiers() == Qt::ShiftModifier && k == Qt::Key_Tab) || (nobtn && k == Qt::Key_Backtab) || (e->modifiers() == Qt::ShiftModifier && k == Qt::Key_Backtab) || k == Qt::Key_Left ) { //! \todo add option for stopping at last column //backward tab if (acceptEditor()) { if (curCol == 0) { if (curRow > 0) {//skip to previous row curRow--; curCol = columnCount() - 1; } } else curCol--; } } else { KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit && edit->handleKeyPress(e, m_editor == edit)) { //try to handle the event @ editor's level e->accept(); return; } else if (nobtn && (k == Qt::Key_Enter || k == Qt::Key_Return || shortCutPressed(e, "edit_edititem"))) { //this condition is moved after handleKeyPress() to allow to everride enter key as well startEditOrToggleValue(); } else { qDebug() << "default"; if (e->text().isEmpty() || !e->text()[0].isPrint()) { qDebug() << "NOT PRINTABLE: 0x0" << QString("%1").arg(k, 0, 16); // e->ignore(); QScrollArea::keyPressEvent(e); return; } printable = true; } } } //finally: we've printable char: if (printable && !ro) { KDbTableViewColumn *tvcol = m_data->column(curCol); if (tvcol->acceptsFirstChar(e->text()[0])) { qDebug() << "ev pressed: acceptsFirstChar()==true"; const CreateEditorFlags flags = DefaultCreateEditorFlags | ReplaceOldValue; createEditor(curRow, curCol, e->text(), flags); } else { //! @todo show message "key not allowed eg. on a statusbar" qDebug() << "ev pressed: acceptsFirstChar()==false"; } } m_verticalScrollBarValueChanged_enabled = false; // if focus cell changes, repaint setCursorPosition(curRow, curCol, DontEnsureCursorVisibleIfPositionUnchanged); m_verticalScrollBarValueChanged_enabled = true; e->accept(); } void KexiTableScrollArea::emitSelected() { if (m_currentRecord) emit itemSelected(m_currentRecord); } int KexiTableScrollArea::recordsPerPage() const { return viewport()->height() / d->recordHeight; } KexiDataItemInterface *KexiTableScrollArea::editor(int col, bool ignoreMissingEditor) { if (!m_data || col < 0 || col >= columnCount()) return 0; KDbTableViewColumn *tvcol = m_data->column(col); //find the editor for this column KexiTableEdit *editor = d->editors.value(tvcol); if (editor) return editor; //not found: create editor = KexiCellEditorFactory::createEditor(*tvcol, d->scrollAreaWidget); if (!editor) {//create error! if (!ignoreMissingEditor) { //! @todo show error??? cancelRecordEditing(); } return 0; } editor->hide(); if (m_data->cursor() && m_data->cursor()->query()) editor->createInternalEditor(*m_data->cursor()->query()); connect(editor, SIGNAL(editRequested()), this, SLOT(slotEditRequested())); connect(editor, SIGNAL(cancelRequested()), this, SLOT(cancelEditor())); connect(editor, SIGNAL(acceptRequested()), this, SLOT(acceptEditor())); editor->resize(columnWidth(col), recordHeight()); editor->installEventFilter(this); if (editor->widget()) editor->widget()->installEventFilter(this); //store d->editors.insert(tvcol, editor); return editor; } KexiTableEdit* KexiTableScrollArea::tableEditorWidget(int col, bool ignoreMissingEditor) { return dynamic_cast(editor(col, ignoreMissingEditor)); } void KexiTableScrollArea::editorShowFocus(int row, int col) { Q_UNUSED(row); KexiDataItemInterface *edit = editor(col); if (edit) { //qDebug() << "IN"; QRect rect = cellGeometry(m_curRecord, m_curColumn); edit->showFocus(rect, isReadOnly() || m_data->column(col)->isReadOnly()); } } void KexiTableScrollArea::slotEditRequested() { createEditor(m_curRecord, m_curColumn); } void KexiTableScrollArea::reloadData() { KexiDataAwareObjectInterface::reloadData(); d->scrollAreaWidget->update(); } void KexiTableScrollArea::createEditor(int row, int col, const QString& addText, CreateEditorFlags flags) { //qDebug() << "addText:" << addText << "removeOld:" << removeOld; if (row < 0) { qWarning() << "ROW NOT SPECIFIED!" << row; return; } if (isReadOnly()) { qDebug() << "DATA IS READ ONLY!"; return; } if (m_data->column(col)->isReadOnly()) {//d->pColumnModes.at(d->numCols-1) & ColumnReadOnly) qDebug() << "COL IS READ ONLY!"; return; } if (recordEditing() >= 0 && row != recordEditing()) { if (!acceptRecordEditing()) { return; } } const bool startRecordEditing = recordEditing() == -1; //remember if we're starting row edit if (startRecordEditing) { //we're starting row editing session m_data->clearRecordEditBuffer(); setRecordEditing(row); //indicate on the vheader that we are editing: if (isInsertingEnabled() && row == recordCount()) { //we should know that we are in state "new record editing" m_newRecordEditing = true; KDbRecordData *insertItem = m_insertRecord; beginInsertItem(insertItem, row); //'insert' row editing: show another row after that: m_data->append(insertItem); //new empty 'inserting' item m_insertRecord = m_data->createItem(); endInsertItem(insertItem, row); updateWidgetContentsSize(); //refr. current and next row d->scrollAreaWidget->update(columnPos(col), recordPos(row), viewport()->width(), d->recordHeight*2); if (flags & EnsureCellVisible) { ensureVisible(columnPos(col), recordPos(row + 1) + d->recordHeight - 1, columnWidth(col), d->recordHeight); } d->verticalHeader->setOffset(verticalScrollBar()->value()); } d->verticalHeader->updateSection(row); } KexiTableEdit *editorWidget = tableEditorWidget(col); m_editor = editorWidget; if (!editorWidget) return; m_editor->setValue(*bufferedValueAt(row, col, !(flags & ReplaceOldValue)/*useDefaultValueIfPossible*/), addText, flags & ReplaceOldValue); if (m_editor->hasFocusableWidget()) { editorWidget->move(columnPos(col), recordPos(row)); editorWidget->resize(columnWidth(col), recordHeight()); editorWidget->show(); m_editor->setFocus(); } if (startRecordEditing) { m_navPanel->showEditingIndicator(true); //this will allow to enable 'next' btn //emit recordEditingStarted(row); } m_editor->installListener(this); } void KexiTableScrollArea::focusOutEvent(QFocusEvent* e) { KexiDataAwareObjectInterface::focusOutEvent(e); } bool KexiTableScrollArea::focusNextPrevChild(bool /*next*/) { return false; //special Tab/BackTab meaning } void KexiTableScrollArea::resizeEvent(QResizeEvent *e) { if (d->insideResizeEvent) return; d->insideResizeEvent = true; QScrollArea::resizeEvent(e); if ((viewport()->height() - e->size().height()) <= d->recordHeight) { slotUpdate(); triggerUpdate(); } d->insideResizeEvent = false; } void KexiTableScrollArea::showEvent(QShowEvent *e) { QScrollArea::showEvent(e); if (!d->maximizeColumnsWidthOnShow.isEmpty()) { maximizeColumnsWidth(d->maximizeColumnsWidthOnShow); d->maximizeColumnsWidthOnShow.clear(); } if (m_initDataContentsOnShow) { //full init m_initDataContentsOnShow = false; initDataContents(); } else { //just update size updateScrollAreaWidgetSize(); } updateGeometries(); //now we can ensure cell's visibility ( if there was such a call before show() ) if (d->ensureCellVisibleOnShow != QPoint(-17, -17)) { // because (-1, -1) means "current cell" ensureCellVisible(d->ensureCellVisibleOnShow.y(), d->ensureCellVisibleOnShow.x()); d->ensureCellVisibleOnShow = QPoint(-17, -17); //reset the flag } if (d->firstShowEvent) { ensureVisible(0, 0, 0, 0); // needed because for small geometries contents were moved 1/2 of row height up d->firstShowEvent = false; } updateViewportMargins(); } void KexiTableScrollArea::dragMoveEvent(QDragMoveEvent *e) { if (!hasData()) return; if (m_dropsAtRecordEnabled) { QPoint p = e->pos(); int row = recordNumberAt(p.y()); if ((p.y() % d->recordHeight) > (d->recordHeight*2 / 3)) { row++; } KDbRecordData *data = m_data->at(row); emit dragOverRecord(data, row, e); if (e->isAccepted()) { if (m_dragIndicatorLine >= 0 && m_dragIndicatorLine != row) { //erase old indicator updateRecord(m_dragIndicatorLine); } if (m_dragIndicatorLine != row) { m_dragIndicatorLine = row; updateRecord(m_dragIndicatorLine); } } else { if (m_dragIndicatorLine >= 0) { //erase old indicator updateRecord(m_dragIndicatorLine); } m_dragIndicatorLine = -1; } } else { e->accept(); } } void KexiTableScrollArea::dropEvent(QDropEvent *e) { if (!hasData()) return; if (m_dropsAtRecordEnabled) { //we're no longer dragging over the table if (m_dragIndicatorLine >= 0) { int row2update = m_dragIndicatorLine; m_dragIndicatorLine = -1; updateRecord(row2update); } QPoint p = e->pos(); int row = recordNumberAt(p.y()); if ((p.y() % d->recordHeight) > (d->recordHeight*2 / 3)) { row++; } KDbRecordData *data = m_data->at(row); KDbRecordData *newData = 0; emit droppedAtRecord(data, row, e, newData ); if (newData ) { const int realRow = (row == m_curRecord ? -1 : row); insertItem(newData , realRow); setCursorPosition(row, 0); } } } void KexiTableScrollArea::dragLeaveEvent(QDragLeaveEvent *e) { Q_UNUSED(e); if (!hasData()) return; if (m_dropsAtRecordEnabled) { //we're no longer dragging over the table if (m_dragIndicatorLine >= 0) { int row2update = m_dragIndicatorLine; m_dragIndicatorLine = -1; updateRecord(row2update); } } } void KexiTableScrollArea::updateCell(int record, int column) { // qDebug() << record << column; d->scrollAreaWidget->update(cellGeometry(record, column)); } void KexiTableScrollArea::updateCurrentCell() { updateCell(m_curRecord, m_curColumn); } void KexiTableScrollArea::updateRecord(int record) { // qDebug()<value() << recordPos(row) << viewport()->width() << rowHeight(); if (record < 0 || record >= (recordCount() + 2/* sometimes we want to refresh the row after last*/)) return; //qDebug() << horizontalScrollBar()->value() << " " << verticalScrollBar()->value(); //qDebug() << QRect( columnPos( leftcol ), recordPos(row), viewport()->width(), rowHeight() ); d->scrollAreaWidget->update(horizontalScrollBar()->value(), recordPos(record), viewport()->width(), recordHeight()); } void KexiTableScrollArea::slotColumnWidthChanged(int column, int oldSize, int newSize) { Q_UNUSED(oldSize); Q_UNUSED(newSize); updateScrollAreaWidgetSize(); d->scrollAreaWidget->update(d->horizontalHeader->offset() + columnPos(column), d->verticalHeader->offset(), viewport()->width() - columnPos(column), viewport()->height()); //qDebug() << QRect(columnPos(column), 0, viewport()->width() - columnPos(column), viewport()->height()); QWidget *editorWidget = dynamic_cast(m_editor); if (editorWidget && editorWidget->isVisible()) { editorWidget->move(columnPos(m_curColumn), recordPos(m_curRecord)); editorWidget->resize(columnWidth(m_curColumn), recordHeight()); } updateGeometries(); editorShowFocus(m_curRecord, m_curColumn); if (editorWidget && editorWidget->isVisible()) { m_editor->setFocus(); } } void KexiTableScrollArea::slotSectionHandleDoubleClicked(int section) { adjustColumnWidthToContents(section); slotColumnWidthChanged(0, 0, 0); //to update contents and redraw ensureColumnVisible(section); } void KexiTableScrollArea::setSortingEnabled(bool set) { KexiDataAwareObjectInterface::setSortingEnabled(set); d->horizontalHeader->setSortingEnabled(set); } void KexiTableScrollArea::sortColumnInternal(int col, int order) { KexiDataAwareObjectInterface::sortColumnInternal(col, order); } int KexiTableScrollArea::leftMargin() const { return verticalHeaderVisible() ? d->verticalHeader->width() : 0; } int KexiTableScrollArea::topMargin() const { //qDebug() << d->horizontalHeader->height(); return horizontalHeaderVisible() ? d->horizontalHeader->height() : 0; } void KexiTableScrollArea::updateGeometries() { const QSize ts(tableSize()); if (d->horizontalHeader->offset() && ts.width() < (d->horizontalHeader->offset() + d->horizontalHeader->width())) { horizontalScrollBar()->setValue(ts.width() - d->horizontalHeader->width()); } const int colOffset = d->columnOffset(); const int frameLeftMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, 0, this) + colOffset; const int frameTopMargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, this) + colOffset; d->horizontalHeader->move(leftMargin() + frameLeftMargin, frameTopMargin); d->verticalHeader->move(frameLeftMargin, d->horizontalHeader->geometry().bottom() + 1); } int KexiTableScrollArea::columnWidth(int col) const { if (!hasData()) return 0; int vcID = m_data->visibleColumnIndex(col); //qDebug() << vcID << d->horizontalHeader->sectionSize(vcID); return (vcID == -1) ? 0 : d->horizontalHeader->sectionSize(vcID); } int KexiTableScrollArea::recordHeight() const { return d->recordHeight; } int KexiTableScrollArea::columnPos(int col) const { if (!hasData()) return 0; //if this column is hidden, find first column before that is visible int c = qMin(col, (int)m_data->columnCount() - 1), vcID = 0; while (c >= 0 && (vcID = m_data->visibleColumnIndex(c)) == -1) c--; if (c < 0) return 0; if (c == col) return d->horizontalHeader->sectionPosition(vcID); return d->horizontalHeader->sectionPosition(vcID) + d->horizontalHeader->sectionSize(vcID); } int KexiTableScrollArea::recordPos(int record) const { return d->recordHeight*record; } int KexiTableScrollArea::columnNumberAt(int pos) const { if (!hasData()) return -1; const int realPos = pos - d->horizontalHeader->offset(); const int c = d->horizontalHeader->logicalIndexAt(realPos); if (c < 0) return c; return m_data->globalIndexOfVisibleColumn(c); } int KexiTableScrollArea::recordNumberAt(int pos, bool ignoreEnd) const { if (!hasData()) return -1; pos /= d->recordHeight; if (pos < 0) return 0; if ((pos >= (int)m_data->count()) && !ignoreEnd) return -1; return pos; } QRect KexiTableScrollArea::cellGeometry(int record, int column) const { return QRect(columnPos(column), recordPos(record), columnWidth(column), recordHeight()); } //#define KEXITABLEVIEW_COMBO_DEBUG QSize KexiTableScrollArea::tableSize() const { #ifdef KEXITABLEVIEW_COMBO_DEBUG if (objectName() == "KexiComboBoxPopup_tv") { qDebug() << "rowCount" << rowCount() << "\nisInsertingEnabled" << isInsertingEnabled() << "columnCount" << columnCount(); } #endif if ((recordCount() + (isInsertingEnabled() ? 1 : 0)) > 0 && columnCount() > 0) { /* qDebug() << columnPos( columnCount() - 1 ) + columnWidth( columnCount() - 1 ) << ", " << recordPos( rowCount()-1+(isInsertingEnabled()?1:0)) + d->rowHeight */ // qDebug() << m_navPanel->isVisible() <<" "<height()<<" " // << horizontalScrollBar()->sizeHint().height()<<" "<recordHeight + d->internal_bottomMargin ); #ifdef KEXITABLEVIEW_COMBO_DEBUG if (objectName() == "KexiComboBoxPopup_tv") { qDebug() << "size" << s << "\ncolumnPos(columnCount()-1)" << columnPos(columnCount() - 1) << "\ncolumnWidth(columnCount()-1)" << columnWidth(columnCount() - 1) << "\nrecordPos(rowCount()-1+(isInsertingEnabled()?1:0))" << recordPos(rowCount()-1+(isInsertingEnabled()?1:0)) << "\nd->rowHeight" << d->rowHeight << "\nd->internal_bottomMargin" << d->internal_bottomMargin; } #endif // qDebug() << rowCount()-1 <<" "<< (isInsertingEnabled()?1:0) <<" "<< rowEditing() << " " << s; #ifdef KEXITABLEVIEW_DEBUG qDebug() << s << "cw(last):" << columnWidth(columnCount() - 1); #endif return s; } return QSize(0, 0); } void KexiTableScrollArea::ensureCellVisible(int record, int column) { if (!isVisible()) { //the table is invisible: we can't ensure visibility now d->ensureCellVisibleOnShow = QPoint(record, column); return; } if (column == -1) { column = m_curColumn; } if (record == -1) { record = m_curRecord; } if (column < 0 || record < 0) { return; } //quite clever: ensure the cell is visible: QRect r(columnPos(column) - 1, recordPos(record) + (d->appearance.fullRecordSelection ? 1 : 0) - 1, columnWidth(column) + 2, recordHeight() + 2); if (navPanelWidgetVisible() && horizontalScrollBar()->isHidden()) { //a hack: for visible navigator: increase height of the visible rect 'r' r.setBottom(r.bottom() + navPanelWidget()->height()); } QSize tableSize(this->tableSize()); const int bottomBorder = r.bottom() + (isInsertingEnabled() ? recordHeight() : 0); if (!spreadSheetMode() && (tableSize.height() - bottomBorder) < recordHeight()) { // ensure the very bottom of scroll area is displayed to help the user see what's there r.moveTop(tableSize.height() - r.height() + 1); } QPoint pcenter = r.center(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << pcenter.x() << pcenter.y() << (r.width() / 2) << (r.height() / 2); #endif ensureVisible(pcenter.x(), pcenter.y(), r.width() / 2, r.height() / 2); } void KexiTableScrollArea::ensureColumnVisible(int col) { if (!isVisible()) { return; } //quite clever: ensure the cell is visible: QRect r(columnPos(col == -1 ? m_curColumn : col) - 1, d->verticalHeader->offset(), columnWidth(col == -1 ? m_curColumn : col) + 2, 0); QPoint pcenter = r.center(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << pcenter.x() << pcenter.y() << (r.width() / 2) << (r.height() / 2); #endif ensureVisible(pcenter.x(), pcenter.y(), r.width() / 2, r.height() / 2); } void KexiTableScrollArea::deleteCurrentRecord() { KexiDataAwareObjectInterface::deleteCurrentRecord(); ensureCellVisible(m_curRecord, -1); } KDbRecordData* KexiTableScrollArea::insertEmptyRecord(int pos) { const int previousRow = m_curRecord; KDbRecordData* data = KexiDataAwareObjectInterface::insertEmptyRecord(pos); // update header selection d->verticalHeader->setCurrentIndex( d->verticalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); d->verticalHeader->updateSection(previousRow); d->verticalHeader->updateSection(m_curRecord); return data; } void KexiTableScrollArea::updateAfterCancelRecordEditing() { KexiDataAwareObjectInterface::updateAfterCancelRecordEditing(); m_navPanel->showEditingIndicator(false); } void KexiTableScrollArea::updateAfterAcceptRecordEditing() { KexiDataAwareObjectInterface::updateAfterAcceptRecordEditing(); m_navPanel->showEditingIndicator(false); } bool KexiTableScrollArea::getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, KDbRecordData *data, KDbTableViewColumn *tvcol) const { if (edit->columnInfo() && edit->columnInfo()->indexForVisibleLookupValue() != -1 && edit->columnInfo()->indexForVisibleLookupValue() < (int)data->count()) { const QVariant *visibleFieldValue = 0; if (m_currentRecord == data && m_data->recordEditBuffer()) { visibleFieldValue = m_data->recordEditBuffer()->at( tvcol->visibleLookupColumnInfo(), false/*!useDefaultValueIfPossible*/); } if (visibleFieldValue) //(use bufferedValueAt() - try to get buffered visible value for lookup field) cellValue = *visibleFieldValue; else cellValue /*txt*/ = data->at(edit->columnInfo()->indexForVisibleLookupValue()); return true; } return false; } //reimpl. void KexiTableScrollArea::removeEditor() { if (!m_editor) return; KexiDataAwareObjectInterface::removeEditor(); viewport()->setFocus(); } void KexiTableScrollArea::slotRecordRepaintRequested(KDbRecordData* data) { updateRecord(m_data->indexOf(data)); } void KexiTableScrollArea::verticalScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::verticalScrollBarValueChanged(v); const QPoint posInViewport = viewport()->mapFromGlobal(QCursor::pos()) - QPoint(contentsMargins().left(), contentsMargins().top()); //qDebug() << posInViewport << contentsRect().size() - QSize(leftMargin(), topMargin()) // << QRect(QPoint(0, 0), contentsRect().size() - QSize(leftMargin(), topMargin())); const int record = recordNumberAt(posInViewport.y() + verticalScrollBar()->value()); if (record >= 0) { setHighlightedRecordNumber(record); } } #ifdef KEXI_TABLE_PRINT_SUPPORT void KexiTableScrollArea::print(QPrinter & /*printer*/ , QPrintDialog & /*printDialog*/) { int leftMargin = printer.margins().width() + 2 + d->rowHeight; int topMargin = printer.margins().height() + 2; // int bottomMargin = topMargin + ( printer.realPageSize()->height() * printer.resolution() + 36 ) / 72; int bottomMargin = 0; qDebug() << "bottom:" << bottomMargin; QPainter p(&printer); KDbRecordData *i; int width = leftMargin; for (int col = 0; col < columnCount(); col++) { p.fillRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, QBrush(Qt::gray)); p.drawRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight); p.drawText(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, Qt::AlignLeft | Qt::AlignVCenter, d->horizontalHeader->label(col)); width = width + columnWidth(col); } int yOffset = topMargin; int row = 0; int right = 0; for (i = m_data->first(); i; i = m_data->next()) { if (!i->isInsertItem()) { qDebug() << "row=" << row << "y=" << yOffset; int xOffset = leftMargin; for (int col = 0; col < columnCount(); col++) { qDebug() << "col=" << col << "x=" << xOffset; p.saveWorldMatrix(); p.translate(xOffset, yOffset); paintCell(&p, i, row, col, QRect(0, 0, columnWidth(col) + 1, d->rowHeight), true); p.restoreWorldMatrix(); xOffset = xOffset + columnWidth(col); right = xOffset; } row++; yOffset = topMargin + row * d->rowHeight; } if (yOffset > 900) { p.drawLine(leftMargin, topMargin, leftMargin, yOffset); p.drawLine(leftMargin, topMargin, right - 1, topMargin); printer.newPage(); yOffset = topMargin; row = 0; } } p.drawLine(leftMargin, topMargin, leftMargin, yOffset); p.drawLine(leftMargin, topMargin, right - 1, topMargin); p.end(); } #endif KDbField* KexiTableScrollArea::field(int column) const { if (!m_data || !m_data->column(column)) return 0; return m_data->column(column)->field(); } void KexiTableScrollArea::adjustColumnWidthToContents(int column) { if (!hasData()) return; if (column == -1) { const int cols = columnCount(); for (int i = 0; i < cols; i++) adjustColumnWidthToContents(i); return; } int indexOfVisibleColumn = (m_data->column(column) && m_data->column(column)->columnInfo()) ? m_data->column(column)->columnInfo()->indexForVisibleLookupValue() : -1; if (-1 == indexOfVisibleColumn) indexOfVisibleColumn = column; if (indexOfVisibleColumn < 0) return; QList::ConstIterator it(m_data->constBegin()); if (it != m_data->constEnd() && (*it)->count() <= indexOfVisibleColumn) return; KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(columnType(indexOfVisibleColumn)); if (!item) return; int maxw = horizontalHeaderVisible() ? d->horizontalHeader->preferredSectionSize(column) : 0; if (maxw == 0 && m_data->isEmpty()) return; //nothing to adjust //! \todo js: this is NOT EFFECTIVE for big data sets!!!! KexiTableEdit *ed = tableEditorWidget(column/* not indexOfVisibleColumn*/); const QFontMetrics fm(fontMetrics()); if (ed) { for (it = m_data->constBegin(); it != m_data->constEnd(); ++it) { const int wfw = ed->widthForValue((*it)->at(indexOfVisibleColumn), fm); maxw = qMax(maxw, wfw); } const bool focused = currentColumn() == column; maxw += (fm.width(" ") + ed->leftMargin() + ed->rightMargin(focused) + 2); } if (maxw < KEXITV_MINIMUM_COLUMN_WIDTH) maxw = KEXITV_MINIMUM_COLUMN_WIDTH; //not too small //qDebug() << "setColumnWidth(column=" << column // << ", indexOfVisibleColumn=" << indexOfVisibleColumn << ", width=" << maxw << " )"; setColumnWidth(column/* not indexOfVisibleColumn*/, maxw); } void KexiTableScrollArea::setColumnWidth(int column, int width) { if (columnCount() <= column || column < 0) return; d->horizontalHeader->resizeSection(column, width); editorShowFocus(m_curRecord, m_curColumn); } void KexiTableScrollArea::maximizeColumnsWidth(const QList &columnList) { if (!isVisible()) { d->maximizeColumnsWidthOnShow += columnList; return; } if (width() <= d->horizontalHeader->headerWidth()) return; //sort the list and make it unique QList cl, sortedList(columnList); qSort(sortedList); int i = -999; QList::ConstIterator it(sortedList.constBegin()), end(sortedList.constEnd()); for (; it != end; ++it) { if (i != (*it)) { cl += (*it); i = (*it); } } //resize int sizeToAdd = (width() - d->horizontalHeader->headerWidth()) / cl.count() - d->verticalHeader->width(); if (sizeToAdd <= 0) return; end = cl.constEnd(); for (it = cl.constBegin(); it != end; ++it) { int w = d->horizontalHeader->sectionSize(*it); if (w > 0) { d->horizontalHeader->resizeSection(*it, w + sizeToAdd); } } d->scrollAreaWidget->update(); editorShowFocus(m_curRecord, m_curColumn); } void KexiTableScrollArea::setColumnResizeEnabled(int column, bool set) { if (column < 0 || column >= columnCount()) { return; } d->horizontalHeader->setSectionResizeMode(column, set ? QHeaderView::Interactive : QHeaderView::Fixed); } void KexiTableScrollArea::setColumnsResizeEnabled(bool set) { d->horizontalHeader->setSectionResizeMode(set ? QHeaderView::Interactive : QHeaderView::Fixed); } bool KexiTableScrollArea::stretchLastColumn() const { return d->horizontalHeader->stretchLastSection(); } void KexiTableScrollArea::setStretchLastColumn(bool set) { if (columnCount() > 0) { setColumnResizeEnabled(columnCount() - 1, !set); } d->horizontalHeader->setStretchLastSection(set); } void KexiTableScrollArea::setEditableOnDoubleClick(bool set) { d->editOnDoubleClick = set; } bool KexiTableScrollArea::editableOnDoubleClick() const { return d->editOnDoubleClick; } bool KexiTableScrollArea::verticalHeaderVisible() const { return d->verticalHeader->isVisible(); } void KexiTableScrollArea::setVerticalHeaderVisible(bool set) { d->verticalHeader->setVisible(set); updateViewportMargins(); } void KexiTableScrollArea::updateViewportMargins() { d->viewportMargins = QMargins( leftMargin() + 1, topMargin() + 1, 0, // right 0 // bottom ); setViewportMargins(d->viewportMargins); //qDebug() << d->viewportMargins; } bool KexiTableScrollArea::horizontalHeaderVisible() const { return d->horizontalHeaderVisible; } void KexiTableScrollArea::setHorizontalHeaderVisible(bool set) { d->horizontalHeaderVisible = set; //needed because isVisible() is not always accurate d->horizontalHeader->setVisible(set); updateViewportMargins(); } void KexiTableScrollArea::triggerUpdate() { // qDebug(); d->pUpdateTimer->start(20); } void KexiTableScrollArea::setHBarGeometry(QScrollBar & hbar, int x, int y, int w, int h) { #ifdef KEXITABLEVIEW_DEBUG /*todo*/ qDebug(); #endif if (d->appearance.navigatorEnabled) { m_navPanel->setHBarGeometry(hbar, x, y, w, h); } else { hbar.setGeometry(x , y, w, h); } } void KexiTableScrollArea::setSpreadSheetMode(bool set) { KexiDataAwareObjectInterface::setSpreadSheetMode(set); d->setSpreadSheetMode(set); } int KexiTableScrollArea::validRowNumber(const QString& text) { bool ok = true; int r = text.toInt(&ok); if (!ok || r < 1) r = 1; else if (r > (recordCount() + (isInsertingEnabled() ? 1 : 0))) r = recordCount() + (isInsertingEnabled() ? 1 : 0); return r -1; } void KexiTableScrollArea::moveToRecordRequested(int record) { setFocus(); selectRecord(record); } void KexiTableScrollArea::moveToLastRecordRequested() { setFocus(); selectLastRecord(); } void KexiTableScrollArea::moveToPreviousRecordRequested() { setFocus(); selectPreviousRecord(); } void KexiTableScrollArea::moveToNextRecordRequested() { setFocus(); selectNextRecord(); } void KexiTableScrollArea::moveToFirstRecordRequested() { setFocus(); selectFirstRecord(); } void KexiTableScrollArea::copySelection() { if (m_currentRecord && m_curColumn != -1) { KexiTableEdit *edit = tableEditorWidget(m_curColumn); QVariant defaultValue; const bool defaultValueDisplayed = isDefaultValueDisplayed(m_currentRecord, m_curColumn, &defaultValue); if (edit) { QVariant visibleValue; getVisibleLookupValue(visibleValue, edit, m_currentRecord, m_data->column(m_curColumn)); edit->handleCopyAction( defaultValueDisplayed ? defaultValue : m_currentRecord->at(m_curColumn), visibleValue); } } } void KexiTableScrollArea::cutSelection() { //try to handle @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit) edit->handleAction("edit_cut"); } void KexiTableScrollArea::paste() { //try to handle @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit) edit->handleAction("edit_paste"); } bool KexiTableScrollArea::eventFilter(QObject *o, QEvent *e) { //don't allow to stole key my events by others: // qDebug() << "spontaneous " << e->spontaneous() << " type=" << e->type(); #ifdef KEXITABLEVIEW_DEBUG if (e->type() != QEvent::Paint && e->type() != QEvent::Leave && e->type() != QEvent::MouseMove && e->type() != QEvent::HoverMove && e->type() != QEvent::HoverEnter && e->type() != QEvent::HoverLeave) { qDebug() << e << o; } if (e->type() == QEvent::Paint) { qDebug() << "PAINT!" << static_cast(e) << static_cast(e)->rect(); } #endif if (e->type() == QEvent::KeyPress) { if (e->spontaneous()) { QKeyEvent *ke = static_cast(e); const int k = ke->key(); int mods = ke->modifiers(); //cell editor's events: //try to handle the event @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit && edit->handleKeyPress(ke, m_editor == edit)) { ke->accept(); return true; } else if (m_editor && (o == dynamic_cast(m_editor) || o == m_editor->widget())) { if ((k == Qt::Key_Tab && (mods == Qt::NoModifier || mods == Qt::ShiftModifier)) || (overrideEditorShortcutNeeded(ke)) || (k == Qt::Key_Enter || k == Qt::Key_Return || k == Qt::Key_Up || k == Qt::Key_Down) || (k == Qt::Key_Left && m_editor->cursorAtStart()) || (k == Qt::Key_Right && m_editor->cursorAtEnd()) ) { //try to steal the key press from editor or it's internal widget... keyPressEvent(ke); if (ke->isAccepted()) return true; } } } } else if (e->type() == QEvent::Leave) { if ( o == d->scrollAreaWidget && d->appearance.recordMouseOverHighlightingEnabled && d->appearance.persistentSelections) { if (d->highlightedRecord != -1) { int oldRow = d->highlightedRecord; d->highlightedRecord = -1; updateRecord(oldRow); d->verticalHeader->updateSection(oldRow); const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted = d->appearance.recordHighlightingEnabled && !d->appearance.persistentSelections; if (oldRow != m_curRecord && m_curRecord >= 0) { if (!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) { //no highlight for now: show selection again updateRecord(m_curRecord); } } } } d->recentCellWithToolTip = QPoint(-1, -1); } else if (o == viewport() && e->type() == QEvent::DragEnter) { e->accept(); } return QScrollArea::eventFilter(o, e); } void KexiTableScrollArea::setBottomMarginInternal(int pixels) { d->internal_bottomMargin = pixels; updateWidgetContentsSize(); } void KexiTableScrollArea::changeEvent(QEvent *e) { switch (e->type()) { case QEvent::PaletteChange: { d->verticalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); d->horizontalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); break; } default:; } QScrollArea::changeEvent(e); } const KexiTableScrollArea::Appearance& KexiTableScrollArea::appearance() const { return d->appearance; } void KexiTableScrollArea::setAppearance(const Appearance& a) { setFont(font()); //this also updates contents if (a.fullRecordSelection) { d->recordHeight -= 1; } else { d->recordHeight += 1; } if (d->verticalHeader) { d->verticalHeader->setDefaultSectionSize(d->recordHeight); } if (a.recordHighlightingEnabled) { m_updateEntireRecordWhenMovingToOtherRecord = true; } navPanelWidget()->setVisible(a.navigatorEnabled); setHorizontalScrollBarPolicy(a.navigatorEnabled ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded); d->highlightedRecord = -1; //! @todo is setMouseTracking useful for other purposes? viewport()->setMouseTracking(a.recordMouseOverHighlightingEnabled); d->appearance = a; updateViewportMargins(); } int KexiTableScrollArea::highlightedRecordNumber() const { return d->highlightedRecord; } void KexiTableScrollArea::setHighlightedRecordNumber(int record) { if (record != -1) { record = qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), record); record = qMax(0, record); } const int previouslyHighlightedRow = d->highlightedRecord; if (previouslyHighlightedRow == record) { if (previouslyHighlightedRow != -1) updateRecord(previouslyHighlightedRow); return; } d->highlightedRecord = record; if (d->highlightedRecord != -1) updateRecord(d->highlightedRecord); if (previouslyHighlightedRow != -1) updateRecord(previouslyHighlightedRow); if (m_curRecord >= 0 && (previouslyHighlightedRow == -1 || previouslyHighlightedRow == m_curRecord) && d->highlightedRecord != m_curRecord && !d->appearance.persistentSelections) { //currently selected row needs to be repainted updateRecord(m_curRecord); } } KDbRecordData *KexiTableScrollArea::highlightedRecord() const { return d->highlightedRecord == -1 ? 0 : m_data->at(d->highlightedRecord); } QScrollBar* KexiTableScrollArea::verticalScrollBar() const { return QScrollArea::verticalScrollBar(); } int KexiTableScrollArea::lastVisibleRecord() const { return recordNumberAt(verticalScrollBar()->value()); } void KexiTableScrollArea::valueChanged(KexiDataItemInterface* item) { #ifdef KEXITABLEVIEW_DEBUG qDebug() << item->field()->name() << item->value(); #else Q_UNUSED(item); #endif // force reload editing-related actions emit updateSaveCancelActions(); } bool KexiTableScrollArea::cursorAtNewRecord() const { return m_newRecordEditing; } void KexiTableScrollArea::lengthExceeded(KexiDataItemInterface *item, bool lengthExceeded) { showLengthExceededMessage(item, lengthExceeded); } void KexiTableScrollArea::updateLengthExceededMessage(KexiDataItemInterface *item) { showUpdateForLengthExceededMessage(item); } QHeaderView* KexiTableScrollArea::horizontalHeader() const { return d->horizontalHeader; } QHeaderView* KexiTableScrollArea::verticalHeader() const { return d->verticalHeader; } int KexiTableScrollArea::horizontalHeaderHeight() const { return d->horizontalHeader->height(); } QWidget* KexiTableScrollArea::navPanelWidget() const { return dynamic_cast(m_navPanel); } bool KexiTableScrollArea::navPanelWidgetVisible() const { return navPanelWidget() && d->appearance.navigatorEnabled; } bool KexiTableScrollArea::event(QEvent *e) { switch (e->type()) { case QEvent::QueryWhatsThis: case QEvent::WhatsThis: { QHelpEvent *he = static_cast(e); QString text = whatsThisText(he->pos()); if (!text.isEmpty()) { if (e->type() == QEvent::WhatsThis) { QWhatsThis::showText(mapToGlobal(he->pos()), text, this); } return true; } return false; } default: break; } return QScrollArea::event(e); } QString KexiTableScrollArea::whatsThisText(const QPoint &pos) const { const int leftMargin = verticalHeaderVisible() ? d->verticalHeader->width() : 0; if (KDbUtils::hasParent(d->verticalHeader, childAt(pos))) { return xi18nc("@info:whatsthis", "Contains a pointer to the currently selected record."); } else if (KDbUtils::hasParent(navPanelWidget(), childAt(pos))) { return xi18nc("@info:whatsthis", "Record navigator."); } const int col = columnNumberAt(pos.x() - leftMargin); KDbField *f = col == -1 ? 0 : field(col); if (!f) { return QString(); } return xi18nc("@info:whatsthis", "Column %1.", f->description().isEmpty() ? f->captionOrName() : f->description()); } void KexiTableScrollArea::selectCellInternal(int previousRow, int previousColumn) { // let the current style draw selection d->horizontalHeader->setCurrentIndex( d->horizontalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); d->verticalHeader->setCurrentIndex( d->verticalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); if (previousColumn != m_curColumn) { d->horizontalHeader->updateSection(previousColumn); } d->horizontalHeader->updateSection(m_curColumn); if (previousRow != m_curRecord) { d->verticalHeader->updateSection(previousRow); } d->verticalHeader->updateSection(m_curRecord); } QAbstractItemModel* KexiTableScrollArea::headerModel() const { return d->headerModel; } void KexiTableScrollArea::beginInsertItem(KDbRecordData *data, int pos) { Q_UNUSED(data); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->beginInsertRows(headerModel->index(pos, 0).parent(), pos, pos); } void KexiTableScrollArea::endInsertItem(KDbRecordData *data, int pos) { Q_UNUSED(data); Q_UNUSED(pos); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->endInsertRows(); } void KexiTableScrollArea::beginRemoveItem(KDbRecordData *data, int pos) { Q_UNUSED(data); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->beginRemoveRows(headerModel->index(pos, 0).parent(), pos, pos); } void KexiTableScrollArea::endRemoveItem(int pos) { Q_UNUSED(pos); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->endRemoveRows(); updateWidgetContentsSize(); } int KexiTableScrollArea::recordCount() const { return KexiDataAwareObjectInterface::recordCount(); } int KexiTableScrollArea::currentRecord() const { return KexiDataAwareObjectInterface::currentRecord(); } diff --git a/src/widget/utils/kexidisplayutils.cpp b/src/widget/utils/kexidisplayutils.cpp index 14519cd1b..c2b962c23 100644 --- a/src/widget/utils/kexidisplayutils.cpp +++ b/src/widget/utils/kexidisplayutils.cpp @@ -1,129 +1,130 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 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 "kexidisplayutils.h" #include #include #include #include #include #include #include //! A color for displaying default values or autonumbers #define SPECIAL_TEXT_COLOR KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color() Q_GLOBAL_STATIC(QPixmap, KexiDisplayUtils_autonum) static void initDisplayUtilsImages() { /*! @warning not reentrant! */ QImage autonum(":/kexi-autonumber"); KexiUtils::replaceColors(&autonum, SPECIAL_TEXT_COLOR); *KexiDisplayUtils_autonum = QPixmap::fromImage(autonum); } //----------------- KexiDisplayUtils::DisplayParameters::DisplayParameters() { } -KexiDisplayUtils::DisplayParameters::DisplayParameters(QWidget *w) +KexiDisplayUtils::DisplayParameters::DisplayParameters(const QWidget *w) { //! @todo check! textColor = w->palette().foreground().color(); selectedTextColor = w->palette().highlightedText().color(); font = w->font(); } static QString autonumberText() { return xi18nc("Autonumber, make it as short as possible", "(auto)"); } -void KexiDisplayUtils::initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget) +void KexiDisplayUtils::initDisplayForAutonumberSign(DisplayParameters* par, const QWidget *widget) { + Q_ASSERT(par); initDisplayUtilsImages(); - par.textColor = SPECIAL_TEXT_COLOR; - par.selectedTextColor = SPECIAL_TEXT_COLOR; //hmm, unused anyway - par.font = widget->font(); - par.font.setItalic(true); - QFontMetrics fm(par.font); - par.textWidth = fm.width(autonumberText()); - par.textHeight = fm.height(); + par->textColor = SPECIAL_TEXT_COLOR; + par->selectedTextColor = SPECIAL_TEXT_COLOR; //hmm, unused anyway + par->font = widget->font(); + par->font.setItalic(true); + QFontMetrics fm(par->font); + par->textWidth = fm.width(autonumberText()); + par->textHeight = fm.height(); } -void KexiDisplayUtils::initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget) +void KexiDisplayUtils::initDisplayForDefaultValue(DisplayParameters* par, const QWidget *widget) { - par.textColor = SPECIAL_TEXT_COLOR; + par->textColor = SPECIAL_TEXT_COLOR; //! @todo check! - par.selectedTextColor = widget->palette().highlightedText().color(); - par.font = widget->font(); - par.font.setItalic(true); + par->selectedTextColor = widget->palette().highlightedText().color(); + par->font = widget->font(); + par->font.setItalic(true); } void KexiDisplayUtils::paintAutonumberSign(const DisplayParameters& par, QPainter* painter, int x, int y, int width, int height, Qt::Alignment alignment, bool overrideColor) { painter->save(); painter->setFont(par.font); if (!overrideColor) painter->setPen(par.textColor); if (!(alignment & Qt::AlignVertical_Mask)) alignment |= Qt::AlignVCenter; if (!(alignment & Qt::AlignHorizontal_Mask)) alignment |= Qt::AlignLeft; int y_pixmap_pos = 0; if (alignment & Qt::AlignVCenter) { y_pixmap_pos = qMax(0, y + 1 + (height - KexiDisplayUtils_autonum->height()) / 2); } else if (alignment & Qt::AlignTop) { y_pixmap_pos = y + qMax(0, (par.textHeight - KexiDisplayUtils_autonum->height()) / 2); } else if (alignment & Qt::AlignBottom) { y_pixmap_pos = y + 1 + height - KexiDisplayUtils_autonum->height() - qMax(0, (par.textHeight - KexiDisplayUtils_autonum->height()) / 2); } if (alignment & (Qt::AlignLeft | Qt::AlignJustify)) { if (!overrideColor) { painter->drawPixmap(x, y_pixmap_pos, *KexiDisplayUtils_autonum); x += (KexiDisplayUtils_autonum->width() + 4); } } else if (alignment & Qt::AlignRight) { if (!overrideColor) { painter->drawPixmap(x + width - par.textWidth - KexiDisplayUtils_autonum->width() - 4, y_pixmap_pos, *KexiDisplayUtils_autonum); } } else if (alignment & Qt::AlignCenter) { //! @todo if (!overrideColor) painter->drawPixmap(x + (width - par.textWidth) / 2 - KexiDisplayUtils_autonum->width() - 4, y_pixmap_pos, *KexiDisplayUtils_autonum); } painter->drawText(x, y, width, height, alignment, autonumberText()); painter->restore(); } diff --git a/src/widget/utils/kexidisplayutils.h b/src/widget/utils/kexidisplayutils.h index 454d521c2..0f6711b64 100644 --- a/src/widget/utils/kexidisplayutils.h +++ b/src/widget/utils/kexidisplayutils.h @@ -1,60 +1,62 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIDISPUTILS_H #define KEXIDISPUTILS_H #include "kexiguiutils_export.h" #include #include class QWidget; //! \brief A set of utilities related to displaying common elements in Kexi, like e.g. (autonumber) sign class KEXIGUIUTILS_EXPORT KexiDisplayUtils { public: //! Stores set of display parameters used in utility functions class KEXIGUIUTILS_EXPORT DisplayParameters { public: //! Creates uninitialized parameters DisplayParameters(); //! Copies properties from \a w. - explicit DisplayParameters(QWidget *w); + explicit DisplayParameters(const QWidget *w); - QColor textColor, selectedTextColor; + QColor textColor; + QColor selectedTextColor; QFont font; - int textWidth, textHeight; //!< used for "(autonumber)" text only + int textWidth = 0; + int textHeight = 0; //!< used for "(autonumber)" text only }; //! Initializes display parameters for autonumber sign - static void initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget); + static void initDisplayForAutonumberSign(DisplayParameters* par, const QWidget *widget); //! Paints autonumber sign using \a par parameters static void paintAutonumberSign(const DisplayParameters& par, QPainter* painter, int x, int y, int width, int height, Qt::Alignment alignment, bool overrideColor = false); //! Initializes display parameters for default value - static void initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget); + static void initDisplayForDefaultValue(DisplayParameters* par, const QWidget *widget); }; #endif