diff --git a/src/core/kexidataiteminterface.h b/src/core/kexidataiteminterface.h index 1aa7852ff..83d18d5c5 100644 --- a/src/core/kexidataiteminterface.h +++ b/src/core/kexidataiteminterface.h @@ -1,266 +1,267 @@ /* This file is part of the KDE project Copyright (C) 2005-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. */ #ifndef KEXIDATAITEMINTERFACE_H #define KEXIDATAITEMINTERFACE_H #include #include #include "kexicore_export.h" class KexiDataItemInterface; +class KDbConnection; class KDbField; class KDbQueryColumnInfo; //! An helper class used to react on KexiDataItemInterface objects' changes. class KEXICORE_EXPORT KexiDataItemChangesListener { public: KexiDataItemChangesListener(); virtual ~KexiDataItemChangesListener(); /*! Implement this to react for change of \a item. Called by KexiDataItemInterface::valueChanged() */ virtual void valueChanged(KexiDataItemInterface* item) = 0; /*! Implement this to return information whether we're currently at new record or not. This can be used e.g. by data-aware widgets to determine if "(auto)" label should be displayed. */ virtual bool cursorAtNewRecord() const = 0; /*! Implement this to react when length of data has been exceeded. */ virtual void lengthExceeded(KexiDataItemInterface *item, bool lengthExceeded) = 0; /*! Implement this to react when "length of data has been exceeded" message need updating. */ virtual void updateLengthExceededMessage(KexiDataItemInterface *item) = 0; }; //! An interface for declaring widgets to be data-aware. class KEXICORE_EXPORT KexiDataItemInterface { public: KexiDataItemInterface(); virtual ~KexiDataItemInterface(); /*! Just initializes \a value, and calls setValueInternal(const QString& add, bool removeOld). If \a removeOld is true, current value is set up as \a add. If \a removeOld if false, current value is set up as \a value + \a add. \a value is stored as 'old value' -it'd be usable in the future (e.g. Combo Box editor can use old value if current value does not match any item on the list). \a visibleValue (if not NULL) is passed to provide visible value to display instead of \a value. This is currently used only in case of the combo box form widget, where displayed content (usually a text of image) differs from the value of the widget (a numeric index). This method is called by table view's and form's editors. */ void setValue(const QVariant& value, const QVariant& add = QVariant(), bool removeOld = false, const QVariant* visibleValue = 0); //! \return field information for this item virtual KDbField *field() = 0; //! \return column information for this item virtual KDbQueryColumnInfo* columnInfo() = 0; //! Used internally to set column information. - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo) = 0; + virtual void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) = 0; //! Sets listener. No need to reimplement this. virtual void installListener(KexiDataItemChangesListener* listener); //! \return value currently represented by this item. virtual QVariant value() = 0; //! \return true if editor's value is valid for a given type //! Used for checking if an entered value is valid, //! E.g. a part of time value can be entered: "12:8" and this is invalid, not only null. //! Null time or date is valid in Kexi, so it is not enough to test value().isValid(). //! Default implementation just returns true. virtual bool valueIsValid(); //! \return true if editor's value is null (not empty) //! Used for checking if a given constraint within table or form is met. virtual bool valueIsNull() = 0; //! \return true if editor's value is empty (not necessary null). //! Only few data types can accept "EMPTY" property //! (use KDbField::hasEmptyProperty() to check this). //! Used for checking if a given constraint within table of form is met. virtual bool valueIsEmpty() = 0; //! \return value that should be displayed for this item. //! Only used for items like combo box, where real value is an integer while //! displayed value is usually a text. For other item types this method should be empty. virtual QVariant visibleValue(); /*! \return 'readOnly' flag for this item. The flag is usually taken from the item's widget, e.g. QLineEdit::isReadOnly(). By default, always returns false. */ virtual bool isReadOnly() const; /*! \return the view widget of this item, e.g. line edit widget. */ virtual QWidget* widget() = 0; /*! Hides item's widget, if available. */ virtual void hideWidget(); /*! Shows item's widget, if available. */ virtual void showWidget(); //! \return true if editor's value is changed (compared to original value) virtual bool valueChanged(); /*! \return true if the item widget's cursor (whatever that means, eg. line edit cursor) is at the beginning of editor's contents. This can inform table/form view that after pressing "left arrow" key should stop editing and move to a field on the left hand. */ virtual bool cursorAtStart() = 0; /*! \return true if the item widget's cursor (whatever that means, eg. line edit cursor) is at the end of editor's contents. This can inform table/form view that after pressing "right arrow" key should stop editing and move to a field on the right hand. */ virtual bool cursorAtEnd() = 0; /*! Moves cursor after the last character (or element). For implementation in items supporting text cursor's movement; by default does nothing. */ virtual void moveCursorToEnd() {} /*! Moves cursor before the first character (or element). For implementation in items supporting text cursor's movement; by default does nothing. */ virtual void moveCursorToStart() {} /*! Selects all characters (or elements) of the item. For implementation in items supporting text or elements; by default does nothing. */ virtual void selectAll() {} //! clears item's data, so the data will contain NULL data virtual void clear() = 0; /*! \return true if this editor offers a widget (e.g. line edit) that we can move focus to. Editor for boolean values has this set to false (see KexiBoolTableEdit). This is true by default. You can override this flag by changing hasFocusableWidget in your subclass' constructor. */ bool hasFocusableWidget() const; void setHasFocusableWidget(bool set) const; /*! Displays additional elements that are needed for indicating that the current cell is selected. For example, combobox editor (KexiComboBoxTableEdit) moves and shows dropdown button. \a r is the rectangle for the cell. If \a readOnly is true, additional elements should be visually disabled, e.g. dropdown button of the combobox editor should be disabled. For reimplementation. By default does nothing. */ virtual void showFocus(const QRect& r, bool readOnly); /*! Hides additional elements that are needed for indicating that the current cell is selected. For reimplementation. By default does nothing. */ virtual void hideFocus(); /*! Allows to define reaction for clicking on cell's contents. Currently it's used for editor of type boolean, where we want to toggle true/false on single mouse click. \sa hasFocusableWidget(), KexiBoolTableEdit. Default implementation does nothing. */ virtual void clickedOnContents(); /*! \return true if editing should be accepted immediately after deleting contents for the cell (usually using Delete key). This flag is false by default, and is true e.g. for date, time and datetime types. */ bool acceptEditorAfterDeleteContents() const; void setAcceptEditorAfterDeleteContents(bool set) const; virtual void setFocus(); bool cursorAtNewRecord(); /*! Sets a pointer to a Parent Data Item Interface. This pointer is 0 by default, but can be set by parent widget if this interface is a building block of a larger data widget. It is the case for KexiDBFieldEdit widget (see KexiDBFieldEdit::createEditor()). Use with care. signalValueChanged() method will check this pointer, and if it's not 0, parentDataItemInterface()->signalValueChanged() is called, so a changes can be signalled at higher level. */ void setParentDataItemInterface(KexiDataItemInterface* parentDataItemInterface); /*! \return a pointer to a Parent Data Item Interface. @see setParentDataItemInterface() */ KexiDataItemInterface* parentDataItemInterface() const; /*! Handles action having standard name \a actionName. Action could be: "edit_cut", "edit_paste", etc. For reimplementation. */ virtual void handleAction(const QString& actionName); virtual bool isComboBox() const; virtual QWidget* internalEditor() const; //! Called (e.g. by KexiDataItemInterface) to change invalid value so it is valid. //! @return true on successful fixing //! Default implementation just returns true. virtual bool fixup(); bool lengthExceededEmittedAtPreviousChange() const; void setLengthExceededEmittedAtPreviousChange(bool set); protected: /*! Initializes this editor with \a add value, which should be somewhat added to the current value (see originalValue()). If \a removeOld is true, a value should be set to \a add, otherwise -it should be set to current \a originalValue() + \a add, if possible. Implement this. */ virtual void setValueInternal(const QVariant& add, bool removeOld) = 0; QVariant originalValue() const; /*! Initializes this editor with \a value visible value. This is currently used only in case of the combo box form widget, where displayed content (usually a text of image) differs from the value of the widget (a numeric index). For implementation in the combo box widget, by default does nothing. */ virtual void setVisibleValueInternal(const QVariant& value); /*! Call this in your implementation when value changes, so installed listener can react on this change. If there is a parent data item defined (see setParentDataItemInterface()), parent's signalValueChanged() method will be called instead. */ virtual void signalValueChanged(); /*! Used to perform some actions before signalValueChanged() call. We need this because the interface is not QObject and thus has got no real signals. Used in KexiDBComboBox. */ virtual void beforeSignalValueChanged() {} /*! Used to indicate that length of data has been exceeded. */ virtual void signalLengthExceeded(bool lengthExceeded); /*! Used to request update for "length has been exceeded" message. */ virtual void signalUpdateLengthExceededMessage(); /*! Emits request for showing or hiding the "Length exceeded" warning, if needed. */ void emitLengthExceededIfNeeded(bool lengthExceeded); KexiDataItemChangesListener* listener(); void setFocusableWidget(bool set); class Private; Private* const d; }; #endif diff --git a/src/migration/KexiSqlMigrate.cpp b/src/migration/KexiSqlMigrate.cpp index 097171ce2..117922915 100644 --- a/src/migration/KexiSqlMigrate.cpp +++ b/src/migration/KexiSqlMigrate.cpp @@ -1,207 +1,207 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2006-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. */ #include "KexiSqlMigrate.h" #include #include #include #include #include #include #include #include KexiSqlMigrate::KexiSqlMigrate(const QString &kdbDriverId, QObject *parent, const QVariantList& args) : KexiMigration::KexiMigrate(parent, args) , m_kdbDriverId(kdbDriverId) { Q_ASSERT(!m_kdbDriverId.isEmpty()); } KexiSqlMigrate::~KexiSqlMigrate() { } KDbConnection* KexiSqlMigrate::drv_createConnection() { KDbDriverManager manager; KDbDriver *driver = manager.driver(m_kdbDriverId); if (!driver) { m_result = manager.result(); return nullptr; } KDbConnection *c = driver->createConnection(*data()->source); m_result = c ? KDbResult() : driver->result(); return c; } bool KexiSqlMigrate::drv_readTableSchema( const QString& originalName, KDbTableSchema *tableSchema) { //! @todo IDEA: ask for user input for captions //Perform a query on the table to get some data KDbEscapedString sql = KDbEscapedString("SELECT * FROM %1 LIMIT 0") .arg(sourceConnection()->escapeIdentifier(tableSchema->name())); QSharedPointer result = sourceConnection()->prepareSql(sql); if (!result) { return false; } bool ok = true; const int fieldsCount = result->fieldsCount(); for (int i = 0; i < fieldsCount; i++) { KDbField *field = result->createField(originalName, i); if (field->type() == KDbField::InvalidType) { field->setType(userType(originalName + '.' + field->name())); } if (!tableSchema->addField(field)) { delete field; tableSchema->clear(); ok = false; break; } } return ok; } bool KexiSqlMigrate::drv_tableNames(QStringList *tableNames) { QSharedPointer result = sourceConnection()->prepareSql(m_tableNamesSql); if (!result || result->fieldsCount() < 1) { return false; } Q_FOREVER { QSharedPointer record = result->fetchRecord(); if (!record) { if (result->lastResult().isError()) { return false; } break; } tableNames->append(record->stringValue(0)); } return true; } tristate KexiSqlMigrate::drv_queryStringListFromSql( const KDbEscapedString& sqlStatement, int fieldIndex, QStringList *stringList, int numRecords) { QSharedPointer result= sourceConnection()->prepareSql(sqlStatement); if (!result) { return true; } if (result->fieldsCount() < (fieldIndex+1)) { qWarning() << sqlStatement << ": fieldIndex too large (" << fieldIndex << "), expected 0.." << result->fieldsCount() - 1; return false; } for (int i = 0; numRecords == -1 || i < numRecords; i++) { QSharedPointer record = result->fetchRecord(); if (!record) { if (numRecords != -1 || result->lastResult().isError()) { return false; } return true; } stringList->append(record->stringValue(fieldIndex)); } return true; } bool KexiSqlMigrate::drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter) { QSharedPointer result = sourceConnection()->prepareSql( KDbEscapedString("SELECT * FROM %1").arg(sourceConnection()->escapeIdentifier(srcTable))); if (!result) { return false; } - const KDbQueryColumnInfo::Vector fieldsExpanded(dstTable->query()->fieldsExpanded()); + const KDbQueryColumnInfo::Vector fieldsExpanded(dstTable->query()->fieldsExpanded(destConn)); const int numFields = qMin(fieldsExpanded.count(), result->fieldsCount()); Q_FOREVER { QSharedPointer record = result->fetchRecord(); if (!record) { if (!result->lastResult().isError()) { break; } return false; } if (recordFilter) { if (!(*recordFilter)(record)) { continue; } } QList vals; for(int i = 0; i < numFields; ++i) { const KDbSqlString s(record->cstringValue(i)); vals.append(KDb::cstringToVariant( s.string, fieldsExpanded.at(i)->field()->type(), 0, s.length)); } updateProgress(); if (recordFilter) { if (!(*recordFilter)(vals)) { continue; } } if (!destConn->insertRecord(dstTable, vals)) { return false; } } /*! @todo Check that wasn't an error, rather than end of result set */ return true; } bool KexiSqlMigrate::drv_getTableSize(const QString& table, quint64 *size) { Q_ASSERT(size); QSharedPointer result = sourceConnection()->prepareSql(KDbEscapedString("SELECT COUNT(*) FROM %1") .arg(sourceConnection()->escapeIdentifier(table))); if (!result) { return false; } QSharedPointer record = result->fetchRecord(); if (!result || result->fieldsCount() == 0) { return false; } bool ok; quint64 value = record->toByteArray(0).toULongLong(&ok); if (!ok) { value = -1; } *size = value; return ok; } QSharedPointer KexiSqlMigrate::drv_readFromTable(const QString& tableName) { QSharedPointer result = sourceConnection()->prepareSql( KDbEscapedString("SELECT * FROM %1").arg(sourceConnection()->escapeIdentifier(tableName))); if (!result || result->lastResult().isError()) { m_result = sourceConnection()->result(); qWarning() << m_result; result.clear(); } return result; } diff --git a/src/plugins/forms/kexiformview.cpp b/src/plugins/forms/kexiformview.cpp index c397266d6..3c10b5eab 100644 --- a/src/plugins/forms/kexiformview.cpp +++ b/src/plugins/forms/kexiformview.cpp @@ -1,1299 +1,1300 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004-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. */ #include "kexiformview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kexidbform.h" #include "kexiformscrollview.h" #include "kexidatasourcepage.h" #include "kexiformmanager.h" #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT #include "kexidbautofield.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @todo #define KEXI_SHOW_SPLITTER_WIDGET class Q_DECL_HIDDEN KexiFormView::Private { public: Private() : resizeMode(KexiFormView::ResizeDefault) , query(0) , queryIsOwned(false) , cursor(0) { } KexiDBForm *dbform; KexiFormScrollView *scrollView; /*! Database cursor used for data retrieving. It is shared between subsequent Data view sessions (just reopened on switch), but deleted and recreated from scratch when form's "dataSource" property changed since last form viewing (d->previousDataSourceString is used for that). */ QString previousDataSourceString; int resizeMode; KDbQuerySchema* query; /*! True, if d->query is created as temporary object within this form. If user selected an existing, predefined (stored) query, d->queryIsOwned will be false, so the query object will not be destroyed. */ bool queryIsOwned; KDbCursor *cursor; /*! For new (empty) forms only: Our form's area will be resized more than once. We will resize form widget itself later (in resizeEvent()). */ int delayedFormContentsResizeOnShow; //! Used in setFocusInternal() QPointer setFocusInternalOnce; #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT /*! Stores geometry of widget recently inserted using insertAutoFields() method. having this information, we'r eable to compute position for a newly inserted widget in insertAutoFields() is such position has not been specified. (the position is specified when a widget is inserted with mouse drag & dropping but not with clicking of 'Insert fields' button from Data Source pane) */ QRect widgetGeometryForRecentInsertAutoFields; #endif //! Cached form pointer QPointer form; }; KexiFormView::KexiFormView(QWidget *parent, bool dbAware) : KexiDataAwareView(parent) , d(new Private) { Q_UNUSED(dbAware); d->delayedFormContentsResizeOnShow = 0; //! @todo remove? setSortedProperties(true); d->scrollView = new KexiFormScrollView( // will be added to layout this, viewMode() == Kexi::DataViewMode); // in KexiDataAwareView::init() (void)initForm(); if (viewMode() == Kexi::DesignViewMode) { connect(form(), SIGNAL(propertySetSwitched()), this, SLOT(slotPropertySetSwitched())); connect(form(), SIGNAL(modified(bool)), this, SLOT(setDirty(bool))); connect(d->scrollView, SIGNAL(resized()), this, SLOT(setFormModified())); connect(d->dbform, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)), this, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*))); connect(d->dbform, SIGNAL(handleDropEvent(QDropEvent*)), this, SLOT(slotHandleDropEvent(QDropEvent*))); // action stuff plugSharedAction("formpart_taborder", form(), SLOT(editTabOrder())); plugSharedAction("formpart_adjust_size", form(), SLOT(adjustWidgetSize())); //! @todo add formpart_pixmap_collection action //! @todo add formpart_connections action plugSharedAction("edit_copy", form(), SLOT(copyWidget())); plugSharedAction("edit_cut", form(), SLOT(cutWidget())); plugSharedAction("edit_paste", form(), SLOT(pasteWidget())); plugSharedAction("edit_delete", form(), SLOT(deleteWidget())); plugSharedAction("edit_select_all", form(), SLOT(selectAll())); plugSharedAction("formpart_clear_contents", form(), SLOT(clearWidgetContent())); plugSharedAction("edit_undo", form(), SLOT(undo())); plugSharedAction("edit_redo", form(), SLOT(redo())); plugSharedAction("formpart_format_raise", form(), SLOT(bringWidgetToFront())); plugSharedAction("formpart_format_lower", form(), SLOT(sendWidgetToBack())); plugSharedAction("other_widgets_menu", form(), 0); setAvailable("other_widgets_menu", true); plugSharedAction("formpart_align_menu", form(), 0); plugSharedAction("formpart_align_to_left", form(), SLOT(alignWidgetsToLeft())); plugSharedAction("formpart_align_to_right", form(), SLOT(alignWidgetsToRight())); plugSharedAction("formpart_align_to_top", form(), SLOT(alignWidgetsToTop())); plugSharedAction("formpart_align_to_bottom", form(), SLOT(alignWidgetsToBottom())); plugSharedAction("formpart_align_to_grid", form(), SLOT(alignWidgetsToGrid())); plugSharedAction("formpart_adjust_size_menu", form(), 0); plugSharedAction("formpart_adjust_to_fit", form(), SLOT(adjustWidgetSize())); plugSharedAction("formpart_adjust_size_grid", form(), SLOT(adjustSizeToGrid())); plugSharedAction("formpart_adjust_height_small", form(), SLOT(adjustHeightToSmall())); plugSharedAction("formpart_adjust_height_big", form(), SLOT(adjustHeightToBig())); plugSharedAction("formpart_adjust_width_small", form(), SLOT(adjustWidthToSmall())); plugSharedAction("formpart_adjust_width_big", form(), SLOT(adjustWidthToBig())); plugSharedAction("format_font", form(), SLOT(changeFont())); // - setup local actions QList viewActions; QAction* a; a = form()->action("edit_undo"); a->setProperty("iconOnly", true); viewActions << a; a = form()->action("edit_redo"); a->setProperty("iconOnly", true); viewActions << a; setViewActions(viewActions); } KexiDataAwareView::init(d->scrollView, d->scrollView, d->scrollView, /* skip data-awarness if design mode */ viewMode() == Kexi::DesignViewMode); connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool))); } KexiFormView::~KexiFormView() { deleteQuery(); propertySetSwitched(); delete d; } void KexiFormView::deleteQuery() { if (d->cursor) { KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->deleteCursor(d->cursor); d->cursor = 0; } if (d->queryIsOwned) { delete d->query; } else { //! @todo remove this shared query from listened queries list } d->query = 0; } void KexiFormView::setForm(KFormDesigner::Form *f) { if (viewMode() == Kexi::DataViewMode) tempData()->previewForm = f; else tempData()->form = f; d->form = f; } bool KexiFormView::initForm() { d->dbform = new KexiDBForm(d->scrollView->widget(), d->scrollView); if (viewMode() == Kexi::DataViewMode) { d->scrollView->setWidget(d->dbform); } else { d->scrollView->setMainAreaWidget(d->dbform); } d->dbform->setObjectName( xi18nc("A prefix for identifiers of forms. Based on that, identifiers such as " "form1, form2 are generated. " "This string can be used to refer the widget object as variables in programming " "languages or macros so it must _not_ contain white spaces and non latin1 characters, " "should start with lower case letter and if there are subsequent words, these should " "start with upper case letter. Example: smallCamelCase. " "Moreover, try to make this prefix as short as possible.", "form")); QPalette pal(d->dbform->palette()); pal.setBrush(QPalette::Window, palette().brush(QPalette::Window)); d->dbform->setPalette(pal); // avoid inheriting QPalette::Window role d->scrollView->setResizingEnabled(true); if (viewMode() == Kexi::DataViewMode) { d->scrollView->recordNavigator()->setRecordHandler(d->scrollView); QPalette pal(d->scrollView->viewport()->palette()); pal.setBrush(d->scrollView->viewport()->backgroundRole(), d->dbform->palette().brush(d->dbform->backgroundRole())); d->scrollView->viewport()->setPalette(pal); } setForm( new KFormDesigner::Form( KexiFormManager::self()->library(), viewMode() == Kexi::DataViewMode ? KFormDesigner::Form::DataMode : KFormDesigner::Form::DesignMode, *KexiMainWindowIface::global()->actionCollection(), *KexiFormManager::self()->widgetActionGroup()) ); form()->createToplevel(d->dbform, d->dbform); const bool newForm = window()->id() < 0; KDbFieldList *fields = 0; #ifndef KEXI_NO_FORM_DATASOURCE_WIZARD if (newForm) { // Show the form wizard if this is a new Form KexiDataSourceWizard *w = new KexiDataSourceWizard( KexiMainWindowIface::global()->thisWidget()); if (!w->exec()) fields = 0; else fields = w->fields(); delete w; } #endif if (fields) { #ifndef KEXI_NO_FORM_DATASOURCE_WIZARD QDomDocument dom; formPart()->generateForm(fields, dom); KFormDesigner::FormIO::loadFormFromDom(form(), d->dbform, &dom); //! @todo handle errors #endif } else { if (!loadForm()) { return false; } } if (form()->autoTabStops()) form()->autoAssignTabStops(); //collect tab order information d->dbform->updateTabStopsOrder(form()); if (viewMode() == Kexi::DesignViewMode) { connect(form(), SIGNAL(widgetNameChanged(QByteArray,QByteArray)), this, SLOT(slotWidgetNameChanged(QByteArray,QByteArray))); connect(form(), SIGNAL(selectionChanged(QWidget*,KFormDesigner::Form::WidgetSelectionFlags)), this, SLOT(slotWidgetSelectionChanged(QWidget*,KFormDesigner::Form::WidgetSelectionFlags))); form()->selectWidget(form()->widget()); } else { form()->setMode(KFormDesigner::Form::DataMode); d->dbform->setMinimumSize(d->dbform->size()); // make vscrollbar appear when viewport is too small } d->scrollView->setForm(form()); d->scrollView->refreshContentsSize(); if (newForm && !fields) { /* Our form's area will be resized more than once. Let's resize form widget itself later. */ d->delayedFormContentsResizeOnShow = 3; } slotPropertySetSwitched(); // this prepares the data source page updateDataSourcePage(); if (!newForm && viewMode() == Kexi::DesignViewMode) { form()->clearUndoStack(); } return true; } void KexiFormView::updateAutoFieldsDataSource() { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT //! @todo call this when form's data source is changed //update autofields: //-inherit captions //-inherit data types //(this data has not been stored in the form) QString dataSourceString(d->dbform->dataSource()); QString dataSourcePartClassString(d->dbform->dataSourcePluginId()); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery( conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table :: KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) return; foreach (KFormDesigner::ObjectTreeItem *item, *form()->objectTree()->hash()) { KexiDBAutoField *afWidget = dynamic_cast(item->widget()); if (afWidget) { KDbQueryColumnInfo *colInfo = tableOrQuery.columnInfo(afWidget->dataSource()); if (colInfo) { afWidget->setColumnInfo(colInfo); } } } #endif } void KexiFormView::updateValuesForSubproperties() { //! @todo call this when form's data source is changed //update autofields: //-inherit captions //-inherit data types //(this data has not been stored in the form) QString dataSourceString(d->dbform->dataSource()); QString dataSourcePartClassString(d->dbform->dataSourcePluginId()); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery(conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) return; foreach (KFormDesigner::ObjectTreeItem *item, *form()->objectTree()->hash()) { // (delayed) set values for subproperties //! @todo this could be at the KFD level, but KFD is going to be merged anyway with kexiforms, right? KFormDesigner::WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast(item->widget()); if (subpropIface && subpropIface->subwidget() && item->subproperties()) { QWidget *subwidget = subpropIface->subwidget(); QHash* subprops = item->subproperties(); for (QHash::const_iterator subpropIt = subprops->constBegin(); subpropIt != subprops->constEnd(); ++subpropIt) { //qDebug() << "delayed setting of the subproperty: widget=" // << item->widget()->objectName() << " prop=" << subpropIt.key() << " val=" // << subpropIt.value(); QMetaProperty meta = KexiUtils::findPropertyWithSuperclasses( subwidget, qPrintable(subpropIt.key())); if (meta.isValid()) { // Special case: the property value of type enum (set) but is saved as a string list, // not as int, so we need to translate it to int. It's been created as such // by FormIO::readPropertyValue(). Example: "alignment" property. if (meta.isEnumType() && subpropIt.value().type() == QVariant::StringList) { const QByteArray keysCombined(subpropIt.value().toStringList().join("|").toLatin1()); subwidget->setProperty(subpropIt.key().toLatin1(), meta.enumerator().keysToValue(keysCombined.constData())); } else { subwidget->setProperty(subpropIt.key().toLatin1(), subpropIt.value()); } } }//for } } } //! Used in KexiFormView::loadForm() static void setUnsavedBLOBIdsForDataViewMode( QWidget* widget, const QHash& unsavedLocalBLOBsByName) { if (widget) { if (-1 != widget->metaObject()->indexOfProperty("pixmapId")) { const KexiBLOBBuffer::Id_t blobID = unsavedLocalBLOBsByName.value(widget->objectName().toLatin1()); if (blobID > 0) //! @todo KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - fix it widget->setProperty( "pixmapId", int(blobID)); } const QList list(widget->findChildren()); if (list.isEmpty()) return; foreach(QWidget *w, list) { setUnsavedBLOBIdsForDataViewMode(w, unsavedLocalBLOBsByName); } } } bool KexiFormView::loadForm() { //! @todo also load d->resizeMode //qDebug() << "Loading the form with id" << window()->id(); // If we are previewing the Form, use the tempData instead of the form stored in the db if (viewMode() == Kexi::DataViewMode && !tempData()->tempForm.isNull()) { if (!KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, tempData()->tempForm)) { return false; } setUnsavedBLOBIdsForDataViewMode(d->dbform, tempData()->unsavedLocalBLOBsByName); updateAutoFieldsDataSource(); updateValuesForSubproperties(); return true; } if (!window()->neverSaved()) { // normal load QString data; if (!loadDataBlock(&data)) { return false; } if (!KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, data)) { return false; } } //"autoTabStops" property is loaded -set it within the form tree as well form()->setAutoTabStops(d->dbform->autoTabStops()); updateAutoFieldsDataSource(); updateValuesForSubproperties(); return true; } void KexiFormView::slotPropertySetSwitched() { propertySetReloaded(); if (viewMode() == Kexi::DesignViewMode) { formPart()->dataSourcePage()->assignPropertySet(form()->propertySet()); } } tristate KexiFormView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); if (mode != viewMode()) { if (viewMode() == Kexi::DataViewMode) { if (!d->scrollView->acceptRecordEditing()) return cancelled; d->scrollView->beforeSwitchView(); } else { //remember our pos tempData()->scrollViewContentsPos = QPoint(d->scrollView->horizontalScrollBar()->value(), d->scrollView->verticalScrollBar()->value()); } } if (d->scrollView->data() && viewMode() == Kexi::DataViewMode) { //old data won't be needed nor valid d->scrollView->setData(0, false); } // we don't store on db, but in our TempData *dontStore = true; if (isDirty() && (mode == Kexi::DataViewMode) && form()->objectTree()) { KexiFormPartTempData* temp = tempData(); if (!KFormDesigner::FormIO::saveFormToString(form(), temp->tempForm)) return false; //collect blobs from design mode by name for use in data view mode temp->unsavedLocalBLOBsByName.clear(); for (QHash::const_iterator it = temp->unsavedLocalBLOBs.constBegin(); it != temp->unsavedLocalBLOBs.constEnd(); ++it) { if (!it.key()) continue; temp->unsavedLocalBLOBsByName.insert(it.key()->objectName().toLatin1(), it.value()); } } return true; } tristate KexiFormView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == 0 || mode == Kexi::DesignViewMode) { if (window()->neverSaved()) { d->scrollView->refreshContentsSizeLater(); } } if (mode == Kexi::DataViewMode) { //preserve contents pos after switching to other view d->scrollView->horizontalScrollBar()->setValue(tempData()->scrollViewContentsPos.x()); d->scrollView->verticalScrollBar()->setValue(tempData()->scrollViewContentsPos.y()); } if ((mode == Kexi::DesignViewMode) && viewMode() == Kexi::DataViewMode) { // The form may have been modified, so we must recreate the preview delete d->dbform; // also deletes form() if (!initForm()) { return false; } //reset position d->scrollView->horizontalScrollBar()->setValue(0); d->scrollView->verticalScrollBar()->setValue(0); d->dbform->move(0, 0); } //update tab stops if needed if (viewMode() == Kexi::DataViewMode) { } else { //set "autoTabStops" property d->dbform->setAutoTabStops(form()->autoTabStops()); } if (viewMode() == Kexi::DataViewMode) { //TMP!! initDataSource(); //handle events for this form d->scrollView->setMainWidgetForEventHandling(d->dbform); //set focus on 1st focusable widget which has valid dataSource property set QList *orderedFocusWidgets = d->dbform->orderedFocusWidgets(); if (!orderedFocusWidgets->isEmpty()) { KexiUtils::unsetFocusWithReason(QApplication::focusWidget(), Qt::TabFocusReason); QWidget *widget = 0; foreach(widget, *orderedFocusWidgets) { KexiFormDataItemInterface *iface = dynamic_cast(widget); if (iface) { //qDebug() << iface->dataSource(); } if (iface && iface->columnInfo() && !iface->isReadOnly() /*! @todo add option for skipping autoincremented fields */ /* also skip autoincremented fields:*/ && !iface->columnInfo()->field()->isAutoIncrement()) { break; } } if (!widget) //eventually, focus first available widget if nothing other is available widget = orderedFocusWidgets->first(); widget->setFocus(); KexiUtils::setFocusWithReason(widget, Qt::TabFocusReason); d->setFocusInternalOnce = widget; } if (d->query) d->scrollView->selectFirstRecord(); } //dirty only if it's a new object if (mode == Kexi::NoViewMode) setDirty(window()->partItem()->neverSaved()); updateActionsInternal(); return true; } KPropertySet* KexiFormView::propertySet() { return d->form->propertySet(); } KexiFormPartTempData* KexiFormView::tempData() const { return dynamic_cast(window()->data()); } KexiFormPart* KexiFormView::formPart() const { return dynamic_cast(part()); } void KexiFormView::initDataSource() { deleteQuery(); //! @todo also handle anonymous (not stored) queries provided as statements here KDbTableSchema *tableSchema = 0; KDbConnection *conn = 0; QStringList sources; bool forceReadOnlyDataSource = false; QString dataSourceString(d->dbform->dataSource()); bool ok = !dataSourceString.isEmpty(); if (ok) { //collect all data-aware widgets and create query schema d->scrollView->setMainDataSourceWidget(d->dbform); sources = d->scrollView->usedDataSources(); conn = KexiMainWindowIface::global()->project()->dbConnection(); QString dataSourcePartClassString(d->dbform->dataSourcePluginId()); if (dataSourcePartClassString.isEmpty() /*table type is the default*/ || dataSourcePartClassString == "org.kexi-project.table") { tableSchema = conn->tableSchema(dataSourceString); if (tableSchema) { /* We will build a _minimud-> query schema from selected table fields. */ d->query = new KDbQuerySchema(); d->queryIsOwned = true; if (dataSourcePartClassString.isEmpty()) d->dbform->setDataSourcePluginId("org.kexi-project.table"); //update for compatibility } } if (!tableSchema) { if (dataSourcePartClassString.isEmpty() /*also try to find a query (for compatibility with Kexi<=0.9)*/ || dataSourcePartClassString == "org.kexi-project.query") { //try to find predefined query schema. //Note: In general, we could not skip unused fields within this query because // it can have GROUP BY clause. //! @todo check if the query could have skipped unused fields (no GROUP BY, no joins, etc.) d->query = conn->querySchema(dataSourceString); d->queryIsOwned = false; ok = d->query != 0; if (ok && dataSourcePartClassString.isEmpty()) d->dbform->setDataSourcePluginId("org.kexi-project.query"); //update for compatibility // query results are read-only //! @todo There can be read-write queries, e.g. simple "SELECT * FROM...". Add a checking function to KDb. forceReadOnlyDataSource = true; } else { //no other classes are supported ok = false; } } } QSet invalidSources; if (ok) { KDbIndexSchema *pkey = tableSchema ? tableSchema->primaryKey() : 0; if (pkey) { //always add all fields from table's primary key // (don't worry about duplicates, unique list will be computed later) sources += pkey->names(); //qDebug() << "pkey added to data sources:" << pkey->names(); } //qDebug() << "sources=" << sources; int index = 0; for (QStringList::ConstIterator it = sources.constBegin(); it != sources.constEnd(); ++it, index++) { /*! @todo add expression support */ QString fieldName((*it).toLower()); //remove "tablename." if it was prepended if (tableSchema && fieldName.startsWith(tableSchema->name() + QLatin1Char('.'), Qt::CaseInsensitive)) fieldName.remove(0, tableSchema->name().length() + 1); //remove "queryname." if it was prepended if (!tableSchema && fieldName.startsWith(d->query->name() + QLatin1Char('.'), Qt::CaseInsensitive)) fieldName.remove(0, d->query->name().length() + 1); KDbField *f = tableSchema ? tableSchema->field(fieldName) : d->query->field(fieldName); if (!f) { /*! @todo show error */ //remove this widget from the set of data widgets in the provider /*! @todo fieldName is ok, but what about expressions? */ invalidSources.insert(fieldName); //qDebug() << "invalidSources+=" << index << " (" << (*it) << ")"; continue; } if (tableSchema) { if (!d->query->hasField(*f)) { //we're building a new query: add this field d->query->addField(f); } } } if (invalidSources.count() == sources.count()) { //all data sources are invalid! don't execute the query deleteQuery(); } else { - qDebug() << d->query->parameters(); + qDebug() << d->query->parameters(conn); // like in KexiQueryView::executeQuery() QList params; { KexiUtils::WaitCursorRemover remover; - params = KexiQueryParameters::getParameters(this, *conn->driver(), d->query, &ok); + params = KexiQueryParameters::getParameters(this, conn, d->query, &ok); } if (ok) //input cancelled d->cursor = conn->executeQuery(d->query, params); } - d->scrollView->invalidateDataSources(invalidSources, d->query); + d->scrollView->invalidateDataSources( + invalidSources, d->cursor ? d->cursor->connection() : nullptr, d->query); ok = d->cursor != 0; } if (!invalidSources.isEmpty()) d->dbform->updateTabStopsOrder(); if (ok) { //! @todo PRIMITIVE!! data setting: //! @todo KDbTableViewData is not a great name for data class here... rename/move? KDbTableViewData* data = new KDbTableViewData(d->cursor); if (forceReadOnlyDataSource) data->setReadOnly(true); data->preloadAllRecords(); ///*! @todo few backends return result count for free! - no need to reopen() */ // int resultCount = -1; // if (ok) { // resultCount = d->conn->resultCount(d->conn->selectStatement(*d->query)); // ok = d->cursor->reopen(); // } // if (ok) // ok = ! (!d->cursor->moveFirst() && d->cursor->error()); d->scrollView->setData(data, true /*owner*/); } else { d->scrollView->setData(0, false); } } void KexiFormView::setFormModified() { form()->setModified(true); } KDbObject* KexiFormView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); KDbObject *s = KexiView::storeNewData(object, options, cancel); //qDebug() << "new id:" << s->id(); if (!s || *cancel) { delete s; return 0; } if (!storeData()) { //failure: remove object's object data to avoid garbage KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->removeObject(s->id()); delete s; return 0; } return s; } tristate KexiFormView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); //qDebug() << window()->partItem()->name() << "[" << window()->id() << "]"; //-- first, store local BLOBs, so identifiers can be updated //! @todo remove unused data stored previously KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *blobsTable = conn->tableSchema("kexi__blobs"); if (!blobsTable) { //compatibility check for older Kexi project versions //! @todo show message about missing kexi__blobs? return false; } // Not all engines accept passing NULL to PKEY o_id, so we're omitting it. QStringList blobsFieldNamesWithoutID(blobsTable->names()); blobsFieldNamesWithoutID.pop_front(); KDbFieldList *blobsFieldsWithoutID = blobsTable->subList(blobsFieldNamesWithoutID); KDbPreparedStatement st = conn->prepareStatement( KDbPreparedStatement::InsertStatement, blobsFieldsWithoutID); if (!st.isValid()) { delete blobsFieldsWithoutID; //! @todo show message return false; } KexiBLOBBuffer *blobBuf = KexiBLOBBuffer::self(); KexiFormView *designFormView = dynamic_cast( window()->viewForMode(Kexi::DesignViewMode)); if (designFormView) { for (QHash::const_iterator it = tempData()->unsavedLocalBLOBs.constBegin(); it != tempData()->unsavedLocalBLOBs.constEnd(); ++it) { if (!it.key()) { qWarning() << "it.key()==0 !"; continue; } //qDebug() << "name=" << it.key()->objectName() << " dataID=" << it.value(); KexiBLOBBuffer::Handle h(blobBuf->objectForId(it.value(), /*!stored*/false)); if (!h) continue; //no BLOB assigned QString originalFileName(h.originalFileName()); QFileInfo fi(originalFileName); QString caption(fi.baseName().replace('_', ' ').simplified()); KDbPreparedStatementParameters parameters; parameters << h.data() << originalFileName << caption << h.mimeType() << int(/*! @todo unsafe */h.folderId()); if (!st.execute(parameters)) { delete blobsFieldsWithoutID; qWarning() << "execute error"; return false; } delete blobsFieldsWithoutID; blobsFieldsWithoutID = 0; const quint64 storedBLOBID = KDb::lastInsertedAutoIncValue( conn, st.lastInsertRecordId(), "o_id", "kexi__blobs"); if (std::numeric_limits::max() == storedBLOBID) { //! @todo show message? return false; } //qDebug() << "storedDataID=" << storedBLOBID; //! @todo unsafe - fix! h.setStoredWidthID((KexiBLOBBuffer::Id_t)storedBLOBID); //set widget's internal property so it can be saved... const QVariant oldStoredPixmapId(it.key()->property("storedPixmapId")); //! @todo KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - fix! it.key()->setProperty("storedPixmapId", QVariant(int(storedBLOBID))); KFormDesigner::ObjectTreeItem *widgetItem = designFormView->form()->objectTree()->lookup(it.key()->objectName()); if (widgetItem) widgetItem->addModifiedProperty("storedPixmapId", oldStoredPixmapId); else qWarning() << "no" << it.key()->objectName() << "widget found within a form"; } } //-- now, save form's XML QString data; if (!KFormDesigner::FormIO::saveFormToString(tempData()->form, data)) return false; if (!storeDataBlock(data)) return false; //all blobs are now saved tempData()->unsavedLocalBLOBs.clear(); tempData()->tempForm.clear(); return true; } //! @todo reuse the action stuff code #if 0 /// Action stuff ///////////////// void KexiFormView::slotWidgetSelected(KFormDesigner::Form *f, bool multiple) { if (f != form()) return; enableFormActions(); // Enable edit actions setAvailable("edit_copy", true); setAvailable("edit_cut", true); setAvailable("edit_clear", true); // 'Align Widgets' menu setAvailable("formpart_align_menu", multiple); setAvailable("formpart_align_to_left", multiple); setAvailable("formpart_align_to_right", multiple); setAvailable("formpart_align_to_top", multiple); setAvailable("formpart_align_to_bottom", multiple); setAvailable("formpart_adjust_size_menu", true); setAvailable("formpart_adjust_width_small", multiple); setAvailable("formpart_adjust_width_big", multiple); setAvailable("formpart_adjust_height_small", multiple); setAvailable("formpart_adjust_height_big", multiple); setAvailable("formpart_format_raise", true); setAvailable("formpart_format_lower", true); // If the widgets selected is a container, we enable layout actions if (!multiple) { KFormDesigner::ObjectTreeItem *item = f->objectTree()->lookup(f->selectedWidgets()->first()->name()); if (item && item->container()) multiple = true; } } void KexiFormView::slotFormWidgetSelected(KFormDesigner::Form *f) { if (f != form()) return; disableWidgetActions(); enableFormActions(); } void KexiFormView::slotNoFormSelected() // == form in preview mode { disableWidgetActions(); // Disable paste action setAvailable("edit_paste", false); setAvailable("edit_undo", false); setAvailable("edit_redo", false); // Disable 'Tools' actions setAvailable("formpart_pixmap_collection", false); setAvailable("formpart_connections", false); setAvailable("formpart_taborder", false); setAvailable("formpart_change_style", false); } void KexiFormView::enableFormActions() { // Enable 'Tools' actions setAvailable("formpart_pixmap_collection", true); setAvailable("formpart_connections", true); setAvailable("formpart_taborder", true); //! @todo KEXI3 Port this.. //! @todo setAvailable("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled()); } void KexiFormView::disableWidgetActions() { // Disable edit actions setAvailable("edit_copy", false); setAvailable("edit_cut", false); setAvailable("edit_clear", false); // Disable format functions setAvailable("formpart_align_menu", false); setAvailable("formpart_align_to_left", false); setAvailable("formpart_align_to_right", false); setAvailable("formpart_align_to_top", false); setAvailable("formpart_align_to_bottom", false); setAvailable("formpart_adjust_size_menu", false); setAvailable("formpart_adjust_width_small", false); setAvailable("formpart_adjust_width_big", false); setAvailable("formpart_adjust_height_small", false); setAvailable("formpart_adjust_height_big", false); setAvailable("formpart_format_raise", false); setAvailable("formpart_format_lower", false); } void KexiFormView::setUndoEnabled(bool enabled) { setAvailable("edit_undo", enabled); } void KexiFormView::setRedoEnabled(bool enabled) { setAvailable("edit_redo", enabled); } #endif //0 int KexiFormView::resizeMode() const { return d->resizeMode; } KFormDesigner::Form* KexiFormView::form() const { return d->form; } QSize KexiFormView::preferredSizeHint(const QSize& otherSize) { return (d->dbform->size() + QSize(d->scrollView->verticalScrollBar()->isVisible() ? d->scrollView->verticalScrollBar()->width()*3 / 2 : 10, d->scrollView->horizontalScrollBar()->isVisible() ? d->scrollView->horizontalScrollBar()->height()*3 / 2 : 10)) .expandedTo(KexiView::preferredSizeHint(otherSize)); } void KexiFormView::resizeEvent(QResizeEvent *e) { if (viewMode() == Kexi::DataViewMode) { d->scrollView->refreshContentsSizeLater(); } KexiView::resizeEvent(e); if (d->delayedFormContentsResizeOnShow > 0) { d->delayedFormContentsResizeOnShow--; d->dbform->resize(e->size() - QSize(30, 30)); } } void KexiFormView::contextMenuEvent(QContextMenuEvent *e) { // qDebug() << form()->selectedWidget() << form()->widget() << e->reason(); if (form()->selectedWidget() && form()->selectedWidget() == form()->widget() && e->reason() == QContextMenuEvent::Keyboard) { // Outer form area received context key. // Redirect the event to top-level form widget. // It will be received in Container::eventFilter(). e->accept(); QContextMenuEvent me(QContextMenuEvent::Keyboard, QPoint(-1, -1)); QApplication::sendEvent(form()->widget(), &me); return; } KexiView::contextMenuEvent(e); } void KexiFormView::setFocusInternal() { if (viewMode() == Kexi::DataViewMode) { if (d->dbform->focusWidget()) { //better-looking focus if (d->setFocusInternalOnce) { KexiUtils::setFocusWithReason(d->setFocusInternalOnce, Qt::OtherFocusReason); d->setFocusInternalOnce = 0; } else { //ok? SET_FOCUS_USING_REASON(d->dbform->focusWidget(), QFocusEvent::Other); } return; } } QWidget::setFocus(); } void KexiFormView::slotFocus(bool in) { Q_UNUSED(in); } void KexiFormView::updateDataSourcePage() { if (viewMode() == Kexi::DesignViewMode) { KPropertySet *set = form()->propertySet(); const QString dataSourcePartClass = set->propertyValue("dataSourcePartClass").toString(); const QString dataSource = set->propertyValue("dataSource").toString(); formPart()->dataSourcePage()->setFormDataSource(dataSourcePartClass, dataSource); } } void KexiFormView::slotHandleDragMoveEvent(QDragMoveEvent* e) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT if (KexiFieldDrag::canDecode(e)) { e->setAccepted(true); } #else Q_UNUSED(e); #endif } void KexiFormView::slotHandleDropEvent(QDropEvent* e) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT const QWidget *targetContainerWidget = dynamic_cast(sender()); KFormDesigner::ObjectTreeItem *targetContainerWidgetItem = targetContainerWidget ? form()->objectTree()->lookup(targetContainerWidget->objectName()) : 0; if (targetContainerWidgetItem && targetContainerWidgetItem->container() && KexiFieldDrag::canDecode(e)) { QString sourcePartClass, sourceName; QStringList fields; if (!KexiFieldDrag::decode(e, &sourcePartClass, &sourceName, &fields)) return; insertAutoFields(sourcePartClass, sourceName, fields, targetContainerWidgetItem->container(), e->pos()); } #else Q_UNUSED(e); #endif } void KexiFormView::insertAutoFields(const QString& sourcePartClass, const QString& sourceName, const QStringList& fields, KFormDesigner::Container* targetContainer, const QPoint& _pos) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT if (fields.isEmpty()) return; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery(conn, sourceName.toLatin1(), sourcePartClass == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) { qWarning() << "no such table/query" << sourceName; return; } QPoint pos(_pos); //if pos is not specified, compute a new position: if (pos == QPoint(-1, -1)) { if (d->widgetGeometryForRecentInsertAutoFields.isValid()) { pos = d->widgetGeometryForRecentInsertAutoFields.bottomLeft() + QPoint(0, form()->gridSize()); } else { pos = QPoint(40, 40); //start here } } // there will be many actions performed, do not update property pane until all that's finished //! todo unnamed query columns are not supported QWidgetList widgetsToSelect; KFormDesigner::PropertyCommandGroup *group = new KFormDesigner::PropertyCommandGroup( fields.count() == 1 ? futureI18n("Insert AutoField widget") : futureI18n2("Insert %1 AutoField widgets", fields.count()) ); foreach(const QString& field, fields) { KDbQueryColumnInfo* column = tableOrQuery.columnInfo(field); if (!column) { qWarning() << "no such field" << field << "in table/query" << sourceName; continue; } //! todo add autolabel using field's caption or name KFormDesigner::InsertWidgetCommand *insertCmd = new KFormDesigner::InsertWidgetCommand( *targetContainer, //! @todo this is hardcoded! "KexiDBAutoField", //! @todo this name can be invalid for expressions: if so, fall back to a default class' prefix! pos, column->aliasOrName(), group ); insertCmd->redo(); KFormDesigner::ObjectTreeItem *newWidgetItem = form()->objectTree()->hash()->value(insertCmd->widgetName()); KexiDBAutoField* newWidget = newWidgetItem ? dynamic_cast(newWidgetItem->widget()) : 0; widgetsToSelect.append(newWidget); KFormDesigner::PropertyCommandGroup *subGroup = new KFormDesigner::PropertyCommandGroup( QString(), group); QHash propValues; propValues.insert("dataSource", column->aliasOrName()); propValues.insert("fieldTypeInternal", (int)column->field->type()); propValues.insert("fieldCaptionInternal", column->captionOrAliasOrName()); form()->createPropertyCommandsInDesignMode( newWidget, propValues, subGroup, false/*!addToActiveForm*/); subGroup->redo(); //set data source and caption //-we don't need to use PropertyCommand here beacause we don't need UNDO // for these single commands //resize again because autofield's type changed what can lead to changed sizeHint() QWidgetList list; list.append(newWidget); KFormDesigner::AdjustSizeCommand *adjustCommand = new KFormDesigner::AdjustSizeCommand( *form(), KFormDesigner::AdjustSizeCommand::SizeToFit, list, group); adjustCommand->redo(); if (newWidget) {//move position down for next widget pos.setY(pos.y() + newWidget->height() + form()->gridSize()); } } if (widgetsToSelect.last()) { //resize form if needed QRect oldFormRect(d->dbform->geometry()); QRect newFormRect(oldFormRect); newFormRect.setWidth(qMax(d->dbform->width(), widgetsToSelect.last()->geometry().right() + 1)); newFormRect.setHeight(qMax(d->dbform->height(), widgetsToSelect.last()->geometry().bottom() + 1)); if (newFormRect != oldFormRect) { //1. resize by hand d->dbform->setGeometry(newFormRect); //2. store information about resize (void)new KFormDesigner::PropertyCommand( *form(), d->dbform->objectName().toLatin1(), oldFormRect, newFormRect, "geometry", group); } //remember geometry of the last inserted widget d->widgetGeometryForRecentInsertAutoFields = widgetsToSelect.last()->geometry(); } //eventually, add entire command group to active form form()->addCommand(group); //qDebug() << *group; d->scrollView->widget()->update(); d->scrollView->refreshContentsSize(); //select all inserted widgets, if multiple if (widgetsToSelect.count() > 1) { form()->selectWidget(0); foreach (QWidget *w, widgetsToSelect) { form()->selectWidget(w, KFormDesigner::Form::AddToPreviousSelection | KFormDesigner::Form::DontRaise); } } //! @todo eventually, update property pane #else Q_UNUSED(sourcePartClass); Q_UNUSED(sourceName); Q_UNUSED(fields); Q_UNUSED(targetContainer); Q_UNUSED(_pos); #endif } void KexiFormView::setUnsavedLocalBLOB(QWidget *widget, KexiBLOBBuffer::Id_t id) { //! @todo if there already was data assigned, remember it should be dereferenced if (id == 0) tempData()->unsavedLocalBLOBs.remove(widget); else tempData()->unsavedLocalBLOBs.insert(widget, id); } void KexiFormView::updateActions(bool activated) { if (viewMode()==Kexi::DesignViewMode) { if (activated) { form()->emitActionSignals(); formPart()->widgetTreePage()->setForm(form()); } } KexiDataAwareView::updateActions(activated); updateActionsInternal(); } void KexiFormView::slotWidgetNameChanged(const QByteArray& oldname, const QByteArray& newname) { Q_UNUSED(oldname); Q_UNUSED(newname); //qDebug() << oldname << newname << form()->propertySet().propertyValue("objectName").toString(); KexiMainWindowIface::global()->updatePropertyEditorInfoLabel(); formPart()->dataSourcePage()->updateInfoLabelForPropertySet(form()->propertySet()); } void KexiFormView::slotWidgetSelectionChanged(QWidget *w, KFormDesigner::Form::WidgetSelectionFlags flags) { Q_UNUSED(w) Q_UNUSED(flags) updateActionsInternal(); } void KexiFormView::updateActionsInternal() { const QWidget* selectedWidget = form()->selectedWidget(); //qDebug() << selectedWidget << (viewMode()==Kexi::DesignViewMode) << widget_assign_action; QByteArray wClass; if (selectedWidget) { wClass = selectedWidget->metaObject()->className(); //qDebug() << wClass; } QAction *widget_assign_action = KexiFormManager::self()->action("widget_assign_action"); if (widget_assign_action) { widget_assign_action->setEnabled( viewMode()==Kexi::DesignViewMode && selectedWidget && (wClass == "QPushButton" || wClass == "KPushButton" || wClass == "KexiDBPushButton" || wClass == "KexiPushButton" || wClass == "KexiDBCommandLinkButton") ); } #ifdef KEXI_DEBUG_GUI QAction *show_form_ui_action = KexiFormManager::self()->action("show_form_ui"); if (show_form_ui_action) { show_form_ui_action->setEnabled(viewMode()==Kexi::DesignViewMode); } #endif } diff --git a/src/plugins/forms/widgets/kexidbautofield.cpp b/src/plugins/forms/widgets/kexidbautofield.cpp index 3afd256b8..25d33c6a1 100644 --- a/src/plugins/forms/widgets/kexidbautofield.cpp +++ b/src/plugins/forms/widgets/kexidbautofield.cpp @@ -1,814 +1,815 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2005 Christian Nitschkowski Copyright (C) 2005-2007 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 "kexidbautofield.h" #include "kexidbcheckbox.h" #include "kexidbimagebox.h" #include "kexidblabel.h" #include "kexidblineedit.h" #include "kexidbtextedit.h" #include "kexidbcombobox.h" #include "KexiDBPushButton.h" #include "kexidbform.h" #include #include #include #include #include #include #include #include #include #define KexiDBAutoField_SPACING 10 //10 pixel for spacing between a label and an editor widget //! @internal class Q_DECL_HIDDEN KexiDBAutoField::Private { public: Private() { } WidgetType widgetType; //!< internal: equal to m_widgetType_property or equal to result //!< of widgetTypeForFieldType() if widgetTypeForFieldType is Auto WidgetType widgetType_property; //!< provides widget type or Auto LabelPosition lblPosition; QBoxLayout *layout; QLabel *label; QString caption; KDbField::Type fieldTypeInternal; QString fieldCaptionInternal; QBrush baseBrush; //!< needed because for unbound mode editor==0 QBrush textBrush; //!< needed because for unbound mode editor==0 bool autoCaption; bool focusPolicyChanged; + KDbConnection *conn = nullptr; }; //------------------------------------- KexiDBAutoField::KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos, QWidget *parent) : QWidget(parent) , KexiFormDataItemInterface() , KFormDesigner::DesignTimeDynamicChildWidgetHandler() , d(new Private()) { init(text, type, pos); } KexiDBAutoField::KexiDBAutoField(QWidget *parent, LabelPosition pos) : QWidget(parent) , KexiFormDataItemInterface() , KFormDesigner::DesignTimeDynamicChildWidgetHandler() , d(new Private()) { init(QString()/*xi18n("Auto Field")*/, Auto, pos); } KexiDBAutoField::~KexiDBAutoField() { setUpdatesEnabled(false); if (subwidget()) subwidget()->setUpdatesEnabled(false); delete d; } void KexiDBAutoField::init(const QString &text, WidgetType type, LabelPosition pos) { d->fieldTypeInternal = KDbField::InvalidType; d->layout = 0; setSubwidget(0); d->label = new QLabel(text, this); d->label->installEventFilter(this); d->autoCaption = true; d->focusPolicyChanged = false; d->widgetType = Auto; d->widgetType_property = (type == Auto ? Text : type); //to force "differ" to be true in setWidgetType() setLabelPosition(pos); setWidgetType(type); d->baseBrush = palette().base(); d->textBrush = palette().text(); } void KexiDBAutoField::setWidgetType(WidgetType type) { const bool differ = (type != d->widgetType_property); d->widgetType_property = type; if (differ) { if (type == Auto) {// try to guess type from data source type if (visibleColumnInfo()) d->widgetType = KexiDBAutoField::widgetTypeForFieldType(visibleColumnInfo()->field()->type()); else d->widgetType = Auto; } else d->widgetType = d->widgetType_property; createEditor(); } } void KexiDBAutoField::createEditor() { if (subwidget()) { delete(QWidget *)subwidget(); } QWidget *newSubwidget; //qDebug() << "widgetType:" << d->widgetType; switch (d->widgetType) { case Text: case Double: //! @todo setup validator case Integer: //! @todo setup validator case Date: case Time: case DateTime: { KexiDBLineEdit *le = new KexiDBLineEdit(this); newSubwidget = le; le->setFrame(false); break; } case MultiLineText: newSubwidget = new KexiDBTextEdit(this); break; case Boolean: newSubwidget = new KexiDBCheckBox(dataSource(), this); break; case Image: newSubwidget = new KexiDBImageBox(designMode(), this); break; case ComboBox: { KexiDBComboBox *cbox = new KexiDBComboBox(this); newSubwidget = cbox; cbox->setDesignMode(designMode()); break; } default: newSubwidget = 0; changeText(d->caption); break; } //qDebug() << newSubwidget; setSubwidget(newSubwidget); //this will also allow to declare subproperties, see KFormDesigner::WidgetWithSubpropertiesInterface if (newSubwidget) { newSubwidget->setObjectName( QString::fromLatin1("KexiDBAutoField_") + newSubwidget->metaObject()->className()); KexiDataItemInterface *iface = dynamic_cast(newSubwidget); if (iface) { iface->setParentDataItemInterface(this); } KexiFormDataItemInterface *formIface = dynamic_cast(newSubwidget); if (formIface) { - formIface->setColumnInfo(columnInfo()); //needed at least by KexiDBImageBox + formIface->setColumnInfo(d->conn, columnInfo()); //needed at least by KexiDBImageBox formIface->setVisibleColumnInfo(visibleColumnInfo()); //needed at least by KexiDBComboBox } newSubwidget->setProperty("dataSource", dataSource()); //needed at least by KexiDBImageBox KFormDesigner::DesignTimeDynamicChildWidgetHandler::childWidgetAdded(this); newSubwidget->show(); d->label->setBuddy(newSubwidget); if (d->focusPolicyChanged) {//if focusPolicy is changed at top level, editor inherits it newSubwidget->setFocusPolicy(focusPolicy()); } else {//if focusPolicy is not changed at top level, inherit it from editor QWidget::setFocusPolicy(newSubwidget->focusPolicy()); } setFocusProxy(newSubwidget); //ok? if (parentWidget()) newSubwidget->setPalette(qApp->palette()); copyPropertiesToEditor(); } setLabelPosition(labelPosition()); } void KexiDBAutoField::copyPropertiesToEditor() { //qDebug() << subwidget(); if (subwidget()) { // qDebug() << "base col: " << d->baseColor.name() << // "; text col: " << d->textColor.name(); QPalette p(subwidget()->palette()); p.setBrush(QPalette::Base, d->baseBrush); if (d->widgetType == Boolean) p.setBrush(QPalette::Foreground, d->textBrush); else p.setBrush(QPalette::Text, d->textBrush); subwidget()->setPalette(p); } } void KexiDBAutoField::setLabelPosition(LabelPosition position) { d->lblPosition = position; if (d->layout) { QBoxLayout *lyr = d->layout; d->layout = 0; delete lyr; } if (subwidget()) subwidget()->show(); //! \todo support right-to-left layout where positions are inverted if (position == Top || position == Left) { Qt::Alignment align = d->label->alignment(); if (position == Top) { d->layout = (QBoxLayout*) new QVBoxLayout(this); align |= Qt::AlignVertical_Mask; align ^= Qt::AlignVertical_Mask; align |= Qt::AlignTop; } else { d->layout = (QBoxLayout*) new QHBoxLayout(this); align |= Qt::AlignVertical_Mask; align ^= Qt::AlignVertical_Mask; align |= Qt::AlignVCenter; } d->label->setAlignment(align); if (d->widgetType == Boolean || (d->widgetType == Auto && fieldTypeInternal() == KDbField::InvalidType && !designMode())) { d->label->hide(); } else { d->label->show(); } d->layout->addWidget(d->label, 0, position == Top ? Qt::AlignLeft : QFlags(0)); if (position == Left && d->widgetType != Boolean) d->layout->addSpacing(KexiDBAutoField_SPACING); d->layout->addWidget(subwidget(), 1); KexiSubwidgetInterface *subwidgetInterface = dynamic_cast(subwidget()); if (subwidgetInterface) { if (subwidgetInterface->appendStretchRequired(this)) d->layout->addStretch(0); if (subwidgetInterface->subwidgetStretchRequired(this)) { QSizePolicy sizePolicy(subwidget()->sizePolicy()); if (position == Left) { sizePolicy.setHorizontalPolicy(QSizePolicy::Minimum); d->label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); } else { sizePolicy.setVerticalPolicy(QSizePolicy::Minimum); d->label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } subwidget()->setSizePolicy(sizePolicy); } } } else { d->layout = (QBoxLayout*) new QHBoxLayout(this); d->label->hide(); d->layout->addWidget(subwidget()); } //a hack to force layout to be refreshed (any better idea for this?) resize(size() + QSize(1, 0)); resize(size() - QSize(1, 0)); KexiDBAutoField* autoField = dynamic_cast(subwidget()); if (autoField) { //needed for KexiDBComboBox autoField->setLabelPosition(position); } } void KexiDBAutoField::setInvalidState(const QString &text) { // Widget with an invalid dataSource is just a QLabel if (designMode()) return; d->widgetType = Auto; createEditor(); setFocusPolicy(Qt::NoFocus); if (subwidget()) subwidget()->setFocusPolicy(Qt::NoFocus); //! @todo or set this to editor's text? d->label->setText(text); } bool KexiDBAutoField::isReadOnly() const { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->isReadOnly(); else return false; } void KexiDBAutoField::setReadOnly(bool readOnly) { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->setReadOnly(readOnly); } void KexiDBAutoField::setValueInternal(const QVariant& add, bool removeOld) { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->setValue(KexiDataItemInterface::originalValue(), add, removeOld); } QVariant KexiDBAutoField::value() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->value(); return QVariant(); } bool KexiDBAutoField::valueIsNull() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->valueIsNull(); return true; } bool KexiDBAutoField::valueIsEmpty() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->valueIsEmpty(); return true; } bool KexiDBAutoField::valueIsValid() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->valueIsValid(); return true; } bool KexiDBAutoField::valueChanged() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); //qDebug() << KexiDataItemInterface::originalValue(); if (iface) return iface->valueChanged(); return false; } void KexiDBAutoField::installListener(KexiDataItemChangesListener* listener) { KexiFormDataItemInterface::installListener(listener); KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->installListener(listener); } KexiDBAutoField::WidgetType KexiDBAutoField::widgetType() const { return d->widgetType_property; } KexiDBAutoField::LabelPosition KexiDBAutoField::labelPosition() const { return d->lblPosition; } QString KexiDBAutoField::caption() const { return d->caption; } bool KexiDBAutoField::hasAutoCaption() const { return d->autoCaption; } QWidget* KexiDBAutoField::editor() const { return subwidget(); } QLabel* KexiDBAutoField::label() const { return d->label; } int KexiDBAutoField::fieldTypeInternal() const { return d->fieldTypeInternal; } QString KexiDBAutoField::fieldCaptionInternal() const { return d->fieldCaptionInternal; } bool KexiDBAutoField::cursorAtStart() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->cursorAtStart(); return false; } bool KexiDBAutoField::cursorAtEnd() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) return iface->cursorAtEnd(); return false; } void KexiDBAutoField::clear() { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->clear(); } void KexiDBAutoField::setFieldTypeInternal(int kexiDBFieldType) { d->fieldTypeInternal = (KDbField::Type)kexiDBFieldType; KDbField::Type fieldType; //find real fied type to use if (d->fieldTypeInternal == KDbField::InvalidType) { if (visibleColumnInfo()) fieldType = KDbField::Text; else fieldType = KDbField::InvalidType; } else fieldType = d->fieldTypeInternal; const WidgetType newWidgetType = KexiDBAutoField::widgetTypeForFieldType(fieldType); if (d->widgetType != newWidgetType) { d->widgetType = newWidgetType; createEditor(); } setFieldCaptionInternal(d->fieldCaptionInternal); } void KexiDBAutoField::setFieldCaptionInternal(const QString& text) { d->fieldCaptionInternal = text; //change text only if autocaption is set and no columnInfo is available KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if ((!iface || !iface->columnInfo()) && d->autoCaption) { changeText(d->fieldCaptionInternal); } } void -KexiDBAutoField::setColumnInfo(KDbQueryColumnInfo* cinfo) +KexiDBAutoField::setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); + d->conn = conn; setColumnInfoInternal(cinfo, cinfo); } -void -KexiDBAutoField::setColumnInfoInternal(KDbQueryColumnInfo* cinfo, KDbQueryColumnInfo* visibleColumnInfo) +void KexiDBAutoField::setColumnInfoInternal(KDbQueryColumnInfo *cinfo, + KDbQueryColumnInfo *visibleColumnInfo) { // change widget type depending on field type if (d->widgetType_property == Auto) { WidgetType newWidgetType = Auto; KDbField::Type fieldType; if (cinfo) fieldType = visibleColumnInfo->field()->type(); else if (dataSource().isEmpty()) fieldType = KDbField::InvalidType; else fieldType = KDbField::Text; if (fieldType != KDbField::InvalidType) { newWidgetType = KexiDBAutoField::widgetTypeForFieldType(fieldType); } if (d->widgetType != newWidgetType || newWidgetType == Auto) { d->widgetType = newWidgetType; createEditor(); } } // update label's text changeText((cinfo && d->autoCaption) ? cinfo->captionOrAliasOrName() : d->caption); KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) - iface->setColumnInfo(visibleColumnInfo); + iface->setColumnInfo(d->conn, visibleColumnInfo); } //static KexiDBAutoField::WidgetType KexiDBAutoField::widgetTypeForFieldType(KDbField::Type type) { switch (type) { case KDbField::Integer: case KDbField::ShortInteger: case KDbField::BigInteger: return Integer; case KDbField::Boolean: return Boolean; case KDbField::Float: case KDbField::Double: return Double; case KDbField::Date: return Date; case KDbField::DateTime: return DateTime; case KDbField::Time: return Time; case KDbField::Text: return Text; case KDbField::LongText: return MultiLineText; case KDbField::Enum: return ComboBox; case KDbField::InvalidType: return Auto; case KDbField::BLOB: return Image; default: break; } return Text; } void KexiDBAutoField::changeText(const QString &text, bool beautify) { QString realText; bool unbound = false; if (d->autoCaption && (d->widgetType == Auto || dataSource().isEmpty())) { if (designMode()) realText = futureI18nc2("Unbound Auto Field", "%1 (unbound)", objectName()); else realText.clear(); unbound = true; } else { if (beautify) { /*! @todo look at appendColonToAutoLabels setting [bool] @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] (see doc/dev/settings.txt) */ if (!text.isEmpty()) { realText = text[0].toUpper() + text.mid(1); if (d->widgetType != Boolean) { //! @todo ":" suffix looks weird for checkbox; remove this condition when [x] is displayed _after_ label //! @todo support right-to-left layout where position of ":" is inverted realText += ": "; } } } else realText = text; } if (unbound) { d->label->setAlignment(Qt::AlignCenter); d->label->setWordWrap(true); } else { d->label->setAlignment(Qt::AlignCenter); } if (d->widgetType == Boolean) { static_cast((QWidget*)subwidget())->setText(realText); } else { d->label->setText(realText); } } void KexiDBAutoField::setCaption(const QString &caption) { d->caption = caption; if (!d->autoCaption && !caption.isEmpty()) changeText(d->caption); } void KexiDBAutoField::setAutoCaption(bool autoCaption) { d->autoCaption = autoCaption; if (d->autoCaption) { if (columnInfo()) { changeText(columnInfo()->captionOrAliasOrName()); } else { changeText(d->fieldCaptionInternal); } } else changeText(d->caption); } void KexiDBAutoField::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); if (ds.isEmpty()) { - setColumnInfo(0); + setColumnInfo(d->conn, nullptr); } } QSize KexiDBAutoField::sizeHint() const { if (d->lblPosition == NoLabel) return subwidget() ? subwidget()->sizeHint() : QWidget::sizeHint(); QSize s1(0, 0); if (subwidget()) s1 = subwidget()->sizeHint(); QSize s2(d->label->sizeHint()); if (d->lblPosition == Top) return QSize(qMax(s1.width(), s2.width()), s1.height() + KexiDBAutoField_SPACING + s2.height()); //left return QSize(s1.width() + KexiDBAutoField_SPACING + s2.width(), qMax(s1.height(), s2.height())); } void KexiDBAutoField::setFocusPolicy(Qt::FocusPolicy policy) { d->focusPolicyChanged = true; QWidget::setFocusPolicy(policy); d->label->setFocusPolicy(policy); if (subwidget()) subwidget()->setFocusPolicy(policy); } void KexiDBAutoField::updateInformationAboutUnboundField() { if ((d->autoCaption && (dataSource().isEmpty() || dataSourcePluginId().isEmpty())) || (!d->autoCaption && d->caption.isEmpty())) { d->label->setText(futureI18nc2("Unbound Auto Field", "%1 (unbound)", objectName())); } } void KexiDBAutoField::paletteChange(const QPalette& oldPal) { Q_UNUSED(oldPal); d->label->setPalette(palette()); } void KexiDBAutoField::unsetPalette() { setPalette(QPalette()); } // ===== methods below are just proxies for the internal editor or label ===== QColor KexiDBAutoField::paletteForegroundColor() const { //! @todo how about brush? return d->textBrush.color(); } void KexiDBAutoField::setPaletteForegroundColor(const QColor & color) { //! @todo how about brush? d->textBrush.setColor(color); copyPropertiesToEditor(); } QColor KexiDBAutoField::paletteBackgroundColor() const { //! @todo how about brush? return d->baseBrush.color(); } void KexiDBAutoField::setPaletteBackgroundColor(const QColor & color) { //qDebug(); //! @todo how about brush? d->baseBrush.setColor(color); copyPropertiesToEditor(); } QColor KexiDBAutoField::foregroundLabelColor() const { if (d->widgetType == Boolean) return paletteForegroundColor(); return d->label->palette().color(d->label->foregroundRole()); } void KexiDBAutoField::setForegroundLabelColor(const QColor & color) { if (d->widgetType == Boolean) setPaletteForegroundColor(color); else { QPalette pal(d->label->palette()); pal.setColor(d->label->foregroundRole(), color); d->label->setPalette(pal); pal = palette(); pal.setColor(foregroundRole(), color); setPalette(pal); } } QColor KexiDBAutoField::backgroundLabelColor() const { if (d->widgetType == Boolean) return paletteBackgroundColor(); return d->label->palette().color(d->label->backgroundRole()); } void KexiDBAutoField::setBackgroundLabelColor(const QColor & color) { if (d->widgetType == Boolean) setPaletteBackgroundColor(color); else { QPalette pal(d->label->palette()); pal.setColor(d->label->backgroundRole(), color); d->label->setPalette(pal); pal = palette(); pal.setColor(backgroundRole(), color); setPalette(pal); } } QVariant KexiDBAutoField::property(const char * name) const { bool ok; QVariant val = KFormDesigner::WidgetWithSubpropertiesInterface::subproperty(name, &ok); if (ok) return val; return QWidget::property(name); } bool KexiDBAutoField::setProperty(const char * name, const QVariant & value) { bool ok = KFormDesigner::WidgetWithSubpropertiesInterface::setSubproperty(name, value); if (ok) return true; return QWidget::setProperty(name, value); } bool KexiDBAutoField::eventFilter(QObject *o, QEvent *e) { if (o == d->label && d->label->buddy() && e->type() == QEvent::MouseButtonRelease) { //focus label's buddy when user clicked the label d->label->buddy()->setFocus(); } return QWidget::eventFilter(o, e); } void KexiDBAutoField::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) { KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); KexiFormDataItemInterface *formIface = dynamic_cast(subwidget()); if (formIface) formIface->setDisplayDefaultValue(subwidget(), displayDefaultValue); } void KexiDBAutoField::moveCursorToEnd() { KexiDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->moveCursorToEnd(); } void KexiDBAutoField::moveCursorToStart() { KexiDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->moveCursorToStart(); } void KexiDBAutoField::selectAll() { KexiDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->selectAll(); } bool KexiDBAutoField::keyPressed(QKeyEvent *ke) { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface && iface->keyPressed(ke)) return true; return false; } - diff --git a/src/plugins/forms/widgets/kexidbautofield.h b/src/plugins/forms/widgets/kexidbautofield.h index ab563db22..fc378f51c 100644 --- a/src/plugins/forms/widgets/kexidbautofield.h +++ b/src/plugins/forms/widgets/kexidbautofield.h @@ -1,218 +1,218 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2005 Christian Nitschkowski 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 KEXIDBAUTOFIELD_H #define KEXIDBAUTOFIELD_H #include "kexiformutils_export.h" #include #include #include #include #include #include #include class QLabel; //! Universal "Auto Field" widget for Kexi forms /*! It acts as a container for most data-aware widgets. */ class KEXIFORMUTILS_EXPORT KexiDBAutoField : public QWidget, public KexiFormDataItemInterface, public KFormDesigner::DesignTimeDynamicChildWidgetHandler, public KFormDesigner::WidgetWithSubpropertiesInterface, public KFormDesigner::FormWidgetInterface { Q_OBJECT //'caption' is uncovered now Q_PROPERTY(QString labelCaption READ caption WRITE setCaption) Q_PROPERTY(QString caption READ caption WRITE setCaption) Q_PROPERTY(QColor foregroundLabelColor READ foregroundLabelColor WRITE setForegroundLabelColor RESET unsetPalette) Q_PROPERTY(QColor backgroundLabelColor READ backgroundLabelColor WRITE setBackgroundLabelColor RESET unsetPalette) Q_PROPERTY(bool autoCaption READ hasAutoCaption WRITE setAutoCaption) Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource) Q_PROPERTY(QString dataSourcePartClass READ dataSourcePluginId WRITE setDataSourcePluginId) Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) Q_PROPERTY(LabelPosition labelPosition READ labelPosition WRITE setLabelPosition) Q_PROPERTY(WidgetType widgetType READ widgetType WRITE setWidgetType) /*internal, for design time only*/ Q_PROPERTY(int fieldTypeInternal READ fieldTypeInternal WRITE setFieldTypeInternal STORED false) Q_PROPERTY(QString fieldCaptionInternal READ fieldCaptionInternal WRITE setFieldCaptionInternal STORED false) public: enum WidgetType { Auto = 100, Text, Integer, Double, Boolean, Date, Time, DateTime, MultiLineText, ComboBox, Image }; Q_ENUM(WidgetType) enum LabelPosition { Left = 300, Top, NoLabel }; Q_ENUM(LabelPosition) KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos, QWidget *parent = 0); explicit KexiDBAutoField(QWidget *parent = 0, LabelPosition pos = Left); virtual ~KexiDBAutoField(); inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } inline QString dataSourcePluginId() const { return KexiFormDataItemInterface::dataSourcePluginId(); } virtual void setDataSource(const QString &ds); virtual void setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); } - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; virtual void setInvalidState(const QString& text); virtual bool isReadOnly() const; virtual void setReadOnly(bool readOnly); virtual QVariant value(); virtual bool valueIsNull(); virtual bool valueIsEmpty(); virtual bool valueIsValid(); virtual bool valueChanged(); virtual void clear(); //! Reimplemented to also install \a listenter for internal editor virtual void installListener(KexiDataItemChangesListener* listener); WidgetType widgetType() const; void setWidgetType(WidgetType type); LabelPosition labelPosition() const; virtual void setLabelPosition(LabelPosition position); QString caption() const; void setCaption(const QString &caption); bool hasAutoCaption() const; void setAutoCaption(bool autoCaption); /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). \a widget is equal to 'this'. Reimplemented after KexiFormDataItemInterface. */ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); QWidget* editor() const; QLabel* label() const; virtual bool cursorAtStart(); virtual bool cursorAtEnd(); static WidgetType widgetTypeForFieldType(KDbField::Type type); /*! On design time it is not possible to pass a reference to KDbField object so we're just providing field type. Only used when widget type is Auto. @internal */ void setFieldTypeInternal(int kexiDBFieldType); /*! On design time it is not possible to pass a reference to KDbField object so we're just providing field caption. Only used when widget type is Auto. @internal */ void setFieldCaptionInternal(const QString& text); /*! @internal */ int fieldTypeInternal() const; /*! @internal */ QString fieldCaptionInternal() const; virtual QSize sizeHint() const; virtual void setFocusPolicy(Qt::FocusPolicy policy); //! Reimplemented to return internal editor's color. QColor paletteForegroundColor() const; //! Reimplemented to set internal editor's color. void setPaletteForegroundColor(const QColor & color); //! Reimplemented to return internal editor's color. QColor paletteBackgroundColor() const; //! Reimplemented to set internal editor's color. virtual void setPaletteBackgroundColor(const QColor & color); //! \return label's foreground color QColor foregroundLabelColor() const; //! Sets label's foreground color virtual void setForegroundLabelColor(const QColor & color); //! \return label's background color QColor backgroundLabelColor() const; //! Sets label's background color virtual void setBackgroundLabelColor(const QColor & color); //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface virtual QVariant property(const char * name) const; //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface virtual bool setProperty(const char * name, const QVariant & value); /*! Called by the top-level form on key press event to consume widget-specific shortcuts. */ virtual bool keyPressed(QKeyEvent *ke); public Q_SLOTS: virtual void unsetPalette(); protected Q_SLOTS: virtual void paletteChange(const QPalette& oldPal); //! Implemented for KexiDataItemInterface virtual void moveCursorToEnd(); //! Implemented for KexiDataItemInterface virtual void moveCursorToStart(); //! Implemented for KexiDataItemInterface virtual void selectAll(); protected: virtual void setValueInternal(const QVariant&add, bool removeOld); void init(const QString &text, WidgetType type, LabelPosition pos); virtual void createEditor(); void changeText(const QString &text, bool beautify = true); void updateInformationAboutUnboundField(); //! internal editor can be created too late, so certain properties should be copied void copyPropertiesToEditor(); virtual bool eventFilter(QObject *o, QEvent *e); //! Used by @ref setLabelPositionInternal(LabelPosition) void setLabelPositionInternal(LabelPosition position, bool noLabel); //! Used by KexiDBAutoField::setColumnInfo() and KexiDBComboBox::setColumnInfo() - void setColumnInfoInternal(KDbQueryColumnInfo* cinfo, KDbQueryColumnInfo* visibleColumnInfo); + void setColumnInfoInternal(KDbQueryColumnInfo *cinfo, KDbQueryColumnInfo *visibleColumnInfo); private: class Private; Private * const d; }; #endif diff --git a/src/plugins/forms/widgets/kexidbcombobox.cpp b/src/plugins/forms/widgets/kexidbcombobox.cpp index 9a3cd476b..34f862b34 100644 --- a/src/plugins/forms/widgets/kexidbcombobox.cpp +++ b/src/plugins/forms/widgets/kexidbcombobox.cpp @@ -1,609 +1,614 @@ /* This file is part of the KDE project Copyright (C) 2006-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 "kexidbcombobox.h" #include "kexidblineedit.h" #include "kexiformscrollview.h" #include #include #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiDBComboBox::Private { public: Private() : popup(0) , visibleColumnInfo(0) , isEditable(false) , buttonPressed(false) , mouseOver(false) , dataEnteredByHand(true) { } ~Private() { } + KDbConnection *connection = nullptr; KexiComboBoxPopup *popup; KComboBox *paintedCombo; //!< fake combo used only to pass it as 'this' for QStyle (because styles use ) QSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(), //!< rebuilt by a font change event and style change event KDbQueryColumnInfo* visibleColumnInfo; //! used for collecting subwidgets and their childrens (if isEditable is false) QList subWidgetsWithDisabledEvents; bool isEditable; //!< true if the combo box is editable bool buttonPressed; bool mouseOver; bool dataEnteredByHand; }; //------------------------------------- KexiDBComboBox::KexiDBComboBox(QWidget *parent) : KexiDBAutoField(parent, NoLabel) , KexiComboBoxBase() , d(new Private()) { //! @todo fix creating popup for forms instead; remove KexiComboBoxBase::m_setReinstantiatePopupOnShow m_reinstantiatePopupOnShow = true; m_focusPopupBeforeShow = true; setMouseTracking(true); setFocusPolicy(Qt::WheelFocus); installEventFilter(this); d->paintedCombo = new KComboBox(this); d->paintedCombo->hide(); d->paintedCombo->move(0, 0); } KexiDBComboBox::~KexiDBComboBox() { delete d; } KDbTableViewColumn* KexiDBComboBox::column() { return nullptr; } KDbField* KexiDBComboBox::field() { return KexiDBAutoField::field(); } QVariant KexiDBComboBox::origValue() const { return KexiDataItemInterface::originalValue(); } QVariant KexiDBComboBox::value() { return KexiComboBoxBase::value(); } KexiComboBoxPopup *KexiDBComboBox::popup() const { return d->popup; } void KexiDBComboBox::setPopup(KexiComboBoxPopup *popup) { d->popup = popup; if (popup) { connect(popup, SIGNAL(hidden()), this, SLOT(slotPopupHidden())); } } void KexiDBComboBox::slotPopupHidden() { moveCursorToEnd(); selectAll(); } void KexiDBComboBox::setEditable(bool set) { if (d->isEditable == set) return; d->isEditable = set; d->paintedCombo->setEditable(set); if (set) createEditor(); else { delete subwidget(); setSubwidget(0); } update(); } bool KexiDBComboBox::isEditable() const { return d->isEditable; } void KexiDBComboBox::paintEvent(QPaintEvent *) { QPainter p(this); p.setPen(palette().color(QPalette::Text)); QPalette pal(palette()); pal.setColor(QPalette::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color if (width() < 5 || height() < 5) { qDrawShadePanel(&p, rect(), pal, false /* !sunken */, 2 /*line width*/, &pal.brush(QPalette::Button)/*fill*/); return; } //! @todo QStyleOptionComboBox option; option.palette = pal; option.initFrom(d->paintedCombo); if (isEnabled()) option.state |= QStyle::State_Enabled; if (hasFocus()) option.state |= QStyle::State_HasFocus; if (d->mouseOver) option.state |= QStyle::State_MouseOver; style()->drawComplexControl(QStyle::CC_ComboBox, &option, &p, d->paintedCombo); //! @todo support reverse layout #if 0 //TODO //bool reverse = QApplication::reverseLayout(); style()->drawComplexControl(QStyle::CC_ComboBox, &option, &p, d->paintedCombo /*this*/ flags, (int)QStyle::SC_All, (d->buttonPressed ? QStyle::SC_ComboBoxArrow : QStyle::SC_None) ); if (d->isEditable) { //if editable, editor paints itself, nothing to do } else { //not editable: we need to paint the current item QRect editorGeometry(this->editorGeometry()); if (hasFocus()) { if (0 == qstrcmp(style()->name(), "windows")) //a hack p.fillRect(editorGeometry, palette().brush(QPalette::Highlight)); QRect r(QStyle::visualRect(style()->subRect(QStyle::SR_ComboBoxFocusRect, d->paintedCombo), this)); r = QRect(r.left() - 1, r.top() - 1, r.width() + 2, r.height() + 2); //enlare by 1 pixel each side to avoid covering by the subwidget style()->drawPrimitive(QStyle::PE_FocusRect, &p, r, cg, flags | QStyle::Style_FocusAtBorder, QStyleOption(cg.highlight())); } //todo } #endif } QRect KexiDBComboBox::editorGeometry() const { //! @todo 20080316, sebsauer; crashes here with; #if 0 QRect r(QStyle::visualRect( qApp->layoutDirection(), d->paintedCombo->geometry(), style()->subControlRect(QStyle::CC_ComboBox, 0, QStyle::SC_ComboBoxEditField, d->paintedCombo))); #else QRect r = d->paintedCombo->geometry(); r.setSize(size()); #endif return r; } void KexiDBComboBox::createEditor() { KexiDBAutoField::createEditor(); if (subwidget()) { subwidget()->setGeometry(editorGeometry()); if (!d->isEditable) { QStyleOptionComboBox option; option.initFrom(subwidget()); const QRect comboRect = subwidget()->style()->subControlRect( QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxEditField, subwidget()); //qDebug() << "comboRect:" << comboRect; subwidget()->setContentsMargins(comboRect.left(), comboRect.top(), width() - comboRect.right(), height() - comboRect.bottom()); int l, t, r, b; subwidget()->getContentsMargins(&l, &t, &r, &b); //qDebug() << "altered margins:" << l << t << r << b; subwidget()->setFocusPolicy(Qt::NoFocus); setFocusProxy(0); // Subwidget is not focusable but the form requires focusable // widget in order to manage data updates so let it be this KexiDBComboBox. subwidget()->setCursor(QCursor(Qt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that QPalette subwidgetPalette(subwidget()->palette()); subwidgetPalette.setColor(QPalette::Base, Qt::transparent); subwidget()->setPalette(subwidgetPalette); d->subWidgetsWithDisabledEvents.clear(); d->subWidgetsWithDisabledEvents << subwidget(); if (!designMode()) { subwidget()->installEventFilter(this); } QList widgets(subwidget()->findChildren()); foreach(QWidget *widget, widgets) { d->subWidgetsWithDisabledEvents << widget; widget->installEventFilter(this); } } } updateGeometry(); } void KexiDBComboBox::setLabelPosition(LabelPosition position) { if (subwidget()) { if (-1 != subwidget()->metaObject()->indexOfProperty("frameShape")) { subwidget()->setProperty("frameShape", QVariant((int)QFrame::NoFrame)); } subwidget()->setGeometry(editorGeometry()); } // update size policy QSizePolicy sizePolicy(this->sizePolicy()); if (position == Left) sizePolicy.setHorizontalPolicy(QSizePolicy::Minimum); else sizePolicy.setVerticalPolicy(QSizePolicy::Minimum); setSizePolicy(sizePolicy); } QRect KexiDBComboBox::buttonGeometry() const { QRect arrowRect( style()->subControlRect( QStyle::CC_ComboBox, 0, QStyle::SC_ComboBoxArrow, d->paintedCombo)); //! @todo ok? arrowRect = QStyle::visualRect( qApp->layoutDirection(), d->paintedCombo->geometry(), arrowRect); arrowRect.setHeight(qMax(height() - (2 * arrowRect.y()), arrowRect.height())); // a fix for Motif style return arrowRect; } bool KexiDBComboBox::handleMousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton || designMode()) return true; if (!isEditable() || buttonGeometry().contains(e->pos())) { d->buttonPressed = false; showPopup(); return true; } return false; } bool KexiDBComboBox::handleKeyPressEvent(QKeyEvent *ke) { const int k = ke->key(); const bool dropDown = (ke->modifiers() == Qt::NoModifier && ((k == Qt::Key_F2 && !d->isEditable) || k == Qt::Key_F4)) || (ke->modifiers() == Qt::AltModifier && k == Qt::Key_Down); const bool escPressed = ke->modifiers() == Qt::NoModifier && k == Qt::Key_Escape; const bool popupVisible = popup() && popup()->isVisible(); if ((dropDown || escPressed) && popupVisible) { popup()->hide(); return true; } else if (dropDown && !popupVisible) { d->buttonPressed = false; showPopup(); return true; } else if (popupVisible) { const bool enterPressed = k == Qt::Key_Enter || k == Qt::Key_Return; if (enterPressed/* && m_internalEditorValueChanged*/) { acceptPopupSelection(); return true; } return handleKeyPressForPopup(ke); } return false; } bool KexiDBComboBox::keyPressed(QKeyEvent *ke) { if (KexiDBAutoField::keyPressed(ke)) return true; const int k = ke->key(); const bool popupVisible = popup() && popup()->isVisible(); const bool escPressed = ke->modifiers() == Qt::NoModifier && k == Qt::Key_Escape; if (escPressed && popupVisible) { popup()->hide(); return true; } if (ke->modifiers() == Qt::NoModifier && (k == Qt::Key_PageDown || k == Qt::Key_PageUp) && popupVisible) { return true; } return false; } void KexiDBComboBox::mousePressEvent(QMouseEvent *e) { if (handleMousePressEvent(e)) return; KexiDBAutoField::mousePressEvent(e); } void KexiDBComboBox::mouseDoubleClickEvent(QMouseEvent *e) { mousePressEvent(e); } void KexiDBComboBox::changeEvent(QEvent * event) { switch(event->type()) { case QEvent::FontChange: case QEvent::StyleChange: d->sizeHint = QSize(); //force rebuild the cache break; default:; } switch(event->type()) { case QEvent::StyleChange: if (subwidget()) { subwidget()->setGeometry(editorGeometry()); } default:; } KexiDBAutoField::changeEvent(event); } bool KexiDBComboBox::eventFilter(QObject *o, QEvent *e) { #if 0 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 << subwidget(); qDebug() << "FOCUS WIDGET:" << focusWidget(); } #endif if (o == this || o == popup() || o == subwidget()) { if (e->type() == QEvent::KeyPress) { // handle F2/F4 if (handleKeyPressEvent(static_cast(e))) return true; } } if (o == this) { if (e->type() == QEvent::Resize) { d->paintedCombo->resize(size()); if (subwidget()) subwidget()->setGeometry(editorGeometry()); } else if (e->type() == QEvent::Enter) { if (!d->isEditable || /*over button if editable combo*/buttonGeometry().contains(static_cast(e)->pos())) { d->mouseOver = true; update(); } } else if (e->type() == QEvent::MouseMove) { if (d->isEditable) { const bool overButton = buttonGeometry().contains(static_cast(e)->pos()); if (overButton != d->mouseOver) { d->mouseOver = overButton; update(); } } } else if (e->type() == QEvent::Leave) { d->mouseOver = false; update(); } else if (e->type() == QEvent::FocusOut || e->type() == QEvent::Hide) { if (!d->isEditable) { moveCursorToEnd(); } if (popup()) { popup()->hide(); } if (popup() && popup()->isVisible()) { undoChanges(); } return true; } } if (!d->isEditable && d->subWidgetsWithDisabledEvents.contains(dynamic_cast(o))) { //qDebug() << "**********************####" << e->type() << o; if (e->type() == QEvent::MouseButtonPress) { // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup) if (handleMousePressEvent(static_cast(e))) return true; } else if (e->type() == QEvent::KeyPress) { if (handleKeyPressEvent(static_cast(e))) return true; } if (e->type() != QEvent::Paint) return true; } return KexiDBAutoField::eventFilter(o, e); } bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const { Q_UNUSED(autoField); return true; } QWidget* KexiDBComboBox::internalEditor() const { return /*WidgetWithSubpropertiesInterface*/subwidget(); } void KexiDBComboBox::setPaletteBackgroundColor(const QColor & color) { KexiDBAutoField::setPaletteBackgroundColor(color); update(); } bool KexiDBComboBox::valueChanged() { //qDebug() << KexiDataItemInterface::originalValue().toString() << " ? " << value().toString(); return KexiDataItemInterface::originalValue() != value(); } -void -KexiDBComboBox::setColumnInfo(KDbQueryColumnInfo* cinfo) +void KexiDBComboBox::setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + d->connection = conn; + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); } void KexiDBComboBox::setVisibleColumnInfo(KDbQueryColumnInfo* cinfo) { d->visibleColumnInfo = cinfo; // we're assuming we already have columnInfo() setColumnInfoInternal(columnInfo(), d->visibleColumnInfo); } KDbQueryColumnInfo* KexiDBComboBox::visibleColumnInfo() { return d->visibleColumnInfo; } QColor KexiDBComboBox::paletteBackgroundColor() const { return KexiDBAutoField::paletteBackgroundColor(); } void KexiDBComboBox::moveCursorToEndInInternalEditor() { if (m_moveCursorToEndInInternalEditor_enabled) moveCursorToEnd(); } void KexiDBComboBox::selectAllInInternalEditor() { if (m_selectAllInInternalEditor_enabled) selectAll(); } void KexiDBComboBox::setValueInternal(const QVariant& add, bool removeOld) { // use KexiDBAutoField instead of KexiComboBoxBase::setValueInternal // expects existing popup(), but we want to have delayed creation if (popup()) popup()->hide(); KexiComboBoxBase::setValueInternal(add, removeOld); } void KexiDBComboBox::setVisibleValueInternal(const QVariant& value) { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->setValue(value, QVariant(), false /*!removeOld*/); } QVariant KexiDBComboBox::visibleValue() { return KexiComboBoxBase::visibleValue(); } void KexiDBComboBox::setValueInInternalEditor(const QVariant& value) { if (!m_setValueInInternalEditor_enabled) return; KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) iface->setValue(value, QVariant(), false/*!removeOld*/); } QVariant KexiDBComboBox::valueFromInternalEditor() { return KexiDBAutoField::value(); } QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const { if (!parentWidget()) return QPoint(-1, -1); return parentWidget()->mapToGlobal(pos); } int KexiDBComboBox::popupWidthHint() const { return width(); } QSize KexiDBComboBox::sizeHint() const { if (isVisible() && d->sizeHint.isValid()) return d->sizeHint; const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18; const int maxHeight = qMax(fontMetrics().lineSpacing(), 14) + 2; QStyleOptionComboBox option; option.initFrom(d->paintedCombo); d->sizeHint = (style()->sizeFromContents( QStyle::CT_ComboBox, &option, QSize(maxWidth, maxHeight), d->paintedCombo).expandedTo(QApplication::globalStrut())); return d->sizeHint; } void KexiDBComboBox::editRequested() { } void KexiDBComboBox::acceptRequested() { signalValueChanged(); } void KexiDBComboBox::slotRecordAccepted(KDbRecordData *data, int record) { d->dataEnteredByHand = false; KexiComboBoxBase::slotRecordAccepted(data, record); d->dataEnteredByHand = true; } void KexiDBComboBox::slotRecordSelected(KDbRecordData *data) { KexiComboBoxBase::slotRecordSelected(data); } void KexiDBComboBox::slotInternalEditorValueChanged(const QVariant& v) { KexiComboBoxBase::slotInternalEditorValueChanged(v); } void KexiDBComboBox::beforeSignalValueChanged() { if (d->dataEnteredByHand) { KexiFormDataItemInterface *iface = dynamic_cast((QWidget*)subwidget()); if (iface) { slotInternalEditorValueChanged(iface->value()); } } } void KexiDBComboBox::undoChanges() { KexiDBAutoField::undoChanges(); KexiComboBoxBase::undoChanges(); } +KDbConnection *KexiDBComboBox::connection() +{ + return d->connection; +} diff --git a/src/plugins/forms/widgets/kexidbcombobox.h b/src/plugins/forms/widgets/kexidbcombobox.h index 04a392249..effe7424b 100644 --- a/src/plugins/forms/widgets/kexidbcombobox.h +++ b/src/plugins/forms/widgets/kexidbcombobox.h @@ -1,173 +1,176 @@ /* This file is part of the KDE project Copyright (C) 2006-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. */ #ifndef KEXIDBCOMBOBOX_H #define KEXIDBCOMBOBOX_H #include "kexidbutils.h" #include "kexidbautofield.h" #include //! @short Combo box widget for Kexi forms /*! This widget is implemented on top of KexiDBAutoField, so as it uses KexiDBAutoField's ability of embedding subwidgets, it can display not only a line edit but also text edit or image box (more can be added in the future). A drop-down button is added to mimic native combo box widget's functionality. */ class KEXIFORMUTILS_EXPORT KexiDBComboBox : public KexiDBAutoField, public KexiComboBoxBase { Q_OBJECT Q_PROPERTY(bool editable READ isEditable WRITE setEditable) public: explicit KexiDBComboBox(QWidget *parent = 0); virtual ~KexiDBComboBox(); //! Implemented for KexiComboBoxBase: form has no 'related data' model (only the full database model) KDbTableViewColumn *column() override; //! Implemented for KexiComboBoxBase KDbField *field() override; //! Implemented for KexiComboBoxBase virtual QVariant origValue() const; void setEditable(bool set); bool isEditable() const; virtual void setLabelPosition(LabelPosition position); virtual QVariant value(); virtual QVariant visibleValue(); //! Reimplemented because to avoid taking value from the internal editor (index is taken from the popup instead) virtual bool valueChanged(); virtual QSize sizeHint() const; //! Reimplemented after KexiDBAutoField: jsut sets \a cinfo without initializing a subwidget. //! Initialization is performed by \ref setVisibleColumnInfo(). - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; /*! Used internally to set visible database column information. Reimplemented: performs initialization of the subwidget. */ virtual void setVisibleColumnInfo(KDbQueryColumnInfo* cinfo); /*! \return visible database column information for this item. Reimplemented. */ virtual KDbQueryColumnInfo* visibleColumnInfo(); virtual QColor paletteBackgroundColor() const; //! Reimplemented to also set 'this' widget's background color, not only subwidget's. virtual void setPaletteBackgroundColor(const QColor & color); /*! Undoes changes made to this item - just resets the widget to original value. Reimplemented after KexiFormDataItemInterface to also revert the visible value (i.e. text) to the original state. */ virtual void undoChanges(); public Q_SLOTS: virtual void slotRecordAccepted(KDbRecordData *data, int record); virtual void slotRecordSelected(KDbRecordData *data); protected Q_SLOTS: virtual void slotInternalEditorValueChanged(const QVariant& v); void slotPopupHidden(); protected: QRect buttonGeometry() const; virtual void paintEvent(QPaintEvent *); virtual void mousePressEvent(QMouseEvent *e); virtual void mouseDoubleClickEvent(QMouseEvent *e); virtual void changeEvent(QEvent * event); virtual bool eventFilter(QObject *o, QEvent *e); //! \return internal editor's geometry QRect editorGeometry() const; //! Creates editor. Reimplemented, because if the combo box is not editable, //! editor should not be created. virtual void createEditor(); virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const; //! Implemented for KexiComboBoxBase virtual QWidget *internalEditor() const; //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable. virtual void moveCursorToEndInInternalEditor(); //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable. virtual void selectAllInInternalEditor(); //! Implemented for KexiComboBoxBase virtual void setValueInInternalEditor(const QVariant& value); //! Implemented for KexiComboBoxBase virtual QVariant valueFromInternalEditor(); //! Implemented for KexiComboBoxBase virtual void editRequested(); //! Implemented for KexiComboBoxBase virtual void acceptRequested(); //! Implement this to return a position \a pos mapped from parent (e.g. viewport) //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed. virtual QPoint mapFromParentToGlobal(const QPoint& pos) const; //! Implement this to return a hint for popup width. virtual int popupWidthHint() const; virtual void setValueInternal(const QVariant& add, bool removeOld); //! Implemented to handle visible value instead of index virtual void setVisibleValueInternal(const QVariant& value); bool handleMousePressEvent(QMouseEvent *e); bool handleKeyPressEvent(QKeyEvent *ke); //! Implemented for KexiDataItemInterface virtual void beforeSignalValueChanged(); virtual KexiComboBoxPopup *popup() const; virtual void setPopup(KexiComboBoxPopup *popup); /*! Called by top-level form on key press event. Used for Key_Escape to if the popup is visible, so the key press won't be consumed to perform "cancel editing". Also used for grabbing page down/up keys. */ virtual bool keyPressed(QKeyEvent *ke); + //! Implemented for KexiComboBoxBase + KDbConnection *connection() override; + class Private; Private * const d; }; #endif diff --git a/src/plugins/forms/widgets/kexidbimagebox.cpp b/src/plugins/forms/widgets/kexidbimagebox.cpp index d413eef85..41b07c75a 100644 --- a/src/plugins/forms/widgets/kexidbimagebox.cpp +++ b/src/plugins/forms/widgets/kexidbimagebox.cpp @@ -1,895 +1,895 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2011 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 "kexidbimagebox.h" #include #include #include #include #include #include #include "kexidbutils.h" #include "kexiformpart.h" #include "kexiformmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @internal struct KexiDBImageBox_Static { KexiDBImageBox_Static() : pixmap(0), small(0) {} ~KexiDBImageBox_Static() { delete pixmap; delete small; } QPixmap *pixmap; QPixmap *small; }; Q_GLOBAL_STATIC(KexiDBImageBox_Static, KexiDBImageBox_static) KexiDBImageBox::KexiDBImageBox(bool designMode, QWidget *parent) : KexiFrame(parent) , KexiFormDataItemInterface() , m_alignment(Qt::AlignLeft | Qt::AlignTop) , m_readOnly(false) , m_scaledContents(false) , m_smoothTransformation(true) , m_keepAspectRatio(true) , m_insideSetData(false) , m_setFocusOnButtonAfterClosingPopup(false) , m_paintEventEnabled(true) , m_dropDownButtonVisible(true) , m_insideSetPalette(false) { setDesignMode(designMode); installEventFilter(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QPalette pal(palette()); pal.setBrush(backgroundRole(), QBrush(Qt::transparent)); KexiFrame::setPalette(pal); m_contextMenu = new KexiImageContextMenu(this); m_contextMenu->installEventFilter(this); if (designMode) { m_chooser = 0; } else { m_chooser = new KexiDropDownButton(this); m_chooser->setFocusPolicy(Qt::StrongFocus); m_chooser->setMenu(m_contextMenu); setFocusProxy(m_chooser); m_chooser->installEventFilter(this); } setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); setFrameColor(palette().color(QPalette::Foreground)); m_paletteBackgroundColorChanged = false; //set this here, not before connect(m_contextMenu, SIGNAL(updateActionsAvailabilityRequested(bool*,bool*)), this, SLOT(slotUpdateActionsAvailabilityRequested(bool*,bool*))); connect(m_contextMenu, SIGNAL(insertFromFileRequested(QUrl)), this, SLOT(handleInsertFromFileAction(QUrl))); connect(m_contextMenu, SIGNAL(saveAsRequested(QUrl)), this, SLOT(handleSaveAsAction(QUrl))); connect(m_contextMenu, SIGNAL(cutRequested()), this, SLOT(handleCutAction())); connect(m_contextMenu, SIGNAL(copyRequested()), this, SLOT(handleCopyAction())); connect(m_contextMenu, SIGNAL(pasteRequested()), this, SLOT(handlePasteAction())); connect(m_contextMenu, SIGNAL(clearRequested()), this, SLOT(clear())); connect(m_contextMenu, SIGNAL(showPropertiesRequested()), this, SLOT(handleShowPropertiesAction())); KexiFrame::setLineWidth(0); setDataSource(QString()); //to initialize popup menu and actions availability } KexiDBImageBox::~KexiDBImageBox() { } KexiImageContextMenu* KexiDBImageBox::contextMenu() const { return m_contextMenu; } QVariant KexiDBImageBox::value() { if (dataSource().isEmpty()) { //not db-aware return QVariant(); } //db-aware mode return m_value; //!< @todo } void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld, bool loadPixmap) { if (isReadOnly()) return; m_contextMenu->hide(); if (removeOld) m_value = add.toByteArray(); else //do not add "m_origValue" to "add" as this is QByteArray m_value = KexiDataItemInterface::originalValue().toByteArray(); bool ok = !m_value.isEmpty(); if (ok) { if (loadPixmap) { ok = KexiUtils::loadPixmapFromData(&m_pixmap, m_value); m_currentScaledPixmap = QPixmap(); // clear cache } if (!ok) { //! @todo inform about error? } } if (!ok) { m_valueMimeType.clear(); m_pixmap = QPixmap(); m_currentScaledPixmap = QPixmap(); // clear cache } repaint(); } void KexiDBImageBox::setInvalidState(const QString& displayText) { Q_UNUSED(displayText); if (!dataSource().isEmpty()) { m_value = QByteArray(); } //! @todo m_pixmapLabel->setText( displayText ); if (m_chooser) m_chooser->hide(); setReadOnly(true); } bool KexiDBImageBox::valueIsNull() { return m_value.isEmpty(); } bool KexiDBImageBox::valueIsEmpty() { return false; } bool KexiDBImageBox::isReadOnly() const { return m_readOnly; } void KexiDBImageBox::setReadOnly(bool set) { m_readOnly = set; } QPixmap KexiDBImageBox::pixmap() const { if (dataSource().isEmpty()) { //not db-aware return m_data.pixmap(); } //db-aware mode return m_pixmap; } int KexiDBImageBox::pixmapId() const { if (dataSource().isEmpty()) { //not db-aware return m_data.id(); } return 0; } void KexiDBImageBox::setPixmapId(int id) { if (m_insideSetData) //avoid recursion return; setData(KexiBLOBBuffer::self()->objectForId(id, /*unstored*/false)); repaint(); } int KexiDBImageBox::storedPixmapId() const { if (dataSource().isEmpty() && m_data.stored()) { //not db-aware return m_data.id(); } return 0; } void KexiDBImageBox::setStoredPixmapId(int id) { setData(KexiBLOBBuffer::self()->objectForId(id, /*stored*/true)); repaint(); } bool KexiDBImageBox::hasScaledContents() const { return m_scaledContents; } void KexiDBImageBox::setScaledContents(bool set) { //! @todo m_pixmapLabel->setScaledContents(set); m_scaledContents = set; m_currentScaledPixmap = QPixmap(); repaint(); } bool KexiDBImageBox::smoothTransformation() const { return m_smoothTransformation; } Qt::Alignment KexiDBImageBox::alignment() const { return m_alignment; } bool KexiDBImageBox::keepAspectRatio() const { return m_keepAspectRatio; } void KexiDBImageBox::setSmoothTransformation(bool set) { m_smoothTransformation = set; m_currentScaledPixmap = QPixmap(); repaint(); } void KexiDBImageBox::setKeepAspectRatio(bool set) { m_keepAspectRatio = set; m_currentScaledPixmap = QPixmap(); if (m_scaledContents) { repaint(); } } QWidget* KexiDBImageBox::widget() { //! @todo return m_pixmapLabel; return this; } bool KexiDBImageBox::cursorAtStart() { return true; } bool KexiDBImageBox::cursorAtEnd() { return true; } QByteArray KexiDBImageBox::data() const { if (dataSource().isEmpty()) { //static mode return m_data.data(); } else { //db-aware mode return m_value; } } void KexiDBImageBox::insertFromFile() { m_contextMenu->insertFromFile(); } void KexiDBImageBox::handleInsertFromFileAction(const QUrl &url) { if (!dataSource().isEmpty() && isReadOnly()) return; if (dataSource().isEmpty()) { //static mode KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(url); if (!h) return; setData(h); repaint(); } else { //db-aware QString fileName(url.isLocalFile() ? url.toLocalFile() : url.toDisplayString()); //! @todo download the file if remote, then set fileName properly QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) { //! @todo err msg return; } QByteArray ba = f.readAll(); if (f.error() != QFile::NoError) { //! @todo err msg f.close(); return; } QMimeDatabase db; m_valueMimeType = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension).name(); setValueInternal(ba, true); } //! @todo emit signal for setting "dirty" flag within the design if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::handleAboutToSaveAsAction( QString* origFilename, QString* mimeType, bool *dataIsEmpty) { Q_ASSERT(origFilename); Q_ASSERT(mimeType); Q_ASSERT(dataIsEmpty); if (data().isEmpty()) { qWarning() << "no pixmap!"; *dataIsEmpty = false; return; } if (dataSource().isEmpty()) { //for static images filename and mimetype can be available *origFilename = m_data.originalFileName(); if (!origFilename->isEmpty()) { *origFilename = QLatin1String("/") + *origFilename; } const QMimeDatabase db; const QMimeType mime(db.mimeTypeForName(m_data.mimeType())); if (!m_data.mimeType().isEmpty() && QImageReader::supportedMimeTypes().contains(mime.name().toLatin1())) { *mimeType = mime.name(); } } } bool KexiDBImageBox::handleSaveAsAction(const QUrl &url) { //! @todo handle remote URLs QFile f(url.toLocalFile()); if (!f.open(QIODevice::WriteOnly)) { //! @todo err msg return false; } f.write(data()); if (f.error() != QFile::NoError) { //! @todo err msg f.close(); return false; } f.close(); return true; } void KexiDBImageBox::handleCutAction() { if (!dataSource().isEmpty() && isReadOnly()) return; handleCopyAction(); clear(); } void KexiDBImageBox::handleCopyAction() { qApp->clipboard()->setPixmap(pixmap(), QClipboard::Clipboard); } void KexiDBImageBox::handlePasteAction() { if (isReadOnly() || (!designMode() && !hasFocus())) return; QPixmap pm(qApp->clipboard()->pixmap(QClipboard::Clipboard)); if (dataSource().isEmpty()) { //static mode KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(pm); if (!h) return; setData(h); } else { //db-aware mode m_pixmap = pm; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (m_pixmap.save(&buffer, "PNG")) { // write pixmap into ba in PNG format setValueInternal(ba, true, false/* !loadPixmap */); m_currentScaledPixmap = QPixmap(); // clear cache } else { setValueInternal(QByteArray(), true); } } repaint(); if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::clear() { if (dataSource().isEmpty()) { //static mode setData(KexiBLOBBuffer::Handle()); } else { if (isReadOnly()) return; //db-aware mode setValueInternal(QByteArray(), true); } //! @todo emit signal for setting "dirty" flag within the design repaint(); if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::handleShowPropertiesAction() { //! @todo } void KexiDBImageBox::slotUpdateActionsAvailabilityRequested(bool* valueIsNull, bool* valueIsReadOnly) { Q_ASSERT(valueIsNull); Q_ASSERT(valueIsReadOnly); *valueIsNull = !( (dataSource().isEmpty() && !pixmap().isNull()) /*static pixmap available*/ || (!dataSource().isEmpty() && !this->valueIsNull()) /*db-aware pixmap available*/ ); // read-only if static pixmap or db-aware pixmap for read-only widget: *valueIsReadOnly = (!designMode() && dataSource().isEmpty()) || (!dataSource().isEmpty() && isReadOnly()) || (designMode() && !dataSource().isEmpty()); } void KexiDBImageBox::contextMenuEvent(QContextMenuEvent * e) { if (popupMenuAvailable()) m_contextMenu->exec(e->globalPos()); } void KexiDBImageBox::updateActionStrings() { if (!m_contextMenu) return; if (designMode()) { } else { //update title in data view mode, based on the data source if (columnInfo()) { KexiImageContextMenu::updateTitle(m_contextMenu, columnInfo()->captionOrAliasOrName(), KexiFormManager::self()->library()->iconName(metaObject()->className())); } } if (m_chooser) { if (popupMenuAvailable() && dataSource().isEmpty()) { //this may work in the future (see @todo below) m_chooser->setToolTip(xi18n("Click to show actions for this image box")); } else { QString beautifiedImageBoxName; if (designMode()) { beautifiedImageBoxName = dataSource(); } else { beautifiedImageBoxName = columnInfo() ? columnInfo()->captionOrAliasOrName() : QString(); /*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] (see doc/dev/settings.txt) */ beautifiedImageBoxName = beautifiedImageBoxName[0].toUpper() + beautifiedImageBoxName.mid(1); } m_chooser->setToolTip( xi18n("Click to show actions for %1 image box", beautifiedImageBoxName)); } } } bool KexiDBImageBox::popupMenuAvailable() { /*! @todo add kexi-global setting which anyway, allows to show this button (read-only actions like copy/save as/print can be available) */ //chooser button can be only visible when data source is specified return !dataSource().isEmpty(); } void KexiDBImageBox::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); setData(KexiBLOBBuffer::Handle()); updateActionStrings(); KexiFrame::setFocusPolicy(focusPolicy()); //set modified policy if (m_chooser) { m_chooser->setEnabled(popupMenuAvailable()); if (m_dropDownButtonVisible && popupMenuAvailable()) { m_chooser->show(); } else { m_chooser->hide(); } } // update some properties not changed by user //! @todo get default line width from global style settings // if (!m_lineWidthChanged) { // KexiFrame::setLineWidth(ds.isEmpty() ? 0 : 1); // } if (!m_paletteBackgroundColorChanged && parentWidget()) { QPalette p = palette(); p.setColor(backgroundRole(), dataSource().isEmpty() ? parentWidget()->palette().color(parentWidget()->backgroundRole()) : palette().color(QPalette::Active, QPalette::Base) ); KexiFrame::setPalette(p); } } QSize KexiDBImageBox::sizeHint() const { if (pixmap().isNull()) return QSize(80, 80); return pixmap().size(); } int KexiDBImageBox::realLineWidth() const { switch (frameShape()) { case QFrame::NoFrame: // shadow, line, midline unused return 0; case QFrame::Box: switch (frameShadow()) { case QFrame::Plain: // midline unused return lineWidth(); default: // sunken, raised: return 2 * lineWidth() + midLineWidth(); } break; case QFrame::Panel: // shadow, midline unused return lineWidth(); case QFrame::WinPanel: // shadow, line, midline unused return 2; case QFrame::StyledPanel: { // shadow, line, midline unused QStyleOptionFrame option; option.initFrom(this); return style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, this); } default: return lineWidth(); } } static QPixmap *scaledImageBoxIcon(const QMargins& margins, const QSize& size) { const int realHeight = size.height() - margins.top() - margins.bottom(); const int realWidth = size.width() - margins.left() - margins.right(); if ( realHeight <= KexiDBImageBox_static->pixmap->height() || realWidth <= KexiDBImageBox_static->pixmap->width()) { if ( realHeight <= KexiDBImageBox_static->small->height() || realWidth <= KexiDBImageBox_static->small->width()) { return 0; } return KexiDBImageBox_static->small; } return KexiDBImageBox_static->pixmap; } void KexiDBImageBox::paintEvent(QPaintEvent *pe) { if (!m_paintEventEnabled) return; QPainter p(this); p.setClipRect(pe->rect()); QMargins margins(contentsMargins()); margins += realLineWidth(); if (designMode() && pixmap().isNull()) { QRect r( QPoint(margins.left(), margins.top()), size() - QSize(margins.left() + margins.right(), margins.top() + margins.bottom())); updatePixmap(); QPixmap *imagBoxPm = scaledImageBoxIcon(margins, size()); if (imagBoxPm) { p.drawPixmap(2, height() - margins.top() - margins.bottom() - imagBoxPm->height() - 2, *imagBoxPm); } QFont f(qApp->font()); p.setFont(f); const QFontMetrics fm(fontMetrics()); QString text; if (dataSource().isEmpty()) { if ((fm.height() * 2) > height()) { text = xi18nc("Unbound Image Box", "%1 (unbound)", objectName()); } else { text = xi18nc("Unbound Image Box", "%1\n(unbound)", objectName()); } } else { text = dataSource(); const QPixmap dataSourceTagIcon(KexiFormUtils::dataSourceTagIcon()); if (width() >= (dataSourceTagIcon.width() + 2 + fm.boundingRect(r, Qt::AlignCenter, text).width())) { r.setLeft( r.left() + dataSourceTagIcon.width() + 2 ); // make some room for the [>] icon QRect bounding = fm.boundingRect(r, Qt::AlignCenter, text); p.drawPixmap( bounding.left() - dataSourceTagIcon.width() - 2, bounding.top() + bounding.height() / 2 - dataSourceTagIcon.height() / 2, dataSourceTagIcon); } } p.drawText(r, Qt::AlignCenter, text); } else { QSize internalSize(size()); if (m_chooser && m_dropDownButtonVisible && !dataSource().isEmpty()) internalSize.setWidth(internalSize.width() - m_chooser->width()); const QRect internalRect(QPoint(0, 0), internalSize); if (m_currentScaledPixmap.isNull() || internalRect != m_currentRect) { m_currentRect = internalRect; m_currentPixmapPos = QPoint(0, 0); m_currentScaledPixmap = KexiUtils::scaledPixmap( margins, m_currentRect, pixmap(), &m_currentPixmapPos, m_alignment, m_scaledContents, m_keepAspectRatio, m_smoothTransformation ? Qt::SmoothTransformation : Qt::FastTransformation); } p.drawPixmap(m_currentPixmapPos, m_currentScaledPixmap); } KexiFrame::drawFrame(&p); if (designMode()) { const bool hasFrame = frameWidth() >= 1 && frameShape() != QFrame::NoFrame; if (!hasFrame) { KFormDesigner::paintWidgetFrame(p, rect()); } } else { // data mode // if the widget is focused, draw focus indicator rect _if_ there is no chooser button if ( !dataSource().isEmpty() && hasFocus() && (!m_chooser || !m_chooser->isVisible())) { QStyleOptionFocusRect option; option.initFrom(this); style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &p, this); } } } void KexiDBImageBox::updatePixmap() { if (!(designMode() && pixmap().isNull())) return; if (!KexiDBImageBox_static->pixmap) { QPixmap pm( KIconLoader::global()->loadMimeTypeIcon( koIconNameCStr("image-x-generic"), KIconLoader::NoGroup, KIconLoader::SizeLarge, KIconLoader::DisabledState) ); if (!pm.isNull()) { KIconEffect::semiTransparent(pm); KIconEffect::semiTransparent(pm); } KexiDBImageBox_static->pixmap = new QPixmap(pm); KexiDBImageBox_static->small = new QPixmap( KexiDBImageBox_static->pixmap->scaled( KexiDBImageBox_static->pixmap->width() / 2, KexiDBImageBox_static->pixmap->height() / 2, Qt::KeepAspectRatio, Qt::SmoothTransformation) ); } } void KexiDBImageBox::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; m_currentScaledPixmap = QPixmap(); // clear cache repaint(); } void KexiDBImageBox::setData(const KexiBLOBBuffer::Handle& handle) { if (m_insideSetData) //avoid recursion return; m_insideSetData = true; m_data = handle; m_currentScaledPixmap = QPixmap(); // clear cache emit idChanged(handle.id()); m_insideSetData = false; update(); } void KexiDBImageBox::resizeEvent(QResizeEvent * e) { KexiFrame::resizeEvent(e); if (m_chooser) { QSize s(m_chooser->sizeHint()); const int _realLineWidth = realLineWidth(); QSize margin(_realLineWidth, _realLineWidth); s.setHeight(height() - 2*margin.height()); s = s.boundedTo(size() - 2 * margin); m_chooser->resize(s); m_chooser->move(QRect(QPoint(0, 0), e->size() - m_chooser->size() - margin + QSize(1, 1)).bottomRight()); } } -void KexiDBImageBox::setColumnInfo(KDbQueryColumnInfo* cinfo) +void KexiDBImageBox::setColumnInfo(KDbConnection *conn,KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); //updating strings and title is needed updateActionStrings(); } bool KexiDBImageBox::keyPressed(QKeyEvent *ke) { // Esc key should close the popup if (ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_Escape) { if (m_contextMenu->isVisible()) { m_setFocusOnButtonAfterClosingPopup = true; return true; } } return false; } void KexiDBImageBox::setPalette(const QPalette &pal) { KexiFrame::setPalette(pal); if (m_insideSetPalette) return; m_insideSetPalette = true; setPaletteBackgroundColor(pal.color(QPalette::Active, QPalette::Base)); QPalette p(palette()); p.setColor(foregroundRole(), pal.color(foregroundRole())); setPalette(p); m_insideSetPalette = false; } void KexiDBImageBox::setPaletteBackgroundColor(const QColor & color) { m_paletteBackgroundColorChanged = true; QPalette pal(palette()); pal.setColor(backgroundRole(), color); setPalette(pal); if (m_chooser) m_chooser->setPalette(qApp->palette()); } bool KexiDBImageBox::dropDownButtonVisible() const { return m_dropDownButtonVisible; } int KexiDBImageBox::lineWidth() const { return KexiFrame::lineWidth(); } void KexiDBImageBox::setDropDownButtonVisible(bool set) { //! @todo use global default setting for this property if (m_dropDownButtonVisible == set) return; m_dropDownButtonVisible = set; if (m_chooser) { if (m_dropDownButtonVisible) m_chooser->show(); else m_chooser->hide(); } } bool KexiDBImageBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const { Q_UNUSED(autoField); return true; } bool KexiDBImageBox::eventFilter(QObject * watched, QEvent * e) { if (watched == this || watched == m_chooser) { //we're watching chooser as well because it's a focus proxy even if invisible if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut || e->type() == QEvent::MouseButtonPress) { update(); //to repaint focus rect } } // hide popup menu as soon as it loses focus if (watched == m_contextMenu && e->type() == QEvent::FocusOut) { m_contextMenu->hide(); } return KexiFrame::eventFilter(watched, e); } void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld) { setValueInternal(add, removeOld, true /*loadPixmap*/); } Qt::FocusPolicy KexiDBImageBox::focusPolicy() const { if (dataSource().isEmpty()) return Qt::NoFocus; return m_focusPolicyInternal; } Qt::FocusPolicy KexiDBImageBox::focusPolicyInternal() const { return m_focusPolicyInternal; } void KexiDBImageBox::setFocusPolicy(Qt::FocusPolicy policy) { m_focusPolicyInternal = policy; KexiFrame::setFocusPolicy(focusPolicy()); //set modified policy } void KexiDBImageBox::setFrameShape(QFrame::Shape s) { KexiFrame::setFrameShape(s); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setFrameShadow(QFrame::Shadow s) { KexiFrame::setFrameShadow(s); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setLineWidth(int w) { KexiFrame::setLineWidth(w); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setMidLineWidth(int w) { KexiFrame::setMidLineWidth(w); m_currentScaledPixmap = QPixmap(); // clear cache update(); } diff --git a/src/plugins/forms/widgets/kexidbimagebox.h b/src/plugins/forms/widgets/kexidbimagebox.h index d2901134a..0cf9bbaac 100644 --- a/src/plugins/forms/widgets/kexidbimagebox.h +++ b/src/plugins/forms/widgets/kexidbimagebox.h @@ -1,263 +1,263 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2011 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 KEXIDBIMAGEBOX_H #define KEXIDBIMAGEBOX_H #include #include #include "kexiframe.h" #include "kexidbutils.h" #include #include #include #include #include class KexiDropDownButton; class KexiImageContextMenu; //! @short A data-aware, editable image box. /*! Can also act as a normal static image box. */ class KEXIFORMUTILS_EXPORT KexiDBImageBox : public KexiFrame, public KexiFormDataItemInterface, public KexiSubwidgetInterface { Q_OBJECT Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource) Q_PROPERTY(QString dataSourcePartClass READ dataSourcePluginId WRITE setDataSourcePluginId) Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) Q_PROPERTY(int pixmapId READ pixmapId WRITE setPixmapId STORED false) Q_PROPERTY(int storedPixmapId READ storedPixmapId WRITE setStoredPixmapId DESIGNABLE false STORED true) Q_PROPERTY(bool scaledContents READ hasScaledContents WRITE setScaledContents) Q_PROPERTY(bool smoothTransformation READ smoothTransformation WRITE setSmoothTransformation) Q_PROPERTY(bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio) Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) Q_PROPERTY(bool dropDownButtonVisible READ dropDownButtonVisible WRITE setDropDownButtonVisible) Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicyInternal WRITE setFocusPolicy) // overridden to update image geometry Q_ENUMS(Shape Shadow) Q_PROPERTY(Shape frameShape READ frameShape WRITE setFrameShape) Q_PROPERTY(Shadow frameShadow READ frameShadow WRITE setFrameShadow) Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth) Q_PROPERTY(int midLineWidth READ midLineWidth WRITE setMidLineWidth) public: explicit KexiDBImageBox(bool designMode, QWidget *parent = 0); virtual ~KexiDBImageBox(); inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } inline QString dataSourcePluginId() const { return KexiFormDataItemInterface::dataSourcePluginId(); } virtual QVariant value(); // { return m_value.data(); } QPixmap pixmap() const; int pixmapId() const; int storedPixmapId() const; virtual void setInvalidState(const QString& displayText); virtual bool valueIsNull(); virtual bool valueIsEmpty(); virtual QWidget* widget(); //! always true virtual bool cursorAtStart(); //! always true virtual bool cursorAtEnd(); virtual bool isReadOnly() const; bool hasScaledContents() const; bool smoothTransformation() const; Qt::Alignment alignment() const; bool keepAspectRatio() const; virtual QSize sizeHint() const; KexiImageContextMenu *contextMenu() const; /*! \return original file name of image loaded from a file. This can be later reused for displaying the image within a collection (to be implemented) or on saving the image data back to file. */ //! @todo QString originalFileName() const { return m_value.originalFileName(); } //! Reimplemented to override behaviour of "paletteBackgroundColor" //! and "paletteForegroundColor" properties. virtual void setPalette(const QPalette &pal); //! Reimplemented to override behaviour of "paletteBackgroundColor" property. virtual void setPaletteBackgroundColor(const QColor & color); //! \return true id drop down button should be visible (the default). bool dropDownButtonVisible() const; //! For overridden property int lineWidth() const; /*! Overridden to change the policy behaviour a bit: NoFocus is returned regardless the real focus flag if the data source is empty (see dataSource()). */ //! @todo KEXI3 focusPolicy() is not virtual! Qt::FocusPolicy focusPolicy() const; //! \return the internal focus policy value, i.e. the one unrelated to data source presence. Qt::FocusPolicy focusPolicyInternal() const; /*! Sets the internal focus policy value. "Internal" means that if there is no data source set, real policy becomes NoFocus. */ virtual void setFocusPolicy(Qt::FocusPolicy policy); public Q_SLOTS: void setPixmapId(int id); void setStoredPixmapId(int id); //! Sets the datasource to \a ds virtual void setDataSource(const QString &ds); inline void setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); } virtual void setReadOnly(bool set); //! Sets \a pixmapData data for this widget. If the widget has data source set, //! the pixmap will be also placed inside of the buffer and saved later. //! @todo void setPixmapData(const QByteArray& pixmapData) { m_value.setData(pixmapData); } /*! Sets original file name of image loaded from a file. @see originalFileName() */ //! @todo void setOriginalFileName(const QString& name) { m_value.setOriginalFileName(name); } void setScaledContents(bool set); void setSmoothTransformation(bool set); void setAlignment(Qt::Alignment alignment); void setKeepAspectRatio(bool set); //! \return sets dropDownButtonVisible property. @see dropDownButtonVisible() void setDropDownButtonVisible(bool set); //! Forces execution of "insert from file" action void insertFromFile(); void setFrameShape(QFrame::Shape s); void setFrameShadow(QFrame::Shadow s); void setLineWidth(int w); void setMidLineWidth(int w); Q_SIGNALS: void idChanged(long id); protected Q_SLOTS: void slotUpdateActionsAvailabilityRequested(bool* valueIsNull, bool* valueIsReadOnly); void handleInsertFromFileAction(const QUrl &url); void handleAboutToSaveAsAction(QString* origFilename, QString* mimeType, bool* dataIsEmpty); bool handleSaveAsAction(const QUrl &url); void handleCutAction(); void handleCopyAction(); void handlePasteAction(); virtual void clear(); void handleShowPropertiesAction(); protected: //! \return data depending on the current mode (db-aware or static) QByteArray data() const; virtual void contextMenuEvent(QContextMenuEvent * e); - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; virtual void paintEvent(QPaintEvent*); virtual void resizeEvent(QResizeEvent* e); virtual bool eventFilter(QObject * watched, QEvent * e); //! Sets value \a value for a widget. virtual void setValueInternal(const QVariant& add, bool removeOld); //! @internal, added \a loadPixmap option used by paste(). void setValueInternal(const QVariant& add, bool removeOld, bool loadPixmap); //! Updates i18n'd action strings after datasource change void updateActionStrings(); void updatePixmap(); //! @internal void setData(const KexiBLOBBuffer::Handle& handle); bool popupMenuAvailable(); /*! Called by top-level form on key press event. Used for Key_Escape to if the popup is visible, so the key press won't be consumed to perform "cancel editing". */ virtual bool keyPressed(QKeyEvent *ke); //! \return real line width, i.e. for Boxed sunken or Boxed raised //! frames returns doubled width value. int realLineWidth() const; //! Implemented for KexiSubwidgetInterface virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const; QPixmap m_pixmap; QByteArray m_value; //!< for db-aware mode QString m_valueMimeType; //!< for db-aware mode KexiBLOBBuffer::Handle m_data; KexiDropDownButton *m_chooser; QPointer m_contextMenu; Qt::Alignment m_alignment; Qt::FocusPolicy m_focusPolicyInternal; //!< Used for focusPolicyInternal() QPixmap m_currentScaledPixmap; //!< for caching QRect m_currentRect; //!< for caching QPoint m_currentPixmapPos; //!< for caching bool m_readOnly; bool m_scaledContents; bool m_smoothTransformation; bool m_keepAspectRatio; bool m_insideSetData; bool m_setFocusOnButtonAfterClosingPopup; bool m_paletteBackgroundColorChanged; bool m_paintEventEnabled; //!< used to disable paintEvent() bool m_dropDownButtonVisible; bool m_insideSetPalette; }; #endif diff --git a/src/plugins/forms/widgets/kexidblabel.cpp b/src/plugins/forms/widgets/kexidblabel.cpp index c3a95c962..81c7425d8 100644 --- a/src/plugins/forms/widgets/kexidblabel.cpp +++ b/src/plugins/forms/widgets/kexidblabel.cpp @@ -1,200 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2005 Christian Nitschkowski Copyright (C) 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 "kexidblabel.h" #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiDBLabel::Private { public: Private() : resizeEvent(false) { } ~Private() {} QColor frameColor; bool resizeEvent; }; //========================================================= KexiDBLabel::KexiDBLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) , KexiDBTextWidgetInterface() , KexiFormDataItemInterface() , d(new Private()) { init(); } KexiDBLabel::KexiDBLabel(const QString& text, QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) , KexiDBTextWidgetInterface() , KexiFormDataItemInterface() , d(new Private()) { init(); setText(text); } KexiDBLabel::~KexiDBLabel() { delete d; } void KexiDBLabel::init() { KexiDataItemInterface::setHasFocusableWidget(false); d->frameColor = palette().color(foregroundRole()); setIndent(2); setAutoFillBackground(true); } void KexiDBLabel::paintEvent(QPaintEvent* e) { QPainter p(this); KexiDBTextWidgetInterface::paint(this, &p, text().isEmpty(), alignment(), false); p.end(); QPalette origPal; if (editingMode()) { origPal = palette(); QPalette pal(palette()); pal.setBrush(QPalette::WindowText, Qt::transparent); setPalette(pal); } QLabel::paintEvent(e); if (editingMode()) { setPalette(origPal); } const bool hasFrame = frameWidth() >= 1 && frameShape() != QFrame::NoFrame; if (designMode() && !hasFrame) { p.begin(this); KFormDesigner::paintWidgetFrame(p, rect()); p.end(); } } void KexiDBLabel::setValueInternal(const QVariant& add, bool removeOld) { if (removeOld) setText(add.toString()); else setText(KexiDataItemInterface::originalValue().toString() + add.toString()); } QVariant KexiDBLabel::value() { return text(); } void KexiDBLabel::setInvalidState(const QString& displayText) { setText(displayText); } bool KexiDBLabel::valueIsNull() { return text().isNull(); } bool KexiDBLabel::valueIsEmpty() { return text().isEmpty(); } bool KexiDBLabel::isReadOnly() const { return true; } void KexiDBLabel::setReadOnly(bool readOnly) { Q_UNUSED(readOnly); } QWidget* KexiDBLabel::widget() { return this; } bool KexiDBLabel::cursorAtStart() { return false; } bool KexiDBLabel::cursorAtEnd() { return false; } void KexiDBLabel::clear() { setText(QString()); } bool KexiDBLabel::setProperty(const char * name, const QVariant & value) { const bool ret = QLabel::setProperty(name, value); return ret; } const QPixmap* KexiDBLabel::pixmap() const { return QLabel::pixmap(); } -void KexiDBLabel::setColumnInfo(KDbQueryColumnInfo* cinfo) +void KexiDBLabel::setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); KexiDBTextWidgetInterface::setColumnInfo(cinfo, this); } void KexiDBLabel::resizeEvent(QResizeEvent* e) { if (isVisible()) d->resizeEvent = true; QLabel::resizeEvent(e); } void KexiDBLabel::setText(const QString& text) { QLabel::setText(text); //This is necessary for KexiFormDataItemInterface valueChanged(); repaint(); } #define ClassName KexiDBLabel #define SuperClassName QLabel #include "kexiframeutils_p.cpp" diff --git a/src/plugins/forms/widgets/kexidblabel.h b/src/plugins/forms/widgets/kexidblabel.h index b1b76b499..b30ee5030 100644 --- a/src/plugins/forms/widgets/kexidblabel.h +++ b/src/plugins/forms/widgets/kexidblabel.h @@ -1,130 +1,130 @@ /* This file is part of the KDE project Copyright (C) 2005 Christian Nitschkowski Copyright (C) 2005 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 KEXIDBLABEL_H #define KEXIDBLABEL_H #include #include #include #include #include #include #include "kexidbtextwidgetinterface.h" #include #include #include class QPainter; //! @short An extended, data-aware, read-only text label. class KEXIFORMUTILS_EXPORT KexiDBLabel : public QLabel, protected KexiDBTextWidgetInterface, public KexiFormDataItemInterface, public KFormDesigner::FormWidgetInterface { Q_OBJECT Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource) Q_PROPERTY(QString dataSourcePartClass READ dataSourcePluginId WRITE setDataSourcePluginId) Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap DESIGNABLE false) Q_PROPERTY(bool scaledContents READ hasScaledContents WRITE setScaledContents DESIGNABLE false) Q_PROPERTY(QColor frameColor READ frameColor WRITE setFrameColor) public: explicit KexiDBLabel(QWidget *parent = 0, Qt::WindowFlags f = 0); explicit KexiDBLabel(const QString& text, QWidget *parent = 0, Qt::WindowFlags f = 0); virtual ~KexiDBLabel(); inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } inline QString dataSourcePluginId() const { return KexiFormDataItemInterface::dataSourcePluginId(); } virtual QVariant value(); virtual void setInvalidState(const QString& displayText); virtual bool valueIsNull(); virtual bool valueIsEmpty(); //! always true virtual bool isReadOnly() const; virtual QWidget* widget(); //! always false virtual bool cursorAtStart(); //! always false virtual bool cursorAtEnd(); virtual void clear(); //! used to catch setIndent(), etc. virtual bool setProperty(const char * name, const QVariant & value); virtual QColor frameColor() const; const QPixmap *pixmap() const; public Q_SLOTS: //! Sets the datasource to \a ds inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } inline void setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); } virtual void setText(const QString& text); virtual void setPalette(const QPalette &pal); virtual void setFrameColor(const QColor& color); protected Q_SLOTS: //! empty virtual void setReadOnly(bool readOnly); protected: void init(); - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; virtual void paintEvent(QPaintEvent*); virtual void resizeEvent(QResizeEvent* e); //! Sets value \a value for a widget. virtual void setValueInternal(const QVariant& add, bool removeOld); //! Reimplemented to paint using real frame color instead of froeground. //! Also allows to paint more types of frame. virtual void drawFrame(QPainter *); void updatePixmapLater(); class Private; Private * const d; }; #endif diff --git a/src/plugins/forms/widgets/kexidblineedit.cpp b/src/plugins/forms/widgets/kexidblineedit.cpp index ec40ca7b1..7a67e0ee1 100644 --- a/src/plugins/forms/widgets/kexidblineedit.cpp +++ b/src/plugins/forms/widgets/kexidblineedit.cpp @@ -1,464 +1,464 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-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 "kexidblineedit.h" #include "kexidbautofield.h" #include #include #include #include #include #include #include #include #include #include #include #include //! @internal A validator used for read only flag to disable editing class KexiDBLineEdit_ReadOnlyValidator : public QValidator { Q_OBJECT public: KexiDBLineEdit_ReadOnlyValidator(QObject * parent) : QValidator(parent) { } ~KexiDBLineEdit_ReadOnlyValidator() {} virtual State validate(QString &input, int &pos) const { input = qobject_cast(parent())->originalText(); pos = qobject_cast(parent())->originalCursorPosition(); return Intermediate; } }; //----- //! A style proxy overriding KexiDBLineEdit style class KexiDBLineEditStyle : public QProxyStyle { Q_OBJECT public: explicit KexiDBLineEditStyle(const QString &baseStyleName) : QProxyStyle(baseStyleName), indent(0) { } virtual ~KexiDBLineEditStyle() { } void setIndent(int indent) { this->indent = indent; } QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget = 0) const { const KFormDesigner::FormWidgetInterface *formWidget = dynamic_cast(widget); if (formWidget && formWidget->designMode()) { const KexiFormDataItemInterface *dataItemIface = dynamic_cast(widget); if (dataItemIface && !dataItemIface->dataSource().isEmpty() && !formWidget->editingMode()) { if (element == SE_LineEditContents) { QRect rect = QProxyStyle::subElementRect(SE_LineEditContents, option, widget); if (option->direction == Qt::LeftToRight) return rect.adjusted(indent, 0, 0, 0); else return rect.adjusted(0, 0, -indent, 0); } } } return QProxyStyle::subElementRect(element, option, widget); } int indent; }; //----- KexiDBLineEdit::KexiDBLineEdit(QWidget *parent) : QLineEdit(parent) , KexiDBTextWidgetInterface() , KexiFormDataItemInterface() , m_readWriteValidator(0) , m_menuExtender(this, this) , m_internalReadOnly(false) , m_slotTextChanged_enabled(true) , m_cursorPosition(0) , m_paletteChangeEvent_enabled(true) , m_inStyleChangeEvent(false) { QFont tmpFont; tmpFont.setPointSize(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSize()); setMinimumHeight(QFontMetrics(tmpFont).height() + 6); m_originalPalette = palette(); connect(this, SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); connect(this, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString))); connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(slotCursorPositionChanged(int,int))); m_internalStyle = new KexiDBLineEditStyle(style()->objectName()); m_internalStyle->setParent(this); m_internalStyle->setIndent(KexiFormUtils::dataSourceTagIcon().width()); m_inStyleChangeEvent = true; // do not allow QLineEdit::event() to touch the style setStyle(m_internalStyle); m_inStyleChangeEvent = false; KexiDataItemInterface::setLengthExceededEmittedAtPreviousChange(false); } KexiDBLineEdit::~KexiDBLineEdit() { } void KexiDBLineEdit::setInvalidState(const QString& displayText) { QLineEdit::setReadOnly(true); //! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? if (focusPolicy() & Qt::TabFocus) setFocusPolicy(Qt::ClickFocus); setValueInternal(displayText, true); } void KexiDBLineEdit::setValueInternal(const QVariant& add, bool removeOld) { m_slotTextChanged_enabled = false; bool lengthExceeded; m_originalText = m_textFormatter.toString( removeOld ? QVariant() : KexiDataItemInterface::originalValue(), add.toString(), &lengthExceeded); setText(m_originalText); setCursorPosition(0); //ok? emitLengthExceededIfNeeded(lengthExceeded); m_slotTextChanged_enabled = true; } QVariant KexiDBLineEdit::value() { return m_textFormatter.fromString(text()); } void KexiDBLineEdit::slotTextChanged(const QString&) { if (!m_slotTextChanged_enabled) return; signalValueChanged(); } void KexiDBLineEdit::slotTextEdited(const QString& text) { bool lengthExceeded = m_textFormatter.lengthExceeded(text); emitLengthExceededIfNeeded(lengthExceeded); } bool KexiDBLineEdit::fixup() { const QString t(text()); bool lengthExceeded = m_textFormatter.lengthExceeded(t); if (lengthExceeded) { m_slotTextChanged_enabled = false; setText(t.left(field()->maxLength())); m_slotTextChanged_enabled = true; } return true; } void KexiDBLineEdit::slotCursorPositionChanged(int oldPos, int newPos) { Q_UNUSED(oldPos); if (m_originalText == text()) { // when cursor was moved without altering the text, remember its position, // otherwise the change will be reverted by the validator m_cursorPosition = newPos; } } int KexiDBLineEdit::originalCursorPosition() const { return m_cursorPosition; } bool KexiDBLineEdit::valueIsNull() { return valueIsEmpty(); //ok??? text().isNull(); } bool KexiDBLineEdit::valueIsEmpty() { return m_textFormatter.valueIsEmpty( text() ); } bool KexiDBLineEdit::valueIsValid() { return m_textFormatter.valueIsValid( text() ); } bool KexiDBLineEdit::isReadOnly() const { return m_internalReadOnly; } void KexiDBLineEdit::updatePalette() { m_paletteChangeEvent_enabled = false; setPalette(m_internalReadOnly ? KexiUtils::paletteForReadOnly(m_originalPalette) : m_originalPalette); m_paletteChangeEvent_enabled = true; } void KexiDBLineEdit::changeEvent(QEvent *e) { if (e->type() == QEvent::PaletteChange && m_paletteChangeEvent_enabled) { m_originalPalette = palette(); updatePalette(); } QLineEdit::changeEvent(e); } void KexiDBLineEdit::setReadOnly(bool readOnly) { m_internalReadOnly = readOnly; updatePalette(); if (!designMode()) { if (m_internalReadOnly) { if (m_readWriteValidator) disconnect(m_readWriteValidator, SIGNAL(destroyed(QObject*)), this, SLOT(slotReadWriteValidatorDestroyed(QObject*))); m_readWriteValidator = validator(); if (m_readWriteValidator) connect(m_readWriteValidator, SIGNAL(destroyed(QObject*)), this, SLOT(slotReadWriteValidatorDestroyed(QObject*))); if (!m_readOnlyValidator) m_readOnlyValidator = new KexiDBLineEdit_ReadOnlyValidator(this); setValidator(m_readOnlyValidator); } else { //revert to r/w validator setValidator(m_readWriteValidator); } } } void KexiDBLineEdit::slotReadWriteValidatorDestroyed(QObject*) { m_readWriteValidator = 0; } void KexiDBLineEdit::contextMenuEvent(QContextMenuEvent *e) { QMenu *menu = createStandardContextMenu(); m_menuExtender.exec(menu, e->globalPos()); delete menu; } QWidget* KexiDBLineEdit::widget() { return this; } bool KexiDBLineEdit::cursorAtStart() { return cursorPosition() == 0; } bool KexiDBLineEdit::cursorAtEnd() { return KexiUtils::cursorAtEnd(this);; } void KexiDBLineEdit::clear() { if (!m_internalReadOnly) QLineEdit::clear(); } -void KexiDBLineEdit::setColumnInfo(KDbQueryColumnInfo* cinfo) +void KexiDBLineEdit::setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); m_textFormatter.setField(cinfo ? cinfo->field() : nullptr); KexiTextFormatter::OverrideDecimalPlaces overrideDecimalPlaces; overrideDecimalPlaces.enabled = true; overrideDecimalPlaces.value = -1; // all possible digits m_textFormatter.setOverrideDecimalPlaces(overrideDecimalPlaces); m_textFormatter.setGroupSeparatorsEnabled(false); // needed, otherwise text box contains separators (confusing) if (!cinfo) return; //! @todo handle input mask (via QLineEdit::setInputMask()) using a special KDbFieldInputMask class delete m_readWriteValidator; KDbFieldValidator* fieldValidator = new KDbFieldValidator(*cinfo->field(), this); if (m_internalReadOnly) { m_readWriteValidator = fieldValidator; } else { setValidator(fieldValidator); } const QString inputMask(m_textFormatter.inputMask()); if (!inputMask.isEmpty()) setInputMask(inputMask); KexiDBTextWidgetInterface::setColumnInfo(cinfo, this); } void KexiDBLineEdit::paintEvent(QPaintEvent *pe) { QLineEdit::paintEvent(pe); KFormDesigner::FormWidgetInterface *formWidget = dynamic_cast(this); if (formWidget && formWidget->designMode()) { KexiFormDataItemInterface *dataItemIface = dynamic_cast(this); if (dataItemIface && !dataItemIface->dataSource().isEmpty() && !formWidget->editingMode()) { // draw "data source tag" icon QPainter p(this); QStyleOptionFrame option; initStyleOption(&option); int leftMargin, topMargin, rightMargin, bottomMargin; getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); QRect r( style()->subElementRect(QStyle::SE_LineEditContents, &option, this) ); r.setX(r.x() + leftMargin); r.setY(r.y() + topMargin); r.setRight(r.right() - rightMargin); r.setBottom(r.bottom() - bottomMargin); QPixmap dataSourceTagIcon; int x; if (layoutDirection() == Qt::LeftToRight) { dataSourceTagIcon = KexiFormUtils::dataSourceTagIcon(); x = r.left() - dataSourceTagIcon.width() + 2; } else { dataSourceTagIcon = KexiFormUtils::dataSourceRTLTagIcon(); x = r.right() - 2; } p.drawPixmap( x, r.top() + (r.height() - dataSourceTagIcon.height()) / 2, dataSourceTagIcon ); } } } bool KexiDBLineEdit::event(QEvent * e) { if (e->type() == QEvent::StyleChange) { if (m_inStyleChangeEvent) { return true; } // let the QLineEdit set its KLineEditStyle if (!QLineEdit::event(e)) { return false; } // move the KLineEditStyle inside our internal style as parent m_internalStyle->setParent(style()); m_inStyleChangeEvent = true; // avoid recursion setStyle(m_internalStyle); m_inStyleChangeEvent = false; return true; } const bool ret = QLineEdit::event(e); KexiDBTextWidgetInterface::event(e, this, text().isEmpty()); if (e->type() == QEvent::FocusOut) { QFocusEvent *fe = static_cast(e); if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason) { //display aligned to left after loosing the focus (only if this is tab/backtab event) //! @todo add option to set cursor at the beginning setCursorPosition(0); //ok? } } return ret; } bool KexiDBLineEdit::appendStretchRequired(KexiDBAutoField* autoField) const { return KexiDBAutoField::Top == autoField->labelPosition(); } void KexiDBLineEdit::handleAction(const QString& actionName) { if (actionName == "edit_copy") { copy(); } else if (actionName == "edit_paste") { paste(); } else if (actionName == "edit_cut") { cut(); } //! @todo ? } void KexiDBLineEdit::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue) { KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); // initialize display parameters for default / entered value KexiDisplayUtils::DisplayParameters * const params = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; setFont(params->font); QPalette pal(palette()); pal.setColor(QPalette::Active, QPalette::Text, params->textColor); setPalette(pal); } void KexiDBLineEdit::undo() { cancelEditor(); } void KexiDBLineEdit::moveCursorToEnd() { QLineEdit::end(false/*!mark*/); } void KexiDBLineEdit::moveCursorToStart() { QLineEdit::home(false/*!mark*/); } void KexiDBLineEdit::selectAll() { QLineEdit::selectAll(); } bool KexiDBLineEdit::keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; } void KexiDBLineEdit::updateTextForDataSource() { if (!designMode()) return; setText(dataSource()); } void KexiDBLineEdit::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); updateTextForDataSource(); } void KexiDBLineEdit::setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); updateTextForDataSource(); } #include "kexidblineedit.moc" diff --git a/src/plugins/forms/widgets/kexidblineedit.h b/src/plugins/forms/widgets/kexidblineedit.h index 73520e45f..b10e7b29a 100644 --- a/src/plugins/forms/widgets/kexidblineedit.h +++ b/src/plugins/forms/widgets/kexidblineedit.h @@ -1,194 +1,194 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-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. */ #ifndef KEXIDBLINEEDIT_H #define KEXIDBLINEEDIT_H #include #include #include #include #include #include #include "kexidbtextwidgetinterface.h" #include "kexidbutils.h" #include #include #include class KexiDBWidgetContextMenuExtender; class KexiDBLineEditStyle; //! @short Line edit widget for Kexi forms /*! Handles many data types. User input is validated by using validators and/or input masks. */ class KEXIFORMUTILS_EXPORT KexiDBLineEdit : public QLineEdit, protected KexiDBTextWidgetInterface, public KexiFormDataItemInterface, public KexiSubwidgetInterface, public KFormDesigner::FormWidgetInterface { Q_OBJECT Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource) Q_PROPERTY(QString dataSourcePartClass READ dataSourcePluginId WRITE setDataSourcePluginId) Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) Q_PROPERTY(QString clickMessage READ placeholderText WRITE setPlaceholderText) // Internal, equivalent of placeholderText // For backward compatibility Kexi projects // created with Qt < 4.7. Q_PROPERTY(bool showClearButton READ isClearButtonEnabled WRITE setClearButtonEnabled) // Internal, equivalent of clearButtonEnabled // For backward compatibility Kexi projects // created with Qt 4. public: explicit KexiDBLineEdit(QWidget *parent); virtual ~KexiDBLineEdit(); inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } inline QString dataSourcePluginId() const { return KexiFormDataItemInterface::dataSourcePluginId(); } virtual QVariant value(); virtual void setInvalidState(const QString& displayText); //! \return true if editor's value is null (not empty) //! Used for checking if a given constraint within table of form is met. virtual bool valueIsNull(); //! \return true if editor's value is empty (not necessary null). //! Only few data types can accept "EMPTY" property //! (use KDbField::hasEmptyProperty() to check this). //! Used for checking if a given constraint within table or form is met. virtual bool valueIsEmpty(); /*! \return true if the value is valid */ virtual bool valueIsValid(); /*! \return 'readOnly' flag for this widget. */ virtual bool isReadOnly() const; /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). \a widget is equal to 'this'. Reimplemented after KexiFormDataItemInterface. */ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); /*! \return the view widget of this item, e.g. line edit widget. */ virtual QWidget* widget(); virtual bool cursorAtStart(); virtual bool cursorAtEnd(); virtual void clear(); - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; /*! Handles action having standard name \a actionName. Action could be: "edit_copy", "edit_paste", etc. Reimplemented after KexiDataItemInterface. */ virtual void handleAction(const QString& actionName); /*! Called by top-level form on key press event to consume widget-specific shortcuts. */ virtual bool keyPressed(QKeyEvent *ke); //! Used when read only flag is true QString originalText() const { return m_originalText; } //! Used when read only flag is true int originalCursorPosition() const; public Q_SLOTS: void setDataSource(const QString &ds); void setDataSourcePluginId(const QString &pluginId); virtual void setReadOnly(bool readOnly); //! Reimplemented, so "undo" means the same as "cancelEditor" action virtual void undo(); //! Implemented for KexiDataItemInterface virtual void moveCursorToEnd(); //! Implemented for KexiDataItemInterface virtual void moveCursorToStart(); //! Implemented for KexiDataItemInterface virtual void selectAll(); //! Implemented for KexiDataItemInterface virtual bool fixup(); protected Q_SLOTS: void slotTextChanged(const QString&); void slotTextEdited(const QString& text); void slotCursorPositionChanged(int oldPos, int newPos); //! Used to protect m_readWriteValidator against after validator is destroyed void slotReadWriteValidatorDestroyed(QObject*); protected: virtual void paintEvent(QPaintEvent *); virtual void setValueInternal(const QVariant& add, bool removeOld); virtual bool event(QEvent *); virtual void contextMenuEvent(QContextMenuEvent *e); virtual void changeEvent(QEvent *e); //! Implemented for KexiSubwidgetInterface virtual bool appendStretchRequired(KexiDBAutoField* autoField) const; void updateTextForDataSource(); void updatePalette(); //! Used to format text KexiTextFormatter m_textFormatter; //! Used for read only flag to disable editing QPointer m_readOnlyValidator; //! Used to remember the previous validator used for r/w mode, after setting //! the read only flag const QValidator* m_readWriteValidator; //! Used for extending context menu KexiDBWidgetContextMenuExtender m_menuExtender; //! Used in isReadOnly, as sometimes we want to have the flag set tot true when QLineEdit::isReadOnly //! is still false. bool m_internalReadOnly; //! Used in slotTextChanged() bool m_slotTextChanged_enabled; QString m_originalText; int m_cursorPosition; QPalette m_originalPalette; //!< Used for read-only case bool m_paletteChangeEvent_enabled; bool m_inStyleChangeEvent; QPointer m_internalStyle; }; #endif diff --git a/src/plugins/forms/widgets/kexidbtextedit.cpp b/src/plugins/forms/widgets/kexidbtextedit.cpp index 50ad08465..4c15dd2c4 100644 --- a/src/plugins/forms/widgets/kexidbtextedit.cpp +++ b/src/plugins/forms/widgets/kexidbtextedit.cpp @@ -1,395 +1,395 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2012 Jarosław Staniek Copyright (C) 2014 Wojciech Kosowicz 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 "kexidbtextedit.h" #include "kexidblineedit.h" #include #include #include #include #include #include #include #include #include class DataSourceLabel : public QLabel { Q_OBJECT public: explicit DataSourceLabel(QWidget *parent) : QLabel(parent) { } protected: void paintEvent(QPaintEvent *pe) { QLabel::paintEvent(pe); QPainter p(this); int leftMargin, topMargin, rightMargin, bottomMargin; getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); QRect r( rect() ); r.setX(r.x() + leftMargin); r.setY(r.y() + topMargin); r.setRight(r.right() - rightMargin); r.setBottom(r.bottom() - bottomMargin); QPixmap dataSourceTagIcon; int x; if (layoutDirection() == Qt::LeftToRight) { dataSourceTagIcon = KexiFormUtils::dataSourceTagIcon(); x = r.left() - 1; } else { dataSourceTagIcon = KexiFormUtils::dataSourceRTLTagIcon(); x = r.right() - dataSourceTagIcon.width() - 5; } p.drawPixmap( x, r.top() + (r.height() - dataSourceTagIcon.height()) / 2, dataSourceTagIcon ); } }; // -------------- KexiDBTextEdit::KexiDBTextEdit(QWidget *parent) : KTextEdit(parent) , KexiDBTextWidgetInterface() , KexiFormDataItemInterface() , m_menuExtender(this, this) , m_slotTextChanged_enabled(true) , m_dataSourceLabel(0) , m_length(0) , m_paletteChangeEvent_enabled(true) { QFont tmpFont; tmpFont.setPointSize(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSize()); setMinimumHeight(QFontMetrics(tmpFont).height() + 6); connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); //hmm disabled again because this makes the widget disappear entirely // setAutoFillBackground(true); // otherwise we get transparent background... // installEventFilter(this); setBackgroundRole(QPalette::Base); setAcceptRichText(false); } KexiDBTextEdit::~KexiDBTextEdit() { } void KexiDBTextEdit::setInvalidState(const QString& displayText) { setReadOnly(true); //! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? if (focusPolicy() & Qt::TabFocus) setFocusPolicy(Qt::ClickFocus); KTextEdit::setPlainText(displayText); } void KexiDBTextEdit::setValueInternal(const QVariant& add, bool removeOld) { //! @todo how about rich text? if (m_columnInfo && m_columnInfo->field()->type() == KDbField::Boolean) { //! @todo temporary solution for booleans! KTextEdit::setHtml(add.toBool() ? "1" : "0"); } else { QString t; if (removeOld) { t = add.toString(); } else { t = KexiDataItemInterface::originalValue().toString() + add.toString(); } if (acceptRichText()) { KTextEdit::setHtml(t); } else { KTextEdit::setPlainText(t); } } } QVariant KexiDBTextEdit::value() { return acceptRichText() ? toHtml() : toPlainText(); } void KexiDBTextEdit::slotTextChanged() { if (!m_slotTextChanged_enabled) return; if (m_length > 0) { QString t; if (acceptRichText()) { t = toHtml(); } else { t = toPlainText(); } if (t.length() > (int)m_length) { m_slotTextChanged_enabled = false; if (acceptRichText()) { //! @todo KEXI3 setHtml(t.left(m_length)); } else { setPlainText(t.left(m_length)); } m_slotTextChanged_enabled = true; moveCursorToEnd(); } } signalValueChanged(); } bool KexiDBTextEdit::valueIsNull() { return (acceptRichText() ? toHtml() : toPlainText()).isNull(); } bool KexiDBTextEdit::valueIsEmpty() { return (acceptRichText() ? toHtml() : toPlainText()).isEmpty(); } bool KexiDBTextEdit::isReadOnly() const { return KTextEdit::isReadOnly(); } void KexiDBTextEdit::setReadOnly(bool readOnly) { KTextEdit::setReadOnly(readOnly); //! @todo KEXI3 KexiDBTextEdit::setReadOnly() - bg color #if 0//TODO QPalette p = palette(); QColor c(readOnly ? KexiFormUtils::lighterGrayBackgroundColor(kapp->palette()) : p.color(QPalette::Active, QPalette::Base)); setPaper(c); p.setColor(QPalette::Base, c); p.setColor(QPalette::Background, c); setPalette(p); #endif } QWidget* KexiDBTextEdit::widget() { return this; } bool KexiDBTextEdit::cursorAtStart() { return textCursor().atStart(); } bool KexiDBTextEdit::cursorAtEnd() { return textCursor().atEnd(); } void KexiDBTextEdit::clear() { document()->clear(); } -void KexiDBTextEdit::setColumnInfo(KDbQueryColumnInfo* cinfo) +void KexiDBTextEdit::setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { - KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiFormDataItemInterface::setColumnInfo(conn, cinfo); if (!cinfo) { m_length = 0; return; } if (cinfo->field()->type() == KDbField::Text) { if (!designMode()) { if (cinfo->field()->maxLength() > 0) { m_length = cinfo->field()->maxLength(); } } } KexiDBTextWidgetInterface::setColumnInfo(m_columnInfo, this); } void KexiDBTextEdit::paintEvent(QPaintEvent *pe) { KTextEdit::paintEvent(pe); QPainter p(viewport()); //! @todo how about rich text? KexiDBTextWidgetInterface::paint(this, &p, toPlainText().isEmpty(), alignment(), hasFocus()); } void KexiDBTextEdit::contextMenuEvent(QContextMenuEvent *e) { QMenu *menu = createStandardContextMenu(); m_menuExtender.exec(menu, e->globalPos()); delete menu; } void KexiDBTextEdit::undo() { cancelEditor(); } void KexiDBTextEdit::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) { KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); // initialize display parameters for default / entered value KexiDisplayUtils::DisplayParameters * const params = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; QPalette pal(palette()); pal.setColor(QPalette::Active, QPalette::Text, params->textColor); setPalette(pal); setFont(params->font); //! @todo support rich text... /* m_slotTextChanged_enabled = false; //for rich text... const QString origText( text() ); KTextEdit::setText(QString()); setCurrentFont(params->font); setColor(params->textColor); KTextEdit::setText(origText); m_slotTextChanged_enabled = true;*/ } void KexiDBTextEdit::moveCursorToEnd() { moveCursor(QTextCursor::End); } void KexiDBTextEdit::moveCursorToStart() { moveCursor(QTextCursor::Start); } void KexiDBTextEdit::selectAll() { KTextEdit::selectAll(); } void KexiDBTextEdit::keyPressEvent(QKeyEvent *ke) { // for instance, Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut if (KStandardShortcut::tabNext().contains(QKeySequence(ke->key() | ke->modifiers())) || KStandardShortcut::tabPrev().contains(QKeySequence(ke->key() | ke->modifiers()))) { ke->ignore(); return; } KTextEdit::keyPressEvent(ke); } bool KexiDBTextEdit::event(QEvent *e) { bool res = KTextEdit::event(e); if (e->type() == QEvent::LayoutDirectionChange) { if (m_dataSourceLabel) { m_dataSourceLabel->setLayoutDirection( layoutDirection() ); } updateTextForDataSource(); } else if (e->type() == QEvent::Resize) { if (m_dataSourceLabel) { m_dataSourceLabel->setFixedWidth(width()); } } return res; } void KexiDBTextEdit::updateTextForDataSource() { if (!designMode()) { if (m_dataSourceLabel) { m_dataSourceLabel->hide(); } return; } setPlainText(QString()); if (!m_dataSourceLabel && !dataSource().isEmpty()) { createDataSourceLabel(); } if (m_dataSourceLabel) { m_dataSourceLabel->setText(dataSource()); m_dataSourceLabel->setIndent( KexiFormUtils::dataSourceTagIcon().width() + (layoutDirection() == Qt::LeftToRight ? 0 : 7) ); m_dataSourceLabel->setVisible(!dataSource().isEmpty()); } } void KexiDBTextEdit::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); updateTextForDataSource(); } void KexiDBTextEdit::setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); updateTextForDataSource(); } void KexiDBTextEdit::createDataSourceLabel() { if (m_dataSourceLabel) return; m_dataSourceLabel = new DataSourceLabel(viewport()); m_dataSourceLabel->hide(); m_dataSourceLabel->move(0, 0); int leftMargin, topMargin, rightMargin, bottomMargin; getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); m_dataSourceLabel->setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin); } void KexiDBTextEdit::selectAllOnFocusIfNeeded() { } void KexiDBTextEdit::focusOutEvent(QFocusEvent *e) { KTextEdit::focusOutEvent(e); if (textCursor().hasSelection()) { moveCursorToEnd(); } } void KexiDBTextEdit::updatePalette() { m_paletteChangeEvent_enabled = false; setPalette(isReadOnly() ? KexiUtils::paletteForReadOnly(m_originalPalette) : m_originalPalette); m_paletteChangeEvent_enabled = true; } void KexiDBTextEdit::changeEvent(QEvent *e) { if (e->type() == QEvent::PaletteChange && m_paletteChangeEvent_enabled) { m_originalPalette = palette(); updatePalette(); } KTextEdit::changeEvent(e); } #include "kexidbtextedit.moc" diff --git a/src/plugins/forms/widgets/kexidbtextedit.h b/src/plugins/forms/widgets/kexidbtextedit.h index 11b0fd5cf..ecdd1c860 100644 --- a/src/plugins/forms/widgets/kexidbtextedit.h +++ b/src/plugins/forms/widgets/kexidbtextedit.h @@ -1,144 +1,144 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2012 Jarosław Staniek Copyright (C) 2014 Wojciech Kosowicz 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 KEXIDBTEXTEDIT_H #define KEXIDBTEXTEDIT_H #include #include "kexidbtextwidgetinterface.h" #include "kexidbutils.h" #include #include #include class DataSourceLabel; //! @short Multiline edit widget for Kexi forms class KEXIFORMUTILS_EXPORT KexiDBTextEdit : public KTextEdit, protected KexiDBTextWidgetInterface, public KexiFormDataItemInterface, public KFormDesigner::FormWidgetInterface { Q_OBJECT Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource) Q_PROPERTY(QString dataSourcePartClass READ dataSourcePluginId WRITE setDataSourcePluginId) public: explicit KexiDBTextEdit(QWidget *parent); virtual ~KexiDBTextEdit(); inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } inline QString dataSourcePluginId() const { return KexiFormDataItemInterface::dataSourcePluginId(); } virtual QVariant value(); virtual void setInvalidState(const QString& displayText); //! \return true if editor's value is null (not empty) //! Used for checking if a given constraint within table of form is met. virtual bool valueIsNull(); //! \return true if editor's value is empty (not necessary null). //! Only few data types can accept "EMPTY" property //! (use KDbField::hasEmptyProperty() to check this). //! Used for checking if a given constraint within table or form is met. virtual bool valueIsEmpty(); /*! \return 'readOnly' flag for this widget. */ virtual bool isReadOnly() const; /*! \return the view widget of this item, e.g. line edit widget. */ virtual QWidget* widget(); virtual bool cursorAtStart(); virtual bool cursorAtEnd(); virtual void clear(); - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) override; /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). \a widget is equal to 'this'. Reimplemented after KexiFormDataItemInterface. */ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); //! Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut virtual void keyPressEvent(QKeyEvent *ke); virtual bool event(QEvent *e); //! Selects contents of the widget if there is such behaviour set (it is by default). //! @todo add option for not selecting the field virtual void selectAllOnFocusIfNeeded(); public Q_SLOTS: void setDataSource(const QString &ds); void setDataSourcePluginId(const QString &pluginId); virtual void setReadOnly(bool readOnly); //! Reimplemented, so "undo" means the same as "cancelEditor" action //! @todo enable "real" undo internally so user can use ctrl+z while editing virtual void undo(); //! Implemented for KexiDataItemInterface virtual void moveCursorToEnd(); //! Implemented for KexiDataItemInterface virtual void moveCursorToStart(); //! Implemented for KexiDataItemInterface virtual void selectAll(); protected Q_SLOTS: void slotTextChanged(); protected: virtual void paintEvent(QPaintEvent *); virtual void contextMenuEvent(QContextMenuEvent *e); virtual void changeEvent(QEvent *e); virtual void setValueInternal(const QVariant& add, bool removeOld); virtual void focusOutEvent(QFocusEvent *e); QMenu * createPopupMenu(const QPoint & pos); void updateTextForDataSource(); void createDataSourceLabel(); void updatePalette(); private: //! Used for extending context menu KexiDBWidgetContextMenuExtender m_menuExtender; //! Used to disable slotTextChanged() bool m_slotTextChanged_enabled; DataSourceLabel *m_dataSourceLabel; //! Text length allowed int m_length; QPalette m_originalPalette; //!< Used for read-only case bool m_paletteChangeEvent_enabled; }; #endif diff --git a/src/plugins/importexport/csv/KexiCsvImportExportPlugin.cpp b/src/plugins/importexport/csv/KexiCsvImportExportPlugin.cpp index 7044d0cc4..8bcb5a6e8 100644 --- a/src/plugins/importexport/csv/KexiCsvImportExportPlugin.cpp +++ b/src/plugins/importexport/csv/KexiCsvImportExportPlugin.cpp @@ -1,91 +1,91 @@ /* This file is part of the KDE project Copyright (C) 2005 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 "KexiCsvImportExportPlugin.h" #include "kexicsvimportdialog.h" #include "kexicsvexportwizard.h" #include #include #include #include #include KEXI_PLUGIN_FACTORY(KexiCsvImportExportPlugin, "kexi_csvimportexportplugin.json") KexiCsvImportExportPlugin::KexiCsvImportExportPlugin(QObject *parent, const QVariantList &args) : KexiInternalPart(parent, args) { } KexiCsvImportExportPlugin::~KexiCsvImportExportPlugin() { } QWidget *KexiCsvImportExportPlugin::createWidget(const char* widgetClass, QWidget *parent, const char *objName, QMap* args) { if (0 == qstrcmp(widgetClass, "KexiCSVImportDialog")) { KexiCSVImportDialog::Mode mode = (args && (*args)["sourceType"] == "file") ? KexiCSVImportDialog::File : KexiCSVImportDialog::Clipboard; KexiCSVImportDialog *dlg = new KexiCSVImportDialog(mode, parent); dlg->setObjectName(objName); KexiInternalPart::setCancelled(dlg->canceled()); if (KexiInternalPart::cancelled()) { delete dlg; return 0; } return dlg; } else if (0 == qstrcmp(widgetClass, "KexiCSVExportWizard")) { if (!args) return 0; KexiCSVExport::Options options; if (!options.assign(args)) return 0; KexiCSVExportWizard *dlg = new KexiCSVExportWizard(options, parent); dlg->setObjectName(objName); KexiInternalPart::setCancelled(dlg->canceled()); if (KexiInternalPart::cancelled()) { delete dlg; return 0; } return dlg; } return 0; } bool KexiCsvImportExportPlugin::executeCommand(const char* commandName, QMap* args) { if (0 == qstrcmp(commandName, "KexiCSVExport")) { KexiCSVExport::Options options; if (!options.assign(args)) return false; - KDbTableOrQuerySchema tableOrQuery( - KexiMainWindowIface::global()->project()->dbConnection(), options.itemId); + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); + KDbTableOrQuerySchema tableOrQuery(conn, options.itemId); QTextStream *stream = 0; if (args->contains("textStream")) { stream = KDbUtils::stringToPointer(args->value("textStream")); } - return KexiCSVExport::exportData(&tableOrQuery, options, -1, stream); + return KexiCSVExport::exportData(conn, &tableOrQuery, options, -1, stream); } return false; } #include "KexiCsvImportExportPlugin.moc" diff --git a/src/plugins/importexport/csv/kexicsvexport.cpp b/src/plugins/importexport/csv/kexicsvexport.cpp index dd66ad703..5970bb8eb 100644 --- a/src/plugins/importexport/csv/kexicsvexport.cpp +++ b/src/plugins/importexport/csv/kexicsvexport.cpp @@ -1,284 +1,283 @@ /* 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), 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) { 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 result; } //------------------------------------ -bool KexiCSVExport::exportData(KDbTableOrQuerySchema *tableOrQuery, +bool KexiCSVExport::exportData(KDbConnection* conn, 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); + recordCount = conn->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)); + const KDbQueryColumnInfo::Vector fields( + query->fieldsExpanded(conn, KDbQuerySchema::FieldsExpandedMode::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 int fieldsCount = query->fieldsExpanded(conn).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); + ci = query->expandedOrInternalField(conn, 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 committing the file" << kSaveFile->fileName(); } } return true; } diff --git a/src/plugins/importexport/csv/kexicsvexport.h b/src/plugins/importexport/csv/kexicsvexport.h index dd8ca390e..f3c3d19c1 100644 --- a/src/plugins/importexport/csv/kexicsvexport.h +++ b/src/plugins/importexport/csv/kexicsvexport.h @@ -1,63 +1,64 @@ /* 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. */ #ifndef KEXI_CSVEXPORT_H #define KEXI_CSVEXPORT_H #include class QTextStream; +class KDbConnection; class KDbTableOrQuerySchema; namespace KexiCSVExport { //! Exporting mode: a file or clipboard enum Mode { Clipboard, File }; //! Options used in KexiCSVExportWizard contructor. class Options { public: Options(); //! Assigns \a args. \return false on failure. bool assign(QMap *args); Mode mode; int itemId; //!< Table or query ID QString fileName; QString delimiter; QString forceDelimiter; //!< Used for "clipboard" mode QString textQuote; bool addColumnNames; bool useTempQuery; }; /*! Exports data. \return false on failure. @param options options for the export @param recordCount record count of the input data or -1 if the record cound has not yet been computed @param predefinedTextStream text stream that should be used instead of writing to a file */ -bool exportData(KDbTableOrQuerySchema *tableOrQuery, const Options& options, +bool exportData(KDbConnection* conn, KDbTableOrQuerySchema *tableOrQuery, const Options& options, int recordCount = -1, QTextStream *predefinedTextStream = 0); } #endif diff --git a/src/plugins/importexport/csv/kexicsvexportwizard.cpp b/src/plugins/importexport/csv/kexicsvexportwizard.cpp index f2008c3f3..ea2491050 100644 --- a/src/plugins/importexport/csv/kexicsvexportwizard.cpp +++ b/src/plugins/importexport/csv/kexicsvexportwizard.cpp @@ -1,430 +1,430 @@ /* This file is part of the KDE project Copyright (C) 2012 Oleg Kukharchuk Copyright (C) 2005-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexicsvexportwizard.h" #include "kexicsvwidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KexiCSVExportWizard::KexiCSVExportWizard(const KexiCSVExport::Options& options, QWidget * parent) : KAssistantDialog(parent) , m_options(options) , m_importExportGroup(KSharedConfig::openConfig()->group("ImportExport")) { KexiMainWindowIface::global()->setReasonableDialogSize(this); buttonBox()->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); if (m_options.mode == KexiCSVExport::Clipboard) { //! @todo KEXI3 ? button(QDialogButtonBox::Ok)->setText(xi18n("Copy")); } else { button(QDialogButtonBox::Ok)->setText(xi18n("Export")); } QString infoLblFromText; QString captionOrName; KexiGUIMessageHandler msgh(this); + KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection(); if (m_options.useTempQuery) { m_tableOrQuery = new KDbTableOrQuerySchema(KexiMainWindowIface::global()->unsavedQuery(options.itemId)); - captionOrName = KexiMainWindowIface::global()->project()->dbConnection()->querySchema(m_options.itemId)->captionOrName(); + captionOrName = conn->querySchema(m_options.itemId)->captionOrName(); } else { - m_tableOrQuery = new KDbTableOrQuerySchema( - KexiMainWindowIface::global()->project()->dbConnection(), m_options.itemId); + m_tableOrQuery = new KDbTableOrQuerySchema(conn, m_options.itemId); captionOrName = m_tableOrQuery->captionOrName(); } if (m_tableOrQuery->table()) { if (m_options.mode == KexiCSVExport::Clipboard) { setWindowTitle(xi18nc("@title:window", "Copy Data From Table to Clipboard")); infoLblFromText = xi18n("Copying data from table:"); } else { setWindowTitle(xi18nc("@title:window", "Export Data From Table to CSV File")); infoLblFromText = xi18n("Exporting data from table:"); } } else if (m_tableOrQuery->query()) { if (m_options.mode == KexiCSVExport::Clipboard) { setWindowTitle(xi18nc("@title:window", "Copy Data From Query to Clipboard")); infoLblFromText = xi18n("Copying data from table:"); } else { setWindowTitle(xi18nc("@title:window", "Export Data From Query to CSV File")); infoLblFromText = xi18n("Exporting data from query:"); } } else { - msgh.showErrorMessage(KexiMainWindowIface::global()->project()->dbConnection()->result(), - KDbMessageHandler::Error, + msgh.showErrorMessage(conn->result(), KDbMessageHandler::Error, xi18n("Could not open data for exporting.")); m_canceled = true; return; } QString text = "\n" + captionOrName; - int m_recordCount = KDb::recordCount(m_tableOrQuery); - int columns = KDb::fieldCount(m_tableOrQuery); + int m_recordCount = conn->recordCount(m_tableOrQuery); + int columns = m_tableOrQuery->fieldCount(conn); text += "\n"; if (m_recordCount > 0) text += xi18n("(rows: %1, columns: %2)", m_recordCount, columns); else text += xi18n("(columns: %1)", columns); infoLblFromText.append(text); // OK, source data found. // Setup pages // 1. File Save Page if (m_options.mode == KexiCSVExport::File) { const QUrl url("kfiledialog:///CSVImportExport"); // startDir m_fileIface = KexiFileWidgetInterface::createWidget( url, KexiFileFilters::CustomSavingFileBasedDB, this); m_fileIface->setAdditionalMimeTypes(csvMimeTypes()); m_fileIface->setDefaultExtension("csv"); //TODO m_fileSaveWidget->setLocationText( // KDbUtils::stringToFileName(captionOrName)); m_fileSavePage = new KPageWidgetItem(m_fileIface->widget(), xi18n("Enter Name of File You Want to Save Data To")); addPage(m_fileSavePage); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); } /* 2. Export options m_exportOptionsPage exportOptionsLyr m_infoLblFrom m_infoLblTo m_showOptionsButton m_exportOptionsSection exportOptionsSectionLyr */ m_exportOptionsWidget = new QWidget(this); m_exportOptionsWidget->setObjectName("m_exportOptionsPage"); QGridLayout *exportOptionsLyr = new QGridLayout(m_exportOptionsWidget); exportOptionsLyr->setObjectName("exportOptionsLyr"); m_infoLblFrom = new KexiCSVInfoLabel(infoLblFromText, m_exportOptionsWidget, true/*showFnameLine*/); KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId( QString("org.kexi-project.%1").arg(m_tableOrQuery->table() ? "table" : "query")); if (partInfo) { m_infoLblFrom->setIcon(partInfo->iconName()); } m_infoLblFrom->separator()->hide(); m_infoLblFrom->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_infoLblFrom, 0, 0, 1, 2); m_infoLblTo = new KexiCSVInfoLabel( (m_options.mode == KexiCSVExport::File) ? xi18n("To CSV file:") : xi18n("To clipboard."), m_exportOptionsWidget, true/*showFnameLine*/); if (m_options.mode == KexiCSVExport::Clipboard) m_infoLblTo->setIcon(koIconName("edit-paste")); m_infoLblTo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_infoLblTo, 1, 0, 1, 2); exportOptionsLyr->setRowStretch(2, 1); m_showOptionsButton = new QPushButton(xi18n("Show Options >>")); m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked())); exportOptionsLyr->addWidget(m_showOptionsButton, 3, 1, Qt::AlignRight); // - m_exportOptionsSection = new QGroupBox(""/*xi18n("Options")*/); m_exportOptionsSection->setObjectName("m_exportOptionsSection"); m_exportOptionsSection->setAlignment(Qt::Vertical); m_exportOptionsSection->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); exportOptionsLyr->addWidget(m_exportOptionsSection, 4, 0, 1, 2); QGridLayout *exportOptionsSectionLyr = new QGridLayout; exportOptionsLyr->setObjectName("exportOptionsLyr"); m_exportOptionsSection->setLayout(exportOptionsSectionLyr); // -delimiter QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:")); exportOptionsSectionLyr->addWidget(delimiterLabel, 0, 0); m_delimiterWidget = new KexiCSVDelimiterWidget(false /* !lineEditOnBottom*/); m_delimiterWidget->setDelimiter(defaultDelimiter()); delimiterLabel->setBuddy(m_delimiterWidget); exportOptionsSectionLyr->addWidget(m_delimiterWidget, 0, 1); // -text quote QLabel *textQuoteLabel = new QLabel(xi18n("Text quote:")); exportOptionsSectionLyr->addWidget(textQuoteLabel, 1, 0); QWidget *textQuoteWidget = new QWidget; QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget); m_textQuote = new KexiCSVTextQuoteComboBox(textQuoteWidget); m_textQuote->setTextQuote(defaultTextQuote()); textQuoteLabel->setBuddy(m_textQuote); textQuoteLyr->addWidget(m_textQuote); textQuoteLyr->addStretch(0); exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1); // - character encoding QLabel *characterEncodingLabel = new QLabel(xi18n("Text encoding:")); exportOptionsSectionLyr->addWidget(characterEncodingLabel, 2, 0); m_characterEncodingCombo = new KexiCharacterEncodingComboBox(); m_characterEncodingCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); characterEncodingLabel->setBuddy(m_characterEncodingCombo); exportOptionsSectionLyr->addWidget(m_characterEncodingCombo, 2, 1); // - checkboxes m_addColumnNamesCheckBox = new QCheckBox(xi18n("Add column names as the first row")); m_addColumnNamesCheckBox->setChecked(true); exportOptionsSectionLyr->addWidget(m_addColumnNamesCheckBox, 3, 1); m_defaultsBtn = new QPushButton(xi18n("Defaults"), this); connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked())); exportOptionsLyr->addWidget(m_defaultsBtn, 5, 0); exportOptionsLyr->setColumnStretch(1, 1); m_alwaysUseCheckBox = new QCheckBox( m_options.mode == KexiCSVExport::Clipboard ? xi18n("Always use above options for copying") : xi18n("Always use above options for exporting")); exportOptionsLyr->addWidget(m_alwaysUseCheckBox, 5, 1, Qt::AlignRight); m_exportOptionsSection->hide(); m_defaultsBtn->hide(); m_alwaysUseCheckBox->hide(); // - m_exportOptionsPage = new KPageWidgetItem(m_exportOptionsWidget, m_options.mode == KexiCSVExport::Clipboard ? xi18n("Copying") : xi18n("Exporting")); addPage(m_exportOptionsPage); // load settings if (m_options.mode != KexiCSVExport::Clipboard && readBoolEntry("ShowOptionsInCSVExportDialog", false)) { show(); slotShowOptionsButtonClicked(); } if (readBoolEntry("StoreOptionsForCSVExportDialog", false)) { // load defaults: m_alwaysUseCheckBox->setChecked(true); QString s = readEntry("DefaultDelimiterForExportingCSVFiles", defaultDelimiter()); if (!s.isEmpty()) m_delimiterWidget->setDelimiter(s); s = readEntry("DefaultTextQuoteForExportingCSVFiles", defaultTextQuote()); m_textQuote->setTextQuote(s); //will be invaliudated here, so not a problem s = readEntry("DefaultEncodingForExportingCSVFiles"); if (!s.isEmpty()) m_characterEncodingCombo->setSelectedEncoding(s); m_addColumnNamesCheckBox->setChecked( readBoolEntry("AddColumnNamesForExportingCSVFiles", true)); } // -keep widths equal on page #2: int width = qMax(m_infoLblFrom->leftLabel()->sizeHint().width(), m_infoLblTo->leftLabel()->sizeHint().width()); m_infoLblFrom->leftLabel()->setFixedWidth(width); m_infoLblTo->leftLabel()->setFixedWidth(width); updateGeometry(); } KexiCSVExportWizard::~KexiCSVExportWizard() { delete m_tableOrQuery; } bool KexiCSVExportWizard::canceled() const { return m_canceled; } void KexiCSVExportWizard::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev) { Q_UNUSED(prev) if (page == m_fileSavePage) { m_fileIface->widget()->setFocus(); } else if (page == m_exportOptionsPage) { if (m_options.mode == KexiCSVExport::File) m_infoLblTo->setFileName(selectedFile()); } } QString KexiCSVExportWizard::selectedFile() const { return m_fileIface->selectedFile(); } void KexiCSVExportWizard::next() { if (currentPage() == m_fileSavePage) { if (!m_fileIface->checkSelectedFile()) { return; } KAssistantDialog::next(); return; } KAssistantDialog::next(); } void KexiCSVExportWizard::done(int result) { + KDbConnection* conn = KexiMainWindowIface::global()->project()->dbConnection(); if (QDialog::Accepted == result) { if (m_fileSavePage) { //qDebug() << selectedFile(); m_options.fileName = selectedFile(); } m_options.delimiter = m_delimiterWidget->delimiter(); m_options.textQuote = m_textQuote->textQuote(); m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked(); - if (!KexiCSVExport::exportData(m_tableOrQuery, m_options)) + if (!KexiCSVExport::exportData(conn, m_tableOrQuery, m_options)) return; //store options if (m_options.mode != KexiCSVExport::Clipboard) writeEntry("ShowOptionsInCSVExportDialog", m_exportOptionsSection->isVisible()); const bool store = m_alwaysUseCheckBox->isChecked(); writeEntry("StoreOptionsForCSVExportDialog", store); // only save if an option differs from default if (store && m_delimiterWidget->delimiter() != defaultDelimiter()) writeEntry("DefaultDelimiterForExportingCSVFiles", m_delimiterWidget->delimiter()); else deleteEntry("DefaultDelimiterForExportingCSVFiles"); if (store && m_textQuote->textQuote() != defaultTextQuote()) writeEntry("DefaultTextQuoteForExportingCSVFiles", m_textQuote->textQuote()); else deleteEntry("DefaultTextQuoteForExportingCSVFiles"); if (store && !m_characterEncodingCombo->defaultEncodingSelected()) writeEntry( "DefaultEncodingForExportingCSVFiles", m_characterEncodingCombo->selectedEncoding()); else deleteEntry("DefaultEncodingForExportingCSVFiles"); if (store && !m_addColumnNamesCheckBox->isChecked()) writeEntry( "AddColumnNamesForExportingCSVFiles", m_addColumnNamesCheckBox->isChecked()); else deleteEntry("AddColumnNamesForExportingCSVFiles"); } else if (QDialog::Rejected == result) { //nothing to do } KAssistantDialog::done(result); } void KexiCSVExportWizard::slotShowOptionsButtonClicked() { if (m_exportOptionsSection->isVisible()) { m_showOptionsButton->setText(xi18n("Show Options >>")); m_exportOptionsSection->hide(); m_alwaysUseCheckBox->hide(); m_defaultsBtn->hide(); } else { m_showOptionsButton->setText(xi18n("Hide Options <<")); m_exportOptionsSection->show(); m_alwaysUseCheckBox->show(); m_defaultsBtn->show(); } } void KexiCSVExportWizard::slotDefaultsButtonClicked() { m_delimiterWidget->setDelimiter(defaultDelimiter()); m_textQuote->setTextQuote(defaultTextQuote()); m_addColumnNamesCheckBox->setChecked(true); m_characterEncodingCombo->selectDefaultEncoding(); } static QString convertKey(const char *key, KexiCSVExport::Mode mode) { QString _key(QString::fromLatin1(key)); if (mode == KexiCSVExport::Clipboard) { _key.replace("Exporting", "Copying"); _key.replace("Export", "Copy"); _key.replace("CSVFiles", "CSVToClipboard"); } return _key; } bool KexiCSVExportWizard::readBoolEntry(const char *key, bool defaultValue) { return m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue); } QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue) { return m_importExportGroup.readEntry(convertKey(key, m_options.mode), defaultValue); } void KexiCSVExportWizard::writeEntry(const char *key, const QString& value) { m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value); } void KexiCSVExportWizard::writeEntry(const char *key, bool value) { m_importExportGroup.writeEntry(convertKey(key, m_options.mode), value); } void KexiCSVExportWizard::deleteEntry(const char *key) { m_importExportGroup.deleteEntry(convertKey(key, m_options.mode)); } QString KexiCSVExportWizard::defaultDelimiter() const { if (m_options.mode == KexiCSVExport::Clipboard) { if (!m_options.forceDelimiter.isEmpty()) return m_options.forceDelimiter; else return KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; } return KEXICSV_DEFAULT_FILE_DELIMITER; } QString KexiCSVExportWizard::defaultTextQuote() const { if (m_options.mode == KexiCSVExport::Clipboard) return KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; return KEXICSV_DEFAULT_FILE_TEXT_QUOTE; } diff --git a/src/plugins/importexport/csv/kexicsvimportdialog.cpp b/src/plugins/importexport/csv/kexicsvimportdialog.cpp index 6b9e595db..4532db845 100644 --- a/src/plugins/importexport/csv/kexicsvimportdialog.cpp +++ b/src/plugins/importexport/csv/kexicsvimportdialog.cpp @@ -1,2181 +1,2179 @@ /* This file is part of the KDE project Copyright (C) 2005-2017 Jarosław Staniek Copyright (C) 2012 Oleg Kukharchuk This work is based on kspread/dialogs/kspread_dlg_csv.cc. Copyright (C) 2002-2003 Norbert Andres Copyright (C) 2002-2003 Ariya Hidayat Copyright (C) 2002 Laurent Montel Copyright (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexicsvimportdialog.h" #include "KexiCSVImportDialogModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexicsvwidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _IMPORT_ICON koIconNeededWithSubs("change to file_import or so", "file_import","table") //! @internal An item delegate for KexiCSVImportDialog's table view class KexiCSVImportDialogItemDelegate : public QStyledItemDelegate { Q_OBJECT public: KexiCSVImportDialogItemDelegate(QObject *parent = 0); virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; KexiCSVImportDialogItemDelegate::KexiCSVImportDialogItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QWidget* KexiCSVImportDialogItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem newOption(option); QWidget *editor = QStyledItemDelegate::createEditor(parent, newOption, index); if (editor && index.row() == 0) { QFont f(editor->font()); f.setBold(true); editor->setFont(f); } return editor; } // -- //! @internal class KexiCSVImportStatic { public: KexiCSVImportStatic() : types(QVector() << KDbField::Text << KDbField::Integer << KDbField::Double << KDbField::Boolean << KDbField::Date << KDbField::Time << KDbField::DateTime) { typeNames.insert(KDbField::Text, KDbField::typeGroupName(KDbField::TextGroup)); typeNames.insert(KDbField::Integer, KDbField::typeGroupName(KDbField::IntegerGroup)); typeNames.insert(KDbField::Double, KDbField::typeGroupName(KDbField::FloatGroup)); typeNames.insert(KDbField::Boolean, KDbField::typeName(KDbField::Boolean)); typeNames.insert(KDbField::Date, KDbField::typeName(KDbField::Date)); typeNames.insert(KDbField::Time, KDbField::typeName(KDbField::Time)); typeNames.insert(KDbField::DateTime, KDbField::typeName(KDbField::DateTime)); for (int i = 0; i < types.size(); ++i) { indicesForTypes.insert(types[i], i); } } const QVector types; QHash typeNames; QHash indicesForTypes; }; Q_GLOBAL_STATIC(KexiCSVImportStatic, kexiCSVImportStatic) #define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable #define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable #define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096 #define MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW 1930 #define PROGRESS_STEP_MS (1000/5) // 5 updates per second static bool shouldSaveRow(int row, bool firstRowForFieldNames) { return row > (firstRowForFieldNames ? 1 : 0); } // -- class Q_DECL_HIDDEN KexiCSVImportDialog::Private { public: Private() : imported(false) { } ~Private() { qDeleteAll(m_uniquenessTest); } void clearDetectedTypes() { m_detectedTypes.clear(); } void clearUniquenessTests() { qDeleteAll(m_uniquenessTest); m_uniquenessTest.clear(); } KDbField::Type detectedType(int col) const { return m_detectedTypes.value(col, KDbField::InvalidType); } void setDetectedType(int col, KDbField::Type type) { if (m_detectedTypes.count() <= col) { for (int i = m_detectedTypes.count(); i < col; ++i) { // append missing bits m_detectedTypes.append(KDbField::InvalidType); } m_detectedTypes.append(type); } else { m_detectedTypes[col] = type; } } QList* uniquenessTest(int col) const { return m_uniquenessTest.value(col); } void setUniquenessTest(int col, QList* test) { if (m_uniquenessTest.count() <= col) { for (int i = m_uniquenessTest.count(); i < col; ++i) { // append missing bits m_uniquenessTest.append(0); } m_uniquenessTest.append(test); } else { m_uniquenessTest[col] = test; } } bool imported; private: //! vector of detected types //! @todo more types QList m_detectedTypes; //! m_detectedUniqueColumns[i]==true means that i-th column has unique values //! (only for numeric type) QList< QList* > m_uniquenessTest; }; // -- KexiCSVImportDialog::KexiCSVImportDialog(Mode mode, QWidget * parent) : KAssistantDialog(parent), m_parseComments(false), m_canceled(false), m_adjustRows(true), m_startline(0), m_textquote(QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0]), m_commentSymbol(QString(KEXICSV_DEFAULT_COMMENT_START)[0]), m_mode(mode), m_columnsAdjusted(false), m_firstFillTableCall(true), m_blockUserEvents(false), m_primaryKeyColumn(-1), m_dialogCanceled(false), m_conn(0), m_fieldsListModel(0), m_destinationTableSchema(0), m_implicitPrimaryKeyAdded(false), m_allRowsLoadedInPreview(false), m_stoppedAt_MAX_BYTES_TO_PREVIEW(false), m_stringNo("no"), m_stringI18nNo(xi18n("no")), m_stringFalse("false"), m_stringI18nFalse(xi18n("false")), m_partItemForSavedTable(0), m_importInProgress(false), m_importCanceled(false), d(new Private) { setWindowTitle( mode == File ? xi18nc("@title:window", "Import CSV Data From File") : xi18nc("@title:window", "Paste CSV Data From Clipboard") ); setWindowIcon(_IMPORT_ICON); //! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard setObjectName("KexiCSVImportDialog"); setSizeGripEnabled(true); KexiMainWindowIface::global()->setReasonableDialogSize(this); KGuiItem::assign(configureButton(), KStandardGuiItem::configure()); finishButton()->setEnabled(false); backButton()->setEnabled(false); KConfigGroup importExportGroup(KSharedConfig::openConfig()->group("ImportExport")); m_maximumRowsForPreview = importExportGroup.readEntry( "MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW); m_maximumBytesForPreview = importExportGroup.readEntry( "MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW); m_minimumYearFor100YearSlidingWindow = importExportGroup.readEntry( "MinimumYearFor100YearSlidingWindow", MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW); m_pkIcon = KexiSmallIcon("database-key"); if (m_mode == File) { createFileOpenPage(); } else if (m_mode == Clipboard) { QString subtype("plain"); m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); /* debug for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++) qDebug() << i << ": " << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i); */ } else { return; } m_file = 0; m_inputStream = 0; createOptionsPage(); createImportMethodPage(); createTableNamePage(); createImportPage(); /** @todo reuse Clipboard too! */ /*if ( m_mode == Clipboard ) { setWindowTitle( xi18n( "Inserting From Clipboard" ) ); QMimeSource * mime = QApplication::clipboard()->data(); if ( !mime ) { KMessageBox::information( this, xi18n("There is no data in the clipboard.") ); m_canceled = true; return; } if ( !mime->provides( "text/plain" ) ) { KMessageBox::information( this, xi18n("There is no usable data in the clipboard.") ); m_canceled = true; return; } m_fileArray = QByteArray(mime->encodedData( "text/plain" ) ); } else if ( mode == File ) {*/ m_dateRegExp = QRegularExpression("^(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})$"); m_timeRegExp1 = QRegularExpression("^(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$"); m_timeRegExp2 = QRegularExpression("^(\\d{1,2}):(\\d{1,2})$"); m_fpNumberRegExp1 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+$"); // E notation, e.g. 0.1e2, 0.1e+2, 0.1e-2, 0.1E2, 0.1E+2, 0.1E-2 m_fpNumberRegExp2 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+[Ee][+-]{0,1}\\d+$"); m_loadingProgressDlg = 0; if (m_mode == Clipboard) { m_infoLbl->setIcon(koIconName("edit-paste")); } m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_formatCombo, SIGNAL(activated(int)), this, SLOT(formatChanged(int))); connect(m_delimiterWidget, SIGNAL(delimiterChanged(QString)), this, SLOT(delimiterChanged(QString))); connect(m_commentWidget, SIGNAL(commentSymbolChanged(QString)), this, SLOT(commentSymbolChanged(QString))); connect(m_startAtLineSpinBox, SIGNAL(valueChanged(int)), this, SLOT(startlineSelected(int))); connect(m_comboQuote, SIGNAL(activated(int)), this, SLOT(textquoteSelected(int))); connect(m_tableView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentCellChanged(QModelIndex,QModelIndex))); connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)), this, SLOT(ignoreDuplicatesChanged(int))); connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)), this, SLOT(slot1stRowForFieldNamesChanged(int))); connect(configureButton(), &QPushButton::clicked, this, &KexiCSVImportDialog::optionsButtonClicked); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); KexiUtils::installRecursiveEventFilter(this, this); if ( m_mode == Clipboard ) initLater(); } KexiCSVImportDialog::~KexiCSVImportDialog() { delete m_file; delete m_inputStream; delete d; } void KexiCSVImportDialog::next() { KPageWidgetItem *curPage = currentPage(); if (curPage == m_openFilePage) { if (m_fileIface->checkSelectedFile()) { m_fname = m_fileIface->selectedFile(); } else { return; } if (!openData()) { return; } } else if (curPage == m_optionsPage) { const int numRows(m_table->rowCount()); if (numRows == 0) return; //impossible if (numRows == 1) { if (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Data set contains no rows. Do you want to import empty table?"))) return; } } else if (curPage == m_saveMethodPage) { if (m_newTableOption->isChecked()) { m_tableNameWidget->setCurrentIndex(0); m_newTableWidget->setFocus(); } else { m_tableNameWidget->setCurrentIndex(1); m_tablesList->setFocus(); } } else if (curPage == m_tableNamePage) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTableOption->isChecked()) { m_partItemForSavedTable->setCaption(m_newTableWidget->captionText()); m_partItemForSavedTable->setName(m_newTableWidget->nameText()); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); KDbObject tmp; tristate res = (part && part->info()) ? m_conn->loadObjectData( project->typeIdForPluginId(part->info()->pluginId()), m_newTableWidget->nameText(), &tmp) : false; if (res == true) { KMessageBox::information(this, "

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

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

" ); return; } else if (res == false) { qFatal("Plugin org.kexi-project.table not found"); return; } } else { m_partItemForSavedTable = m_tablesList->selectedPartItem(); } } KAssistantDialog::next(); } void KexiCSVImportDialog::slotShowSchema(KexiPart::Item *item) { if (!item) { return; } nextButton()->setEnabled(true); - KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema( - KexiMainWindowIface::global()->project()->dbConnection(), - item->identifier() - ); + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); + KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema(conn, item->identifier()); m_tableCaptionLabel->setText(tableOrQuery->captionOrName()); m_tableNameLabel->setText(tableOrQuery->name()); - m_recordCountLabel->setText(QString::number(KDb::recordCount(tableOrQuery))); - m_colCountLabel->setText(QString::number(tableOrQuery->fieldCount())); + m_recordCountLabel->setText(QString::number(conn->recordCount(tableOrQuery))); + m_colCountLabel->setText(QString::number(tableOrQuery->fieldCount(conn))); delete m_fieldsListModel; m_fieldsListModel = new KexiFieldListModel(m_fieldsListView, ShowDataTypes); - m_fieldsListModel->setSchema(tableOrQuery); + m_fieldsListModel->setSchema(conn, tableOrQuery); m_fieldsListView->setModel(m_fieldsListModel); m_fieldsListView->header()->resizeSections(QHeaderView::ResizeToContents); } void KexiCSVImportDialog::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev) { nextButton()->setEnabled(page == m_saveMethodPage ? false : true); finishButton()->setEnabled(page == m_importPage ? true : false); if (page == m_importPage) { KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); } configureButton()->setEnabled(page == m_optionsPage); nextButton()->setEnabled(page == m_importPage ? false : true); backButton()->setEnabled(page == m_openFilePage ? false : true); if (page == m_saveMethodPage && prev == m_tableNamePage && m_partItemForSavedTable) { if (m_newTableOption->isChecked()) { KexiMainWindowIface::global()->project()->deleteUnstoredItem(m_partItemForSavedTable); } m_partItemForSavedTable = 0; } if(page == m_optionsPage){ if (m_mode == File) { m_loadingProgressDlg = new QProgressDialog(this); m_loadingProgressDlg->setObjectName("m_loadingProgressDlg"); m_loadingProgressDlg->setLabelText( xi18nc("@info", "Loading CSV Data from %1...", QDir::toNativeSeparators(m_fname))); m_loadingProgressDlg->setWindowTitle(xi18nc("@title:window", "Loading CSV Data")); m_loadingProgressDlg->setModal(true); m_loadingProgressDlg->setMaximum(m_maximumRowsForPreview); m_loadingProgressDlg->show(); } // delimiterChanged(detectedDelimiter); // this will cause fillTable() m_detectDelimiter = true; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { // m_loadingProgressDlg->hide(); // m_loadingProgressDlg->close(); QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); m_tableView->setFocus(); } else if (page == m_saveMethodPage) { m_newTableOption->setFocus(); } else if (page == m_tableNamePage) { if (m_newTableOption->isChecked() && !m_partItemForSavedTable) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); //get suggested name based on the file name QString suggestedName; if (m_mode == File) { suggestedName = QUrl(m_fname).fileName(); //remove extension if (!suggestedName.isEmpty()) { const int idx = suggestedName.lastIndexOf('.'); if (idx != -1) { suggestedName = suggestedName.mid(0, idx).simplified(); } } } KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } //-new part item m_partItemForSavedTable = project->createPartItem(part->info(), suggestedName); if (!m_partItemForSavedTable) { msg.showErrorMessage(project->result()); return; } m_newTableWidget->setCaptionText(m_partItemForSavedTable->caption()); m_newTableWidget->setNameText(m_partItemForSavedTable->name()); m_newTableWidget->captionLineEdit()->setFocus(); m_newTableWidget->captionLineEdit()->selectAll(); } else if (!m_newTableOption->isChecked()) { KexiPart::Item *i = m_tablesList->selectedPartItem(); if (!i) { nextButton()->setEnabled(false); } slotShowSchema(i); } } else if (page == m_importPage) { m_fromLabel->setFileName(m_fname); m_toLabel->setFileNameText(m_partItemForSavedTable->name()); m_importingProgressBar->hide(); m_importProgressLabel->hide(); } } void KexiCSVImportDialog::createFileOpenPage() { m_fileIface = KexiFileWidgetInterface::createWidget(QUrl("kfiledialog:///CSVImportExport"), KexiFileFilters::CustomOpening, this); m_fileIface->setAdditionalMimeTypes(csvMimeTypes()); m_fileIface->setDefaultExtension("csv"); m_fileIface->connectFileSelectedSignal(this, SLOT(next())); m_openFilePage = new KPageWidgetItem(m_fileIface->widget(), xi18n("Select Import Filename")); addPage(m_openFilePage); } void KexiCSVImportDialog::createOptionsPage() { QWidget *m_optionsWidget = new QWidget(this); QVBoxLayout *lyr = new QVBoxLayout(m_optionsWidget); m_infoLbl = new KexiCSVInfoLabel( m_mode == File ? xi18n("Preview of data from file:") : xi18n("Preview of data from clipboard"), m_optionsWidget, m_mode == File /*showFnameLine*/ ); lyr->addWidget(m_infoLbl); QWidget* page = new QFrame(m_optionsWidget); QGridLayout *glyr = new QGridLayout(page); lyr->addWidget(page); // Delimiter: comma, semicolon, tab, space, other m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page); glyr->addWidget(m_delimiterWidget, 1, 0, 1, 1); QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:"), page); delimiterLabel->setBuddy(m_delimiterWidget); delimiterLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(delimiterLabel, 0, 0, 1, 1); m_commentWidget = new KexiCSVCommentWidget(true, page); glyr->addWidget(m_commentWidget, 1, 4); QLabel *commentLabel = new QLabel(xi18n("Comment symbol:"), page); commentLabel->setBuddy(m_commentWidget); commentLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(commentLabel, 0, 4); // Format: number, text... //! @todo Object and Currency types m_formatCombo = new KComboBox(page); m_formatCombo->setObjectName("m_formatCombo"); for (int i = 0; i < kexiCSVImportStatic->types.size(); ++i) { m_formatCombo->addItem(kexiCSVImportStatic->typeNames.value(kexiCSVImportStatic->types[i])); } glyr->addWidget(m_formatCombo, 1, 1, 1, 1); m_formatLabel = new QLabel(page); m_formatLabel->setBuddy(m_formatCombo); m_formatLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_formatLabel, 0, 1); m_primaryKeyField = new QCheckBox(xi18n("Primary key"), page); m_primaryKeyField->setObjectName("m_primaryKeyField"); glyr->addWidget(m_primaryKeyField, 2, 1); connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool))); m_comboQuote = new KexiCSVTextQuoteComboBox(page); glyr->addWidget(m_comboQuote, 1, 2); TextLabel2 = new QLabel(xi18n("Text quote:"), page); TextLabel2->setBuddy(m_comboQuote); TextLabel2->setObjectName("TextLabel2"); TextLabel2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); TextLabel2->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(TextLabel2, 0, 2); m_startAtLineSpinBox = new QSpinBox(page); m_startAtLineSpinBox->setObjectName("m_startAtLineSpinBox"); m_startAtLineSpinBox->setMinimum(1); m_startAtLineSpinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_startAtLineSpinBox->setMinimumWidth( QFontMetrics(m_startAtLineSpinBox->font()).width("8888888")); glyr->addWidget(m_startAtLineSpinBox, 1, 3); m_startAtLineLabel = new QLabel(page); m_startAtLineLabel->setBuddy(m_startAtLineSpinBox); m_startAtLineLabel->setObjectName("m_startAtLineLabel"); m_startAtLineLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); m_startAtLineLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_startAtLineLabel, 0, 3); m_ignoreDuplicates = new QCheckBox(page); m_ignoreDuplicates->setObjectName("m_ignoreDuplicates"); m_ignoreDuplicates->setText(xi18n("Ignore duplicated delimiters")); glyr->addWidget(m_ignoreDuplicates, 2, 2, 1, 2); m_1stRowForFieldNames = new QCheckBox(page); m_1stRowForFieldNames->setObjectName("m_1stRowForFieldNames"); m_1stRowForFieldNames->setText(xi18n("First row contains column names")); glyr->addWidget(m_1stRowForFieldNames, 3, 2, 1, 2); QSpacerItem* spacer_2 = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred); glyr->addItem(spacer_2, 0, 5, 4, 1); glyr->setColumnStretch(5, 2); m_tableView = new QTableView(m_optionsWidget); m_table = new KexiCSVImportDialogModel(m_tableView); m_table->setObjectName("m_table"); m_tableView->setModel(m_table); m_tableItemDelegate = new KexiCSVImportDialogItemDelegate(m_tableView); m_tableView->setItemDelegate(m_tableItemDelegate); lyr->addWidget(m_tableView); QSizePolicy spolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); spolicy.setHorizontalStretch(1); spolicy.setVerticalStretch(1); m_tableView->setSizePolicy(spolicy); m_optionsPage = new KPageWidgetItem(m_optionsWidget, xi18n("Import Options")); addPage(m_optionsPage); } void KexiCSVImportDialog::createImportMethodPage() { m_saveMethodWidget = new QWidget(this); QGridLayout *l = new QGridLayout(m_saveMethodWidget); m_newTableOption = new QRadioButton( xi18nc("@option:check CSV import: data will be appended to a new table", "&New table")); m_newTableOption->setChecked(true); m_existingTableOption = new QRadioButton( xi18nc("@option:check CSV import: data will be appended to existing table", "&Existing table")); l->addWidget(m_newTableOption, 0, 0, 1, 1); l->addWidget(m_existingTableOption, 1, 0, 1, 1); QSpacerItem *hSpacer = new QSpacerItem(200, 20, QSizePolicy::Preferred, QSizePolicy::Minimum); QSpacerItem *vSpacer = new QSpacerItem(20, 200, QSizePolicy::Minimum, QSizePolicy::Expanding); l->addItem(hSpacer, 1, 1, 1, 1); l->addItem(vSpacer, 2, 0, 1, 1); m_saveMethodPage = new KPageWidgetItem(m_saveMethodWidget, xi18n("Choose Destination for Imported Data")); addPage(m_saveMethodPage); } void KexiCSVImportDialog::createTableNamePage() { m_tableNameWidget = new QStackedWidget(this); m_tableNameWidget->setObjectName("m_tableNameWidget"); QWidget *page1=new QWidget(m_tableNameWidget); m_newTableWidget = new KexiNameWidget(QString(), page1); m_newTableWidget->addNameSubvalidator(new KDbObjectNameValidator( KexiMainWindowIface::global()->project()->dbConnection()->driver())); QVBoxLayout *l=new QVBoxLayout(page1); l->addWidget(m_newTableWidget); l->addStretch(1); m_tableNameWidget->addWidget(page1); QSplitter *splitter = new QSplitter(m_tableNameWidget); QWidget *tablesListParentWidget = new QWidget; QVBoxLayout *tablesListParentWidgetLayout = new QVBoxLayout(tablesListParentWidget); tablesListParentWidgetLayout->setMargin(0); QLabel *tablesListLabel = new QLabel(xi18nc("@label", "Select existing table:")); tablesListParentWidgetLayout->addWidget(tablesListLabel); KexiProjectNavigator::Features tablesListFeatures = KexiProjectNavigator::DefaultFeatures; tablesListFeatures &= (~KexiProjectNavigator::AllowSingleClickForOpeningItems); tablesListFeatures &= (~KexiProjectNavigator::ClearSelectionAfterAction); tablesListFeatures |= KexiProjectNavigator::Borders; m_tablesList = new KexiProjectNavigator(tablesListParentWidget, tablesListFeatures); tablesListParentWidgetLayout->addWidget(m_tablesList, 1); tablesListLabel->setBuddy(m_tablesList); QString errorString; m_tablesList->setProject(KexiMainWindowIface::global()->project(), "org.kexi-project.table", &errorString, false); connect (m_tablesList, SIGNAL(openOrActivateItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(next())); connect (m_tablesList, SIGNAL(selectionChanged(KexiPart::Item*)), this, SLOT(slotShowSchema(KexiPart::Item*))); splitter->addWidget(tablesListParentWidget); QWidget *tableDetailsWidget = new QWidget; QFormLayout *formLayout = new QFormLayout(tableDetailsWidget); formLayout->setContentsMargins(KexiUtils::marginHint(), 0, 0, 0); formLayout->addRow(new QLabel(xi18nc("@label Preview of selected table", "Table preview:"))); formLayout->addRow(xi18nc("@label", "Name:"), m_tableNameLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Caption:"), m_tableCaptionLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Row count:"), m_recordCountLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Column count:"), m_colCountLabel = new QLabel(tableDetailsWidget)); formLayout->addItem(new QSpacerItem(1, KexiUtils::spacingHint())); m_fieldsListView = new QTreeView(tableDetailsWidget); m_fieldsListView->setItemsExpandable(false); m_fieldsListView->setRootIsDecorated(false); QSizePolicy fieldsListViewPolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); fieldsListViewPolicy.setVerticalStretch(1); m_fieldsListView->setSizePolicy(fieldsListViewPolicy); formLayout->addRow(new QLabel(xi18nc("@label", "Fields:"))); formLayout->addRow(m_fieldsListView); splitter->addWidget(tableDetailsWidget); splitter->setStretchFactor(splitter->indexOf(tableDetailsWidget), 1); m_tableNameWidget->addWidget(splitter); m_tableNamePage = new KPageWidgetItem(m_tableNameWidget, xi18nc("@label", "Choose Name of Destination Table")); addPage(m_tableNamePage); } void KexiCSVImportDialog::createImportPage() { m_importWidget = new QWidget(this); m_fromLabel = new KexiCSVInfoLabel(m_mode == File ? xi18n("From CSV file:") : xi18n("From Clipboard"), m_importWidget, m_mode == File); m_fromLabel->separator()->hide(); if (m_mode != File) { m_fromLabel->setIcon(koIconName("edit-paste")); } m_toLabel = new KexiCSVInfoLabel(xi18nc("@label Importing CSV data to table:", "To table:"), m_importWidget, true); KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId("org.kexi-project.table"); m_toLabel->setIcon(partInfo->iconName()); m_importProgressLabel = new QLabel(m_importWidget); m_importingProgressBar = new QProgressBar(m_importWidget); QVBoxLayout *l = new QVBoxLayout(m_importWidget); l->addWidget(m_fromLabel); l->addWidget(m_toLabel); l->addSpacing(m_importProgressLabel->fontMetrics().height()); l->addWidget(m_importProgressLabel); l->addWidget(m_importingProgressBar); l->addStretch(1); m_importingProgressBar->hide(); m_importProgressLabel->hide(); m_importPage = new KPageWidgetItem(m_importWidget, xi18n("Ready to Import")); addPage(m_importPage); } void KexiCSVImportDialog::initLater() { if (!openData()) return; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); show(); m_tableView->setFocus(); } bool KexiCSVImportDialog::openData() { if (m_mode != File) //data already loaded, no encoding stuff needed return true; delete m_inputStream; m_inputStream = 0; if (m_file) { m_file->close(); delete m_file; } m_file = new QFile(m_fname); if (!m_file->open(QIODevice::ReadOnly)) { m_file->close(); delete m_file; m_file = 0; KMessageBox::sorry(this, xi18n("Cannot open input file %1.", QDir::toNativeSeparators(m_fname))); nextButton()->setEnabled(false); m_canceled = true; if (parentWidget()) parentWidget()->raise(); return false; } return true; } bool KexiCSVImportDialog::canceled() const { return m_canceled; } void KexiCSVImportDialog::fillTable() { KexiUtils::WaitCursor wc(true); repaint(); m_blockUserEvents = true; button(QDialogButtonBox::Cancel)->setEnabled(true); KexiUtils::WaitCursor wait; if (m_table->rowCount() > 0) //to accept editor m_tableView->setCurrentIndex(QModelIndex()); int row, column, maxColumn; QString field; m_table->clear(); d->clearDetectedTypes(); d->clearUniquenessTests(); m_primaryKeyColumn = -1; if (true != loadRows(field, row, column, maxColumn, true)) return; // file with only one line without EOL if (field.length() > 0) { setText(row - m_startline, column, field, true); ++row; field.clear(); } adjustRows(row - m_startline - (m_1stRowForFieldNames->isChecked() ? 1 : 0)); maxColumn = qMax(maxColumn, column); m_table->setColumnCount(maxColumn); for (column = 0; column < m_table->columnCount(); ++column) { updateColumn(column); if (!m_columnsAdjusted) m_tableView->resizeColumnToContents(column); } m_columnsAdjusted = true; if (m_primaryKeyColumn >= 0 && m_primaryKeyColumn < m_table->columnCount()) { if (KDbField::Integer != d->detectedType(m_primaryKeyColumn)) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = -1; } } m_tableView->setCurrentIndex(m_table->index(0, 0)); currentCellChanged(m_table->index(0, 0), QModelIndex()); setPrimaryKeyIcon(m_primaryKeyColumn, true); const int count = qMax(0, m_table->rowCount() - 1 + m_startline); m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW; if (count > 1) { if (m_allRowsLoadedInPreview) { m_startAtLineSpinBox->setMaximum(count); m_startAtLineSpinBox->setValue(m_startline + 1); } m_startAtLineSpinBox->setEnabled(true); m_startAtLineLabel->setText( m_allRowsLoadedInPreview ? xi18n("Start at line (1-%1):", count) : xi18n("Start at line:") //we do not know what's real count ); m_startAtLineLabel->setEnabled(true); } else { // no data m_startAtLineSpinBox->setMaximum(1); m_startAtLineSpinBox->setValue(1); m_startAtLineSpinBox->setEnabled(false); m_startAtLineLabel->setText(xi18n("Start at line:")); m_startAtLineLabel->setEnabled(false); } updateRowCountInfo(); m_blockUserEvents = false; repaint(); } QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream *inputStream) { // try to detect delimiter // \t has priority, then ; then , const qint64 origOffset = inputStream->pos(); QChar c, prevChar = 0; int detectedDelimiter = 0; bool insideQuote = false; //characters by priority const int CH_TAB_AFTER_QUOTE = 500; const int CH_SEMICOLON_AFTER_QUOTE = 499; const int CH_COMMA_AFTER_QUOTE = 498; const int CH_TAB = 200; // \t const int CH_SEMICOLON = 199; // ; const int CH_COMMA = 198; // , QList tabsPerLine, semicolonsPerLine, commasPerLine; int tabs = 0, semicolons = 0, commas = 0; int line = 0; bool wasChar13 = false; // true if previous x was '\r' for (int i = 0; !inputStream->atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) { (*m_inputStream) >> c; // read one char if (prevChar == '"') { if (c != '"') //real quote (not double "") insideQuote = !insideQuote; } if (insideQuote) { prevChar = c; continue; } if (c == ' ') continue; if (wasChar13 && c == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = c == '\r'; if (c == '\n' || c == '\r') {//end of line //remember # of tabs/semicolons/commas in this line tabsPerLine += tabs; tabs = 0; semicolonsPerLine += semicolons; semicolons = 0; commasPerLine += commas; commas = 0; line++; } else if (c == '\t') { tabs++; detectedDelimiter = qMax(prevChar == '"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter); } else if (c == ';') { semicolons++; detectedDelimiter = qMax(prevChar == '"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter); } else if (c == ',') { commas++; detectedDelimiter = qMax(prevChar == '"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter); } prevChar = c; } inputStream->seek(origOffset); //restore orig. offset //now, try to find a delimiter character that exists the same number of times in all the checked lines //this detection method has priority over others QList::ConstIterator it; if (tabsPerLine.count() > 1) { tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first(); for (it = tabsPerLine.constBegin(); it != tabsPerLine.constEnd(); ++it) { if (tabs != *it) break; } if (tabs > 0 && it == tabsPerLine.constEnd()) return "\t"; } if (semicolonsPerLine.count() > 1) { semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first(); for (it = semicolonsPerLine.constBegin(); it != semicolonsPerLine.constEnd(); ++it) { if (semicolons != *it) break; } if (semicolons > 0 && it == semicolonsPerLine.constEnd()) return ";"; } if (commasPerLine.count() > 1) { commas = commasPerLine.first(); for (it = commasPerLine.constBegin(); it != commasPerLine.constEnd(); ++it) { if (commas != *it) break; } if (commas > 0 && it == commasPerLine.constEnd()) return ","; } //now return the winning character by looking at CH_* symbol if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB) return "\t"; if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON) return ";"; if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA) return ","; return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default } tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, bool inGUI) { enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD, S_COMMENT } state = S_START; field.clear(); const bool ignoreDups = m_ignoreDuplicates->isChecked(); bool lastCharDelimiter = false; bool nextRow = false; row = column = 1; m_prevColumnForSetText = 0; maxColumn = 0; QChar x; const bool hadInputStream = m_inputStream != 0; delete m_inputStream; if (m_mode == Clipboard) { m_inputStream = new QTextStream(&m_clipboardData, QIODevice::ReadOnly); if (!hadInputStream) m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER); } else { m_file->seek(0); //always seek at 0 because loadRows() is called many times m_inputStream = new QTextStream(m_file); QTextCodec *codec = KCharsets::charsets()->codecForName(m_options.encoding); if (codec) { m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250")); } if (m_detectDelimiter) { const QString delimiter(detectDelimiterByLookingAtFirstBytesOfFile(m_inputStream)); if (m_delimiterWidget->delimiter() != delimiter) m_delimiterWidget->setDelimiter(delimiter); } } const QChar delimiter(m_delimiterWidget->delimiter()[0]); const QChar commentSymbol(m_commentWidget->commentSymbol()[0]); m_stoppedAt_MAX_BYTES_TO_PREVIEW = false; if (m_importingProgressBar) { m_elapsedTimer.start(); m_elapsedMs = m_elapsedTimer.elapsed(); } int offset = 0; bool wasChar13 = false; // true if previous x was '\r' for (;; ++offset) { if (m_importingProgressBar && (offset % 0x100) == 0 && (m_elapsedMs + PROGRESS_STEP_MS) < m_elapsedTimer.elapsed()) { //update progr. bar dlg on final exporting m_elapsedMs = m_elapsedTimer.elapsed(); m_importingProgressBar->setValue(offset); qApp->processEvents(); if (m_importCanceled) { return ::cancelled; } } if (m_inputStream->atEnd()) { if (x != '\n' && x != '\r') { x = '\n'; // simulate missing \n at end wasChar13 = false; } else { break; // finish! } } else { (*m_inputStream) >> x; // read one char } if (wasChar13 && x == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = x == '\r'; if (offset == 0 && x.unicode() == 0xfeff) { // Ignore BOM, the "Byte Order Mark" // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf) // Probably fixed in Qt4. continue; } switch (state) { case S_START : if (x == m_textquote) { state = S_QUOTED_FIELD; } else if (x == delimiter) { field.clear(); if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } else if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { if (!inGUI) { //fill remaining empty fields (database wants them explicitly) for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { setText(row - m_startline, additionalColumn, QString(), inGUI); } } nextRow = true; if (ignoreDups && lastCharDelimiter) { // we're ignoring repeated delimiters so remove any extra trailing delimiters --column; } maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; maxColumn -= 1; break; } } else { field += x; state = S_MAYBE_NORMAL_FIELD; } break; case S_QUOTED_FIELD : if (x == m_textquote) { state = S_MAYBE_END_OF_QUOTED_FIELD; } /*allow \n inside quoted fields else if (x == '\n') { setText(row - m_startline, column, field, inGUI); field = ""; if (x == '\n') { nextRow = true; maxColumn = qMax( maxColumn, column ); column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; }*/ else { field += x; } break; case S_MAYBE_END_OF_QUOTED_FIELD : if (x == m_textquote) { field += x; //no, this was just escaped quote character state = S_QUOTED_FIELD; } else if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_END_OF_QUOTED_FIELD : if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_COMMENT : if (x == '\n' || x == '\r') { state = S_START; } if (lastCharDelimiter) { lastCharDelimiter = false; } break; case S_MAYBE_NORMAL_FIELD : if (x == m_textquote) { field.clear(); state = S_QUOTED_FIELD; } break; case S_NORMAL_FIELD : if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { field += x; } } // switch if (x != delimiter) lastCharDelimiter = false; if (nextRow) { if (!inGUI && !shouldSaveRow(row - m_startline, m_1stRowForFieldNames->isChecked())) { // do not save to the database 1st row if it contains column names m_valuesToInsert.clear(); } else if (!saveRow(inGUI)) return false; ++row; } if (m_firstFillTableCall && row == 2 && !m_1stRowForFieldNames->isChecked() && m_table->firstRowForFieldNames()) { m_table->clear(); m_firstFillTableCall = false; //this trick is allowed only once, on startup m_1stRowForFieldNames->setChecked(true); //this will reload table m_blockUserEvents = false; repaint(); return false; } if (!m_importingProgressBar && row % 20 == 0) { qApp->processEvents(); //only for GUI mode: if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCanceled()) { delete m_loadingProgressDlg; m_loadingProgressDlg = 0; m_dialogCanceled = true; reject(); return false; } } if (!m_firstFillTableCall && m_loadingProgressDlg) { m_loadingProgressDlg->setValue(qMin(m_maximumRowsForPreview, row)); } if (inGUI && row > (m_maximumRowsForPreview + (m_table->firstRowForFieldNames() ? 1 : 0))) { qDebug() << "loading stopped at row #" << m_maximumRowsForPreview; break; } if (nextRow) { nextRow = false; //additional speedup: stop processing now if too many bytes were loaded for preview //qDebug() << offset; if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) { m_stoppedAt_MAX_BYTES_TO_PREVIEW = true; return true; } } } return true; } void KexiCSVImportDialog::updateColumn(int col) { KDbField::Type detectedType = d->detectedType(col); if (detectedType == KDbField::InvalidType) { d->setDetectedType(col, KDbField::Text); //entirely empty column detectedType = KDbField::Text; } m_table->setHeaderData(col, Qt::Horizontal, QString(xi18n("Column %1", col + 1) + " \n(" + kexiCSVImportStatic->typeNames[detectedType].toLower() + ") ")); m_tableView->horizontalHeader()->adjustSize(); if (m_primaryKeyColumn == -1 && isPrimaryKeyAllowed(col)) { m_primaryKeyColumn = col; } } bool KexiCSVImportDialog::isPrimaryKeyAllowed(int col) { QList *list = d->uniquenessTest(col); if (m_primaryKeyColumn != -1 || !list || list->isEmpty()) { return false; } bool result = false; int expectedRowCount = m_table->rowCount(); if (m_table->firstRowForFieldNames()) { expectedRowCount--; } if (list->count() == expectedRowCount) { qSort(*list); QList::ConstIterator it = list->constBegin(); int prevValue = *it; ++it; for (; it != list->constEnd() && prevValue != (*it); ++it) { prevValue = (*it); } result = it == list->constEnd(); // no duplicates } list->clear(); // not needed now: conserve memory return result; } void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text) { int intValue; KDbField::Type type = d->detectedType(col); if (row == 1 || type != KDbField::Text) { bool found = false; if (text.isEmpty() && type == KDbField::InvalidType) found = true; //real type should be found later //detect type because it's 1st row or all prev. rows were not text //-FP number? (trying before "number" type is a must) if (!found && (row == 1 || type == KDbField::Integer || type == KDbField::Double || type == KDbField::InvalidType)) { bool ok = text.isEmpty() || m_fpNumberRegExp1.match(text).hasMatch() || m_fpNumberRegExp2.match(text).hasMatch(); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Double); found = true; //yes } } //-number? if (!found && (row == 1 || type == KDbField::Integer || type == KDbField::InvalidType)) { bool ok = text.isEmpty();//empty values allowed if (!ok) intValue = text.toInt(&ok); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Integer); found = true; //yes } } //-date? if (!found && (row == 1 || type == KDbField::Date || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_dateRegExp.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Date); found = true; //yes } } //-time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_timeRegExp1.match(text).hasMatch() || m_timeRegExp2.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Time); found = true; //yes } } //-date/time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if (row == 1 || type == KDbField::InvalidType) { bool detected = text.isEmpty(); if (!detected) { const QStringList dateTimeList(text.split(' ')); bool ok = dateTimeList.count() >= 2; //! @todo also support ISODateTime's "T" separator? //! @todo also support timezones? if (ok) { //try all combinations QString datePart(dateTimeList[0].trimmed()); QString timePart(dateTimeList[1].trimmed()); ok = m_dateRegExp.match(datePart).hasMatch() && (m_timeRegExp1.match(timePart).hasMatch() || m_timeRegExp2.match(timePart).hasMatch()); } detected = ok; } if (detected) { d->setDetectedType(col, KDbField::DateTime); found = true; //yes } } } if (!found && type == KDbField::InvalidType && !text.isEmpty()) { //eventually, a non-emptytext after a while d->setDetectedType(col, KDbField::Text); found = true; //yes } //default: text type (already set) } type = d->detectedType(col); //qDebug() << type; if (type == KDbField::Integer) { // check uniqueness for this value QList *list = d->uniquenessTest(col); if (text.isEmpty()) { if (list) { list->clear(); // empty value cannot be in PK } } else { if (!list) { list = new QList(); d->setUniquenessTest(col, list); } list->append(intValue); } } } QDate KexiCSVImportDialog::buildDate(int y, int m, int d) const { if (y < 100) { if ((1900 + y) >= m_minimumYearFor100YearSlidingWindow) return QDate(1900 + y, m, d); else return QDate(2000 + y, m, d); } return QDate(y, m, d); } bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date) { QRegularExpressionMatch match = m_dateRegExp.match(text); if (!match.hasMatch()) return false; //dddd - dd - dddd //1 2 3 4 5 <- pos const int d1 = match.captured(1).toInt(), d3 = match.captured(3).toInt(), d5 = match.captured(5).toInt(); switch (m_options.dateFormat) { case KexiCSVImportOptions::DMY: date = buildDate(d5, d3, d1); break; case KexiCSVImportOptions::YMD: date = buildDate(d1, d3, d5); break; case KexiCSVImportOptions::MDY: date = buildDate(d5, d1, d3); break; case KexiCSVImportOptions::AutoDateFormat: if (match.captured(2) == "/") { //probably separator for american format mm/dd/yyyy date = buildDate(d5, d1, d3); } else { if (d5 > 31) //d5 == year date = buildDate(d5, d3, d1); else //d1 == year date = buildDate(d1, d3, d5); } break; default:; } return date.isValid(); } bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time) { time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1 if (time.isValid()) return true; QRegularExpressionMatch match = m_timeRegExp2.match(text); if (match.hasMatch()) { //hh:mm:ss time = QTime(match.captured(1).toInt(), match.captured(3).toInt(), match.captured(5).toInt()); return true; } return false; } void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI) { if (!inGUI) { if (!shouldSaveRow(row, m_1stRowForFieldNames->isChecked())) return; // do not care about this value if it contains column names (these were already used) //save text directly to database buffer if (m_prevColumnForSetText == 0) { //1st call m_valuesToInsert.clear(); if (m_implicitPrimaryKeyAdded) { m_valuesToInsert << QVariant(); //id will be autogenerated here } } if ((m_prevColumnForSetText + 1) < col) { //skipped one or more columns //before this: save NULLs first for (int i = m_prevColumnForSetText + 1; i < col; i++) { if (m_options.nullsImportedAsEmptyTextChecked && KDbField::isTextType(d->detectedType(i-1))) { m_valuesToInsert << QString(""); } else { m_valuesToInsert << QVariant(); } } } m_prevColumnForSetText = col; const KDbField::Type detectedType = d->detectedType(col-1); if (detectedType == KDbField::Integer) { m_valuesToInsert << (text.isEmpty() ? QVariant() : text.toInt()); //! @todo what about time and float/double types and different integer subtypes? } else if (detectedType == KDbField::Double) { //replace ',' with '.' QByteArray t(text.toLatin1()); const int textLen = t.length(); for (int i = 0; i < textLen; i++) { if (t[i] == ',') { t[i] = '.'; break; } } m_valuesToInsert << (t.isEmpty() ? QVariant() : t.toDouble()); } else if (detectedType == KDbField::Boolean) { const QString t(text.trimmed().toLower()); if (t.isEmpty()) m_valuesToInsert << QVariant(); else if (t == "0" || t == m_stringNo || t == m_stringI18nNo || t == m_stringFalse || t == m_stringI18nFalse) m_valuesToInsert << QVariant(false); else m_valuesToInsert << QVariant(true); //anything nonempty } else if (detectedType == KDbField::Date) { QDate date; if (parseDate(text, date)) m_valuesToInsert << date; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::Time) { QTime time; if (parseTime(text, time)) m_valuesToInsert << time; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::DateTime) { QStringList dateTimeList(text.split(' ')); if (dateTimeList.count() < 2) dateTimeList = text.split('T'); //also support ISODateTime's "T" separator //! @todo also support timezones? if (dateTimeList.count() >= 2) { //try all combinations QString datePart(dateTimeList[0].trimmed()); QDate date; if (parseDate(datePart, date)) { QString timePart(dateTimeList[1].trimmed()); QTime time; if (parseTime(timePart, time)) m_valuesToInsert << QDateTime(date, time); else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else { // Text type and the rest if (m_options.nullsImportedAsEmptyTextChecked && text.isNull()) { //default value is empty string not null - otherwise querying data without knowing SQL is very confusing m_valuesToInsert << QString(""); } else { m_valuesToInsert <columnCount() < col) { m_table->setColumnCount(col); } if (!m_1stRowForFieldNames->isChecked()) { if ((row + m_startline) == 1) {//this row is for column name if (m_table->firstRowForFieldNames() && !m_1stRowForFieldNames->isChecked()) { QString f(text.simplified()); if (f.isEmpty() || !f[0].isLetter()) { m_table->setFirstRowForFieldNames(false); } } } row++; //1st row was for column names } else { if ((row + m_startline) == 1) {//this is for column name m_table->setRowCount(1); QString colName(text.simplified()); if (!colName.isEmpty()) { if (colName.at(0) >= QLatin1Char('0') && colName.at(0) <= QLatin1Char('9')) { colName.prepend(xi18n("Column") + " "); } m_table->setData(m_table->index(0, col - 1), colName); } return; } } if (row < 2) // skipped by the user return; if (m_table->rowCount() < row) { m_table->setRowCount(row + 100); /* We add more rows at a time to limit recalculations */ m_adjustRows = true; } m_table->setData(m_table->index(row-1 ,col-1),m_options.trimmedInTextValuesChecked ? text.trimmed() : text); detectTypeAndUniqueness(row - 1, col - 1, text); } bool KexiCSVImportDialog::saveRow(bool inGUI) { if (inGUI) { //nothing to do return true; } bool res = m_importingStatement.execute(m_valuesToInsert); //! @todo move if (!res) { const QStringList msgList = KexiUtils::convertTypesUsingMethod(m_valuesToInsert); const KMessageBox::ButtonCode msgRes = KMessageBox::warningContinueCancelList(this, xi18nc("@info", "An error occurred during insert record."), QStringList(msgList.join(";")), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "SkipImportErrors" ); res = msgRes == KMessageBox::Continue; } m_valuesToInsert.clear(); return res; } void KexiCSVImportDialog::adjustRows(int iRows) { if (m_adjustRows) { m_table->setRowCount(iRows); m_adjustRows = false; for (int i = 0; i < iRows; i++) m_tableView->resizeRowToContents(i); } } void KexiCSVImportDialog::formatChanged(int index) { if (index < 0 || index >= kexiCSVImportStatic->types.size()) return; KDbField::Type type = kexiCSVImportStatic->types[index]; d->setDetectedType(m_tableView->currentIndex().column(), type); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->setChecked(m_primaryKeyColumn == m_tableView->currentIndex().column() && m_primaryKeyField->isEnabled()); updateColumn(m_tableView->currentIndex().column()); } void KexiCSVImportDialog::delimiterChanged(const QString& delimiter) { Q_UNUSED(delimiter); m_columnsAdjusted = false; m_detectDelimiter = false; //selected by hand: do not detect in the future //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::commentSymbolChanged(const QString& commentSymbol) { QString noneString = QString(xi18n("None")); if (commentSymbol.compare(noneString) == 0) { m_parseComments = false; } else { m_parseComments = true; } m_columnsAdjusted = false; m_detectDelimiter = false; //selected by hand: do not detect in the future //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::textquoteSelected(int) { const QString tq(m_comboQuote->textQuote()); if (tq.isEmpty()) m_textquote = 0; else m_textquote = tq[0]; qDebug() << m_textquote; //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::fillTableLater() { m_table->setColumnCount(0); QTimer::singleShot(10, this, SLOT(fillTable())); } void KexiCSVImportDialog::startlineSelected(int startline) { if (m_startline == (startline - 1)) return; m_startline = startline - 1; m_adjustRows = true; m_columnsAdjusted = false; fillTable(); m_tableView->setFocus(); } void KexiCSVImportDialog::currentCellChanged(const QModelIndex &cur, const QModelIndex &prev) { if (prev.column() == cur.column() || !cur.isValid()) return; const KDbField::Type type = d->detectedType(cur.column()); m_formatCombo->setCurrentIndex(kexiCSVImportStatic->indicesForTypes.value(type, -1)); m_formatLabel->setText(xi18n("Format for column %1:", cur.column() + 1)); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled() m_primaryKeyField->setChecked(m_primaryKeyColumn == cur.column()); m_primaryKeyField->blockSignals(false); } //! Used in emergency by accept() void KexiCSVImportDialog::dropDestinationTable(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { m_importingProgressBar->hide(); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ m_destinationTableSchema = 0; m_conn = 0; } //! Used in emergency by accept() void KexiCSVImportDialog::raiseErrorInAccept(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { finishButton()->setEnabled(true); KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; delete m_destinationTableSchema; m_destinationTableSchema = 0; m_conn = 0; backButton()->setEnabled(true); m_importInProgress = false; m_importingProgressBar->hide(); } void KexiCSVImportDialog::accept() { if (d->imported) { parentWidget()->raise(); bool openingCanceled; KexiWindow *win = KexiMainWindowIface::global()->openedWindowFor(m_partItemForSavedTable); if (win) { KexiMainWindowIface::global()->closeObject(m_partItemForSavedTable); } KexiMainWindowIface::global()->openObject(m_partItemForSavedTable, Kexi::DataViewMode, &openingCanceled); KAssistantDialog::accept(); } else { import(); } } void KexiCSVImportDialog::import() { //! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiWindow code is moved to non-gui place KMessageBox::enableMessage("SkipImportErrors"); KexiGUIMessageHandler msg; //! @todo make it better integrated with main window KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTableOption->isChecked()) { m_destinationTableSchema = new KDbTableSchema(m_partItemForSavedTable->name()); m_destinationTableSchema->setCaption(m_partItemForSavedTable->caption()); m_destinationTableSchema->setDescription(m_partItemForSavedTable->description()); const int numCols(m_table->columnCount()); m_implicitPrimaryKeyAdded = false; //add PK if user wanted it int msgboxResult; if ( m_primaryKeyColumn == -1 && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "No primary key (autonumber) has been defined." "Should it be automatically defined on import (recommended)?" "An imported table without a primary key may not be " "editable (depending on database type)."), QString(), KGuiItem(xi18nc("@action:button Add Database Primary Key to a Table", "&Add Primary Key"), KexiIconName("database-key")), KGuiItem(xi18nc("@action:button Do Not Add Database Primary Key to a Table", "Do &Not Add"), KStandardGuiItem::no().icon())))) { if (msgboxResult == KMessageBox::Cancel) { raiseErrorInAccept(project, m_partItemForSavedTable); return; //cancel accepting } //add implicit PK field //! @todo make this field hidden (what about e.g. pgsql?) m_implicitPrimaryKeyAdded = true; QString fieldName("id"); QString fieldCaption("Id"); QSet colnames; for (int col = 0; col < numCols; col++) colnames.insert(m_table->data(m_table->index(0, col)).toString().toLower().simplified()); if (colnames.contains(fieldName)) { int num = 1; while (colnames.contains(fieldName + QString::number(num))) num++; fieldName += QString::number(num); fieldCaption += QString::number(num); } KDbField *field = new KDbField( fieldName, KDbField::Integer, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now field->setPrimaryKey(true); field->setAutoIncrement(true); if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } for (int col = 0; col < numCols; col++) { QString fieldCaption(m_table->data(m_table->index(0, col)).toString().simplified()); QString fieldName; if (fieldCaption.isEmpty()) { int i = 0; do { fieldCaption = xi18nc("@title:column Column 1, Column 2, etc.", "Column %1", i + 1); fieldName = KDb::stringToIdentifier(fieldCaption); if (!m_destinationTableSchema->field(fieldName)) { break; } i++; } while (true); } else { fieldName = KDb::stringToIdentifier(fieldCaption); if (m_destinationTableSchema->field(fieldName)) { QString fixedFieldName; int i = 2; //"apple 2, apple 3, etc. if there're many "apple" names do { fixedFieldName = fieldName + "_" + QString::number(i); if (!m_destinationTableSchema->field(fixedFieldName)) break; i++; } while (true); fieldName = fixedFieldName; fieldCaption += (" " + QString::number(i)); } } KDbField::Type detectedType = d->detectedType(col); //! @todo what about time and float/double types and different integer subtypes? //! @todo what about long text? if (detectedType == KDbField::InvalidType) { detectedType = KDbField::Text; } KDbField *field = new KDbField( fieldName, detectedType, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now if ((int)col == m_primaryKeyColumn) { field->setPrimaryKey(true); field->setAutoIncrement(true); } if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } } else { m_implicitPrimaryKeyAdded = false; m_destinationTableSchema = m_conn->tableSchema(m_partItemForSavedTable->name()); int firstColumn = 0; if (m_destinationTableSchema->field(0)->isPrimaryKey() && m_primaryKeyColumn == -1) { m_implicitPrimaryKeyAdded = true; firstColumn = 1; } if (m_destinationTableSchema->fields()->size() - firstColumn < m_table->columnCount()) { KMessageBox::error(this, xi18n("Field count does not match." "Please choose another table.")); return; } } m_importInProgress = true; backButton()->setEnabled(false); finishButton()->setEnabled(false); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } KDbTransaction transaction = m_conn->beginTransaction(); if (transaction.isNull()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } KDbTransactionGuard tg(transaction); //-create physical table if (m_newTableOption->isChecked() && !m_conn->createTable(m_destinationTableSchema, KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::Default) & ~KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::DropDestination))) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } m_importingStatement = m_conn->prepareStatement( KDbPreparedStatement::InsertStatement, m_destinationTableSchema); if (!m_importingStatement.isValid()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } if (m_file) { m_importProgressLabel->setText(xi18n("Importing data...")); m_importingProgressBar->setMaximum(QFileInfo(*m_file).size() - 1); m_importingProgressBar->show(); m_importProgressLabel->show(); } int row, column, maxColumn; QString field; // main job tristate res = loadRows(field, row, column, maxColumn, false /*!gui*/); if (true != res) { //importing canceled or failed if (!res) { //do not display err msg when res == cancelled m_importProgressLabel->setText(xi18n("Import has been canceled.")); } else if (~res) { m_importProgressLabel->setText(xi18n("Error occurred during import.")); } raiseErrorInAccept(project, m_partItemForSavedTable); return; } // file with only one line without '\n' if (field.length() > 0) { setText(row - m_startline, column, field, false /*!gui*/); //fill remaining empty fields (database wants them explicitly) for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { setText(row - m_startline, additionalColumn, QString(), false /*!gui*/); } if (!saveRow(false /*!gui*/)) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } ++row; field.clear(); } if (!tg.commit()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } //-now we can store the item if (m_newTableOption->isChecked()) { m_partItemForSavedTable->setIdentifier(m_destinationTableSchema->id()); project->addStoredItem(part->info(), m_partItemForSavedTable); } m_importingProgressBar->hide(); m_importProgressLabel->setText(xi18nc("@info", "Data has been successfully imported to table %1.", m_destinationTableSchema->name())); m_importInProgress = false; //qDebug()<<"IMPORT DONE"; KGuiItem::assign(finishButton(), KStandardGuiItem::open()); finishButton()->setEnabled(true); KGuiItem::assign(button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); nextButton()->setEnabled(false); backButton()->setEnabled(false); m_conn = 0; d->imported = true; } void KexiCSVImportDialog::reject() { //qDebug()<<"IMP_P"<horizontalHeaderItem(col)->text(); if (header == xi18nc("Text type for column", "Text")) return TEXT; else if (header == xi18nc("Numeric type for column", "Number")) return NUMBER; else if (header == xi18nc("Currency type for column", "Currency")) return CURRENCY; else return DATE; } QString KexiCSVImportDialog::getText(int row, int col) { return m_table->item(row, col)->text(); } void KexiCSVImportDialog::ignoreDuplicatesChanged(int) { fillTable(); } void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int state) { m_adjustRows = true; if (m_1stRowForFieldNames->isChecked() && m_startline > 0 && m_startline >= (m_startAtLineSpinBox->maximum() - 1)) { m_startline--; } m_columnsAdjusted = false; fillTable(); m_table->setFirstRowForFieldNames(state); } void KexiCSVImportDialog::optionsButtonClicked() { KexiCSVImportOptionsDialog dlg(m_options, this); if (QDialog::Accepted != dlg.exec()) return; KexiCSVImportOptions newOptions(dlg.options()); if (m_options != newOptions) { m_options = newOptions; if (!openData()) return; fillTable(); } } bool KexiCSVImportDialog::eventFilter(QObject * watched, QEvent * e) { QEvent::Type t = e->type(); // temporary disable keyboard and mouse events for time-consuming tasks if (m_blockUserEvents && (t == QEvent::KeyPress || t == QEvent::KeyRelease || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonDblClick || t == QEvent::Paint)) return true; if (watched == m_startAtLineSpinBox && t == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { m_tableView->setFocus(); return true; } } return QDialog::eventFilter(watched, e); } void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = on ? m_tableView->currentIndex().column() : -1; setPrimaryKeyIcon(m_primaryKeyColumn, true); } void KexiCSVImportDialog::setPrimaryKeyIcon(int column, bool set) { if (column >= 0 && column < m_table->columnCount()) { m_table->setData(m_table->index(0, column), set ? m_pkIcon : QPixmap(), Qt::DecorationRole); } } void KexiCSVImportDialog::updateRowCountInfo() { m_infoLbl->setFileName(m_fname); if (m_allRowsLoadedInPreview) { m_infoLbl->setCommentText( xi18nc("row count", "(rows: %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(QString()); } else { m_infoLbl->setCommentText( xi18nc("row count", "(rows: more than %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(xi18n("Not all rows are visible on this preview")); } } QPushButton* KexiCSVImportDialog::configureButton() const { // Help button is used as Configure return button(QDialogButtonBox::Help); } #include "kexicsvimportdialog.moc" diff --git a/src/plugins/queries/kexiquerydesignerguieditor.cpp b/src/plugins/queries/kexiquerydesignerguieditor.cpp index 445b6ee96..7f5dfde14 100644 --- a/src/plugins/queries/kexiquerydesignerguieditor.cpp +++ b/src/plugins/queries/kexiquerydesignerguieditor.cpp @@ -1,1938 +1,1939 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004-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. */ #include "kexiquerydesignerguieditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kexiquerypart.h" #include "kexiqueryview.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 //! @todo remove KEXI_NO_QUERY_TOTALS later #define KEXI_NO_QUERY_TOTALS //! indices for table columns #define COLUMN_ID_COLUMN 0 #define COLUMN_ID_TABLE 1 #define COLUMN_ID_VISIBLE 2 #ifdef KEXI_NO_QUERY_TOTALS # define COLUMN_ID_SORTING 3 # define COLUMN_ID_CRITERIA 4 #else # define COLUMN_ID_TOTALS 3 # define COLUMN_ID_SORTING 4 # define COLUMN_ID_CRITERIA 5 #endif /*! @internal */ class Q_DECL_HIDDEN KexiQueryDesignerGuiEditor::Private { public: Private(KexiQueryDesignerGuiEditor *p) : q(p) - , conn(0) { droppedNewRecord = 0; slotTableAdded_enabled = true; sortColumnPreferredWidth = 0; } bool changeSingleCellValue(KDbRecordData *recordData, int columnNumber, const QVariant& value, KDbResultInfo* result) { data->clearRecordEditBuffer(); if (!data->updateRecordEditBuffer(recordData, columnNumber, value) || !data->saveRecordChanges(recordData, true)) { if (result) *result = data->result(); return false; } return true; } KexiQueryDesignerGuiEditor *q; KDbTableViewData *data; KexiDataTableView *dataTable; - //! @todo KEXI3 use equivalent of QPointer - KDbConnection *conn; - KexiRelationsView *relations; KexiSectionHeader *head; QSplitter *spl; /*! Used to remember in slotDroppedAtRow() what data was dropped, so we can create appropriate prop. set in slotRecordInserted() This information is cached and entirely refreshed on updateColumnsData(). */ KDbTableViewData *fieldColumnData, *tablesColumnData; /*! Collects identifiers selected in 1st (field) column, so we're able to distinguish between table identifiers selected from the dropdown list, and strings (e.g. expressions) entered by hand. This information is cached and entirely refreshed on updateColumnsData(). The dict is filled with (char*)1 values (doesn't matter what it is); */ QSet fieldColumnIdentifiers; void addFieldColumnIdentifier(const QString& id) { fieldColumnIdentifiers.insert(id.toLower()); } int comboArrowWidth; int sortColumnPreferredWidth; void initSortColumnPreferredWidth(const QVector &items) { int maxw = -1; for (int i=0; i < items.size(); ++i) { maxw = qMax(maxw, q->fontMetrics().width(items[i] + QLatin1String(" "))); } sortColumnPreferredWidth = maxw + KexiUtils::comboBoxArrowSize(q->style()).width(); } KexiDataAwarePropertySet* sets; KDbRecordData *droppedNewRecord; QString droppedNewTable, droppedNewField; bool slotTableAdded_enabled; }; static bool isAsterisk(const QString& tableName, const QString& fieldName) { return tableName == "*" || fieldName.endsWith('*'); } //! @internal \return true if sorting is allowed for \a fieldName and \a tableName static bool sortingAllowed(const QString& fieldName, const QString& tableName) { return !(fieldName == "*" || (fieldName.isEmpty() && tableName == "*")); } //========================================================= KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor( QWidget *parent) : KexiView(parent) , d(new Private(this)) { - d->conn = KexiMainWindowIface::global()->project()->dbConnection(); - d->spl = new QSplitter(Qt::Vertical, this); d->spl->setChildrenCollapsible(false); d->relations = new KexiRelationsView(d->spl); d->spl->addWidget(d->relations); d->relations->setObjectName("relations"); connect(d->relations, SIGNAL(tableAdded(KDbTableSchema*)), this, SLOT(slotTableAdded(KDbTableSchema*))); connect(d->relations, SIGNAL(tableHidden(KDbTableSchema*)), this, SLOT(slotTableHidden(KDbTableSchema*))); connect(d->relations, SIGNAL(appendFields(KDbTableOrQuerySchema&,QStringList)), this, SLOT(slotAppendFields(KDbTableOrQuerySchema&,QStringList))); d->head = new KexiSectionHeader(xi18n("Query Columns"), Qt::Vertical, d->spl); d->spl->addWidget(d->head); d->dataTable = new KexiDataTableView(d->head, false); d->head->setWidget(d->dataTable); d->dataTable->setObjectName("guieditor_dataTable"); d->dataTable->dataAwareObject()->setSpreadSheetMode(true); d->data = new KDbTableViewData(); //just empty data d->sets = new KexiDataAwarePropertySet(this, d->dataTable->dataAwareObject()); connect(d->sets, SIGNAL(propertyChanged(KPropertySet&,KProperty&)), this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&))); initTableColumns(); initTableRows(); QList c; c << COLUMN_ID_COLUMN << COLUMN_ID_TABLE << COLUMN_ID_CRITERIA; if (d->dataTable->tableView()/*sanity*/) { d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_VISIBLE); d->dataTable->tableView()->setColumnWidth(COLUMN_ID_SORTING, d->sortColumnPreferredWidth); d->dataTable->tableView()->setStretchLastColumn(true); d->dataTable->tableView()->maximizeColumnsWidth(c); d->dataTable->tableView()->setDropsAtRecordEnabled(true); connect(d->dataTable->tableView(), SIGNAL(dragOverRecord(KDbRecordData*,int,QDragMoveEvent*)), this, SLOT(slotDragOverTableRecord(KDbRecordData*,int,QDragMoveEvent*))); connect(d->dataTable->tableView(), SIGNAL(droppedAtRecord(KDbRecordData*,int,QDropEvent*,KDbRecordData*&)), this, SLOT(slotDroppedAtRecord(KDbRecordData*,int,QDropEvent*,KDbRecordData*&))); connect(d->dataTable->tableView(), SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()), this, SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode())); } connect(d->data, SIGNAL(aboutToChangeCell(KDbRecordData*,int,QVariant*,KDbResultInfo*)), this, SLOT(slotBeforeCellChanged(KDbRecordData*,int,QVariant*,KDbResultInfo*))); connect(d->data, SIGNAL(recordInserted(KDbRecordData*,int,bool)), this, SLOT(slotRecordInserted(KDbRecordData*,int,bool))); connect(d->relations, SIGNAL(tablePositionChanged(KexiRelationsTableContainer*)), this, SLOT(slotTablePositionChanged(KexiRelationsTableContainer*))); connect(d->relations, SIGNAL(aboutConnectionRemove(KexiRelationsConnection*)), this, SLOT(slotAboutConnectionRemove(KexiRelationsConnection*))); addChildView(d->relations); addChildView(d->dataTable); setViewWidget(d->spl, false/* no focus proxy*/); setFocusProxy(d->dataTable); d->relations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->head->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); updateGeometry(); d->spl->setSizes(QList() << 800 << 400); } KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor() { delete d; } void KexiQueryDesignerGuiEditor::initTableColumns() { KDbTableViewColumn *col1 = new KDbTableViewColumn("column", KDbField::Enum, xi18n("Column"), xi18n("Describes field name or expression for the designed query.")); col1->setRelatedDataEditable(true); d->fieldColumnData = new KDbTableViewData(KDbField::Text, KDbField::Text); col1->setRelatedData(d->fieldColumnData); d->data->addColumn(col1); KDbTableViewColumn *col2 = new KDbTableViewColumn("table", KDbField::Enum, xi18n("Table"), xi18n("Describes table for a given field. Can be empty.")); d->tablesColumnData = new KDbTableViewData(KDbField::Text, KDbField::Text); col2->setRelatedData(d->tablesColumnData); d->data->addColumn(col2); KDbTableViewColumn *col3 = new KDbTableViewColumn("visible", KDbField::Boolean, xi18n("Visible"), xi18n("Describes visibility for a given field or expression.")); col3->field()->setDefaultValue(QVariant(false)); col3->field()->setNotNull(true); d->data->addColumn(col3); #ifndef KEXI_NO_QUERY_TOTALS KDbTableViewColumn *col4 = new KDbTableViewColumn("totals", KDbField::Enum, futureI18n("Totals"), futureI18n("Describes a way of computing totals for a given field or expression.")); QVector totalsTypes; totalsTypes.append(futureI18n("Group by")); totalsTypes.append(futureI18n("Sum")); totalsTypes.append(futureI18n("Average")); totalsTypes.append(futureI18n("Min")); totalsTypes.append(futureI18n("Max")); //! @todo more like this col4->field()->setEnumHints(totalsTypes); d->data->addColumn(col4); #endif KDbTableViewColumn *col5 = new KDbTableViewColumn("sort", KDbField::Enum, xi18n("Sorting"), xi18n("Describes a way of sorting for a given field.")); QVector sortTypes; sortTypes.append(""); sortTypes.append(xi18n("Ascending")); sortTypes.append(xi18n("Descending")); col5->field()->setEnumHints(sortTypes); d->data->addColumn(col5); d->initSortColumnPreferredWidth(sortTypes); KDbTableViewColumn *col6 = new KDbTableViewColumn("criteria", KDbField::Text, xi18n("Criteria"), xi18n("Describes the criteria for a given field or expression.")); d->data->addColumn(col6); } void KexiQueryDesignerGuiEditor::initTableRows() { d->data->deleteAllRecords(); for (int i = 0; i < (int)d->sets->size(); i++) { KDbRecordData* data = d->data->createItem(); d->data->append(data); (*data)[COLUMN_ID_VISIBLE] = QVariant(false); } d->dataTable->dataAwareObject()->setData(d->data); updateColumnsData(); } void KexiQueryDesignerGuiEditor::updateColumnsData() { + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); d->dataTable->dataAwareObject()->acceptRecordEditing(); QStringList sortedTableNames; foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { sortedTableNames += cont->schema()->name(); } qSort(sortedTableNames); //several tables can be hidden now, so remove rows for these tables QList recordsToDelete; for (int r = 0; r < (int)d->sets->size(); r++) { KPropertySet *set = d->sets->at(r); if (set) { QString tableName = (*set)["table"].value().toString(); //QString fieldName = (*set)["field"].value().toString(); const bool allTablesAsterisk = tableName == "*" && d->relations->tables()->isEmpty(); const bool fieldNotFound = tableName != "*" && !(*set)["isExpression"].value().toBool() && sortedTableNames.end() == qFind(sortedTableNames.begin(), sortedTableNames.end(), tableName); if (allTablesAsterisk || fieldNotFound) { //table not found: mark this line for later removal recordsToDelete += r; } } } d->data->deleteRecords(recordsToDelete); //update 'table' and 'field' columns d->tablesColumnData->deleteAllRecords(); d->fieldColumnData->deleteAllRecords(); d->fieldColumnIdentifiers.clear(); KDbRecordData *data = d->fieldColumnData->createItem(); (*data)[COLUMN_ID_COLUMN] = "*"; (*data)[COLUMN_ID_TABLE] = "*"; d->fieldColumnData->append(data); d->addFieldColumnIdentifier((*data)[COLUMN_ID_COLUMN].toString()); //cache tempData()->unregisterForTablesSchemaChanges(); foreach(const QString& tableName, sortedTableNames) { //table /*! @todo what about query? */ const KDbTableSchema *table = d->relations->tables()->value(tableName)->schema()->table(); - KDbTableSchemaChangeListener::registerForChanges(d->conn, tempData(), table); //this table will be used + KDbTableSchemaChangeListener::registerForChanges(conn, tempData(), table); //this table will be used data = d->tablesColumnData->createItem(); (*data)[COLUMN_ID_COLUMN] = table->name(); (*data)[COLUMN_ID_TABLE] = (*data)[COLUMN_ID_COLUMN]; d->tablesColumnData->append(data); //fields data = d->fieldColumnData->createItem(); (*data)[COLUMN_ID_COLUMN] = QString(table->name() + ".*"); (*data)[COLUMN_ID_TABLE] = (*data)[COLUMN_ID_COLUMN]; d->fieldColumnData->append(data); d->addFieldColumnIdentifier((*data)[COLUMN_ID_COLUMN].toString()); //cache foreach(KDbField *field, *table->fields()) { data = d->fieldColumnData->createItem(); (*data)[COLUMN_ID_COLUMN] = QString(table->name() + '.' + field->name()); (*data)[COLUMN_ID_TABLE] = QString(" " + field->name()); d->fieldColumnData->append(data); d->addFieldColumnIdentifier((*data)[COLUMN_ID_COLUMN].toString()); //cache } } //! @todo } KexiRelationsView *KexiQueryDesignerGuiEditor::relationsView() const { return d->relations; } KexiQueryPartTempData * KexiQueryDesignerGuiEditor::tempData() const { return static_cast(window()->data()); } static QString msgCannotSwitch_EmptyDesign() { return xi18n("Cannot switch to data view, because query design is empty.\n" "First, please create your design."); } bool KexiQueryDesignerGuiEditor::buildSchema(QString *errMsg) { //build query schema + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiQueryPartTempData * temp = tempData(); if (temp->query()) { KexiQueryView *queryDataView = dynamic_cast(window()->viewForMode(Kexi::DataViewMode)); if (queryDataView) { queryDataView->setData(0); } temp->clearQuery(); } else { temp->setQuery(new KDbQuerySchema()); } //add tables foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { /*! @todo what about query? */ temp->query()->addTable(cont->schema()->table()); } //add fields, also build: // -WHERE expression // -ORDER BY list KDbExpression whereExpr; const int count = qMin(d->data->count(), d->sets->size()); bool fieldsFound = false; KDbTableViewDataConstIterator it(d->data->constBegin()); for (int i = 0; i < count && it != d->data->constEnd(); ++it, i++) { if (!(**it)[COLUMN_ID_TABLE].isNull() && (**it)[COLUMN_ID_COLUMN].isNull()) { //show message about missing field name, and set focus to that cell qDebug() << "no field provided!"; d->dataTable->dataAwareObject()->setCursorPosition(i, 0); if (errMsg) *errMsg = xi18nc("@info", "Select column for table %1", (**it)[COLUMN_ID_TABLE].toString()); return false; } KPropertySet *set = d->sets->at(i); if (set) { QString tableName = (*set)["table"].value().toString().trimmed(); QString fieldName = (*set)["field"].value().toString(); QString fieldAndTableName = fieldName; KDbField *currentField = 0; // will be set if this column is a single field if (!tableName.isEmpty()) fieldAndTableName.prepend(tableName + "."); const bool fieldVisible = (*set)["visible"].value().toBool(); QString criteriaStr = (*set)["criteria"].value().toString(); QByteArray alias((*set)["alias"].value().toByteArray()); if (!criteriaStr.isEmpty()) { KDbToken token; KDbExpression criteriaExpr = parseExpressionString(criteriaStr, &token, true/*allowRelationalOperator*/); if (!criteriaExpr.isValid()) {//for sanity if (errMsg) *errMsg = xi18nc("@info", "Invalid criteria %1", criteriaStr); return false; } //build relational expression for column variable KDbVariableExpression varExpr(fieldAndTableName); criteriaExpr = KDbBinaryExpression(varExpr, token, criteriaExpr); //critera ok: add it to WHERE section if (whereExpr.isNull()) { whereExpr = criteriaExpr; } else { //first expr. whereExpr = KDbBinaryExpression(whereExpr, KDbToken::AND, criteriaExpr); } } if (tableName.isEmpty()) { if ((*set)["isExpression"].value().toBool() == true) { //add expression column KDbToken dummyToken; KDbExpression columnExpr = parseExpressionString(fieldName, &dummyToken, false/*!allowRelationalOperator*/); if (!columnExpr.isValid()) { if (errMsg) *errMsg = xi18nc("@info", "Invalid expression %1", fieldName); return false; } if (fieldVisible) { if (!temp->query()->addExpression(columnExpr)) { if (errMsg) *errMsg = xi18nc("@info", "Invalid expression %1", fieldName); return false; } fieldsFound = true; } else { if (!temp->query()->addInvisibleExpression(columnExpr)) { if (errMsg) *errMsg = xi18nc("@info", "Invalid expression %1", fieldName); return false; } } if (!alias.isEmpty()) temp->query()->setColumnAlias(temp->query()->fieldCount() - 1, alias); } //! @todo } else if (tableName == "*") { //all tables asterisk if (fieldVisible) { if (!temp->query()->addAsterisk(new KDbQueryAsterisk(temp->query()))) { return false; } fieldsFound = true; } else { if (!temp->query()->addInvisibleAsterisk(new KDbQueryAsterisk(temp->query()))) { return false; } } continue; } else { - KDbTableSchema *t = d->conn->tableSchema(tableName); + KDbTableSchema *t = conn->tableSchema(tableName); if (fieldName == "*") { //single-table asterisk: + ".*" + number if (fieldVisible) { if (!t || !temp->query()->addAsterisk(new KDbQueryAsterisk(temp->query(), *t))) { return false; } fieldsFound = true; } else { if (!t || !temp->query()->addInvisibleAsterisk(new KDbQueryAsterisk(temp->query(), *t))) { return false; } } } else { if (!t) { qWarning() << "query designer: NO TABLE '" << (*set)["table"].value().toString() << "'"; continue; } currentField = t->field(fieldName); if (!currentField) { qWarning() << "query designer: NO FIELD '" << fieldName << "'"; continue; } if (!fieldVisible && criteriaStr.isEmpty() && set->contains("isExpression") && (*set)["sorting"].value().toString() != "nosorting") { qDebug() << "invisible field with sorting: do not add it to the fields list"; continue; } const int tablePosition = temp->query()->tablePosition(t->name()); if (fieldVisible) { if (!temp->query()->addField(currentField, tablePosition)) { return false; } fieldsFound = true; } else { if (!temp->query()->addInvisibleField(currentField, tablePosition)) { return false; } } if (!alias.isEmpty()) temp->query()->setColumnAlias(temp->query()->fieldCount() - 1, alias); } } } else {//!set //qDebug() << (**it)[COLUMN_ID_TABLE].toString(); } } if (!fieldsFound) { if (errMsg) *errMsg = msgCannotSwitch_EmptyDesign(); return false; } //set always, because if whereExpr==NULL, //this will clear prev. expr QString errorMessage; QString errorDescription; if (!temp->query()->setWhereExpression(whereExpr, &errorMessage, &errorDescription)) { qWarning() << "Invalid expression cannot be set as WHERE:" << whereExpr; qWarning() << "message=" << errorMessage << "description=" << errorDescription; return false; } qDebug() << "WHERE set to" << whereExpr; //add relations (looking for connections) foreach(KexiRelationsConnection* conn, *d->relations->relationsConnections()) { KexiRelationsTableContainer *masterTable = conn->masterTable(); KexiRelationsTableContainer *detailsTable = conn->detailsTable(); /*! @todo what about query? */ temp->query()->addRelationship( masterTable->schema()->table()->field(conn->masterField()), detailsTable->schema()->table()->field(conn->detailsField())); } // Add sorting information (ORDER BY) - we can do that only now // after all KDbQueryColumnInfo items are instantiated KDbOrderByColumnList orderByColumns; it = d->data->constBegin(); int fieldNumber = -1; //field number (empty rows are omitted) for (int i = 0/*row number*/; i < count && it != d->data->constEnd(); ++it, i++) { KPropertySet *set = d->sets->at(i); if (!set) continue; fieldNumber++; KDbField *currentField = 0; KDbQueryColumnInfo *currentColumn = 0; const QString sortingString((*set)["sorting"].value().toString()); if (sortingString != "ascending" && sortingString != "descending") { continue; } const KDbOrderByColumn::SortOrder sortOrder = sortingString == QLatin1String("ascending") ? KDbOrderByColumn::SortOrder::Ascending : KDbOrderByColumn::SortOrder::Descending; if (!(*set)["visible"].value().toBool()) { // this row defines invisible field but contains sorting information, // what means KDbField should be used as a reference for this sorting // Note1: alias is not supported here. // Try to find a field (not mentioned after SELECT): currentField = temp->query()->findTableField((*set)["field"].value().toString()); if (!currentField) { qWarning() << "NO FIELD" << (*set)["field"].value().toString() << "available for sorting"; continue; } orderByColumns.appendField(currentField, sortOrder); continue; } currentField = temp->query()->field(fieldNumber); if (!currentField || currentField->isExpression() || currentField->isQueryAsterisk()) //! @todo support expressions here continue; //! @todo ok, but not for expressions QString aliasString((*set)["alias"].value().toString()); currentColumn = temp->query()->columnInfo( - (*set)["table"].value().toString() + "." - + (aliasString.isEmpty() ? currentField->name() : aliasString)); + conn, (*set)["table"].value().toString() + "." + + (aliasString.isEmpty() ? currentField->name() : aliasString)); if (currentField && currentColumn) { if (currentColumn->isVisible()) orderByColumns.appendColumn(currentColumn, sortOrder); else if (currentColumn->field()) orderByColumns.appendField(currentColumn->field(), sortOrder); } } temp->query()->setOrderByColumnList(orderByColumns); - qDebug() << *temp->query(); + qDebug() << KDbConnectionAndQuerySchema(conn, *temp->query()); temp->registerTableSchemaChanges(temp->query()); //! @todo ? return true; } tristate KexiQueryDesignerGuiEditor::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); qDebug() << mode; if (!d->dataTable->dataAwareObject()->acceptRecordEditing()) return cancelled; qDebug() << "queryChangedInView:" << tempData()->queryChangedInView(); if (mode == Kexi::DesignViewMode) { return true; } else if (mode == Kexi::DataViewMode) { if (!isDirty() && window()->neverSaved()) { KMessageBox::information(this, msgCannotSwitch_EmptyDesign()); return cancelled; } if (tempData()->queryChangedInView() != Kexi::NoViewMode || !tempData()->query()) { //remember current design in a temporary structure QString errMsg; //build schema; problems are not allowed if (!buildSchema(&errMsg)) { KMessageBox::sorry(this, errMsg); return cancelled; } } *dontStore = true; //! @todo return true; } else if (mode == Kexi::TextViewMode) { *dontStore = true; if (tempData()->queryChangedInView() != Kexi::NoViewMode || !tempData()->query()) { //remember current design in a temporary structure //build schema; ignore problems buildSchema(); } /* if (tempData()->query && tempData()->query->fieldCount()==0) { //no fields selected: let's add "*" (all-tables asterisk), // otherwise SQL statement will be invalid tempData()->query->addAsterisk( new KDbQueryAsterisk( tempData()->query ) ); }*/ //! @todo return true; } return false; } tristate KexiQueryDesignerGuiEditor::afterSwitchFrom(Kexi::ViewMode mode) { - if (!d->relations->setConnection(d->conn)) { - window()->setStatus(d->conn); + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); + if (!d->relations->setConnection(conn)) { + window()->setStatus(conn); return false; } if (mode == Kexi::NoViewMode || (mode == Kexi::DataViewMode && !tempData()->query())) { //this is not a SWITCH but a fresh opening in this view mode if (!window()->neverSaved()) { if (!loadLayout()) { //err msg - window()->setStatus(d->conn, + window()->setStatus(conn, xi18n("Query definition loading failed."), xi18n("Query design may be corrupted so it could not be opened even in text view.\n" "You can delete the query and create it again.")); return false; } // Invalid queries case: // KexiWindow::switchToViewMode() first opens DesignViewMode, // and then KexiQueryPart::loadSchemaObject() doesn't allocate KDbQuerySchema object // do we're carefully looking at window()->schemaObject() KDbQuerySchema * q = dynamic_cast(window()->schemaObject()); if (q) { KDbResultInfo result; showFieldsForQuery(q, result); if (!result.success) { window()->setStatus(&result, xi18n("Query definition loading failed.")); tempData()->proposeOpeningInTextViewModeBecauseOfProblems = true; return false; } } //! @todo load global query properties } } else if (mode == Kexi::TextViewMode || mode == Kexi::DataViewMode) { // Switch from text or data view. In the second case, the design could be changed as well // because there could be changes made in the text view before switching to the data view. if (tempData()->queryChangedInView() == Kexi::TextViewMode) { //SQL view changed the query design //-clear and regenerate GUI items initTableRows(); //! @todo if (tempData()->query()) { //there is a query schema to show showTablesForQuery(tempData()->query()); //-show fields KDbResultInfo result; showFieldsAndRelationsForQuery(tempData()->query(), result); if (!result.success) { window()->setStatus(&result, xi18n("Query definition loading failed.")); return false; } } else { d->relations->clear(); } } //! @todo load global query properties } if (mode == Kexi::DataViewMode) { //this is just a SWITCH from data view //set cursor if needed: if (d->dataTable->dataAwareObject()->currentRecord() < 0 || d->dataTable->dataAwareObject()->currentColumn() < 0) { d->dataTable->dataAwareObject()->ensureCellVisible(0, 0); d->dataTable->dataAwareObject()->setCursorPosition(0, 0); } } if (d->sets->size() > 0) { d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_COLUMN); d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_TABLE); } tempData()->setQueryChangedInView(false); setFocus(); //to allow shared actions proper update return true; } KDbObject* KexiQueryDesignerGuiEditor::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); Q_UNUSED(options); + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); if (!d->dataTable->dataAwareObject()->acceptRecordEditing()) { *cancel = true; return 0; } QString errMsg; KexiQueryPartTempData * temp = tempData(); if (!temp->query() || !(viewMode() == Kexi::DesignViewMode && temp->queryChangedInView() == Kexi::NoViewMode)) { //only rebuild schema if it has not been rebuilt previously if (!buildSchema(&errMsg)) { KMessageBox::sorry(this, errMsg); *cancel = true; return 0; } } (KDbObject&)*temp->query() = object; //copy main attributes - bool ok = d->conn->storeNewObjectData(temp->query()); + bool ok = conn->storeNewObjectData(temp->query()); if (ok) { ok = KexiMainWindowIface::global()->project()->removeUserDataBlock(temp->query()->id()); // for sanity } window()->setId(temp->query()->id()); if (ok) ok = storeLayout(); if (!ok) { temp->setQuery(0); return 0; } return temp->takeQuery(); //will be returned, so: don't keep it in temp } tristate KexiQueryDesignerGuiEditor::storeData(bool dontAsk) { if (!d->dataTable->dataAwareObject()->acceptRecordEditing()) return cancelled; const bool was_dirty = isDirty(); tristate res = KexiView::storeData(dontAsk); //this clears dirty flag if (true == res) res = buildSchema(); if (true == res) res = storeLayout(); if (true != res) { if (was_dirty) setDirty(true); } return res; } void KexiQueryDesignerGuiEditor::showTablesForQuery(KDbQuerySchema *query) { // instead of hiding all tables and showing some tables, // show only these new and hide these unncecessary; the same for connections) d->slotTableAdded_enabled = false; //speedup d->relations->removeAllConnections(); //connections will be recreated d->relations->hideAllTablesExcept(query->tables()); foreach(KDbTableSchema* table, *query->tables()) { d->relations->addTable(table); } d->slotTableAdded_enabled = true; updateColumnsData(); } void KexiQueryDesignerGuiEditor::addConnection( KDbField *masterField, KDbField *detailsField) { SourceConnection conn; conn.masterTable = masterField->table()->name(); //<<name(); conn.detailsTable = detailsField->table()->name(); conn.detailsField = detailsField->name(); d->relations->addConnection(conn); } void KexiQueryDesignerGuiEditor::showFieldsForQuery(KDbQuerySchema *query, KDbResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, true, false, result); } void KexiQueryDesignerGuiEditor::showRelationsForQuery(KDbQuerySchema *query, KDbResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, false, true, result); } void KexiQueryDesignerGuiEditor::showFieldsAndRelationsForQuery(KDbQuerySchema *query, KDbResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, true, true, result); } void KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal( KDbQuerySchema *query, bool showFields, bool showRelations, KDbResultInfo& result) { + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); result.clear(); const bool was_dirty = isDirty(); //1. Show explicitly declared relations: if (showRelations) { foreach(KDbRelationship *rel, *query->relationships()) { //! @todo: now only sigle-field relationships are implemented! KDbField *masterField = rel->masterIndex()->fields()->first(); KDbField *detailsField = rel->detailsIndex()->fields()->first(); addConnection(masterField, detailsField); } } //2. Collect information about criterias // --this must be top level chain of AND's // --this will also show joins as: [table1.]field1 = [table2.]field2 KDbUtils::CaseInsensitiveHash criterias; KDbExpression e = query->whereExpression(); KDbExpression eItem; while (e.isValid()) { //eat parentheses because the expression can be (....) AND (... AND ... ) while (e.isValid() && e.isUnary() && e.token() == '(') e = e.toUnary().arg(); if (e.isBinary() && e.token() == KDbToken::AND) { eItem = e.toBinary().left(); e = e.toBinary().right(); } else { eItem = e; e = KDbExpression(); } //eat parentheses while (eItem.isValid() && eItem.isUnary() && eItem.token() == '(') eItem = eItem.toUnary().arg(); if (!eItem.isValid()) continue; qDebug() << eItem; KDbBinaryExpression binary(eItem.toBinary()); if (binary.isValid() && eItem.expressionClass() == KDb::RelationalExpression) { KDbField *leftField = 0, *rightField = 0; if (eItem.token() == '=' && binary.left().isVariable() && binary.right().isVariable() && (leftField = query->findTableField(binary.left().toString(0).toString())) && (rightField = query->findTableField(binary.right().toString(0).toString()))) { //! @todo move this check to parser on KDbQuerySchema creation //! or to KDbQuerySchema creation (WHERE expression should be then simplified //! by removing joins //this is relationship defined as following JOIN: [table1.]field1 = [table2.]field2 if (showRelations) { //! @todo testing primary key here is too simplified; maybe look ar isForeignKey() or indices.. //! @todo what about multifield joins? if (leftField->isPrimaryKey()) addConnection(leftField /*master*/, rightField /*details*/); else addConnection(rightField /*master*/, leftField /*details*/); //! @todo addConnection() should have "bool oneToOne" arg, for 1-to-1 relations } } else if (binary.left().isVariable()) { //this is: variable , op , argument //store variable -> argument: criterias.insertMulti(binary.left().toVariable().name(), binary.right()); } else if (binary.right().isVariable()) { //this is: argument , op , variable //store variable -> argument: criterias.insertMulti(binary.right().toVariable().name(), binary.left()); } } } //while if (!showFields) return; //3. show fields (including * and table.*) int row_num = 0; QSet usedCriterias; // <-- used criterias will be saved here // so in step 4. we will be able to add // remaining invisible columns with criterias - qDebug() << *query; + qDebug() << KDbConnectionAndQuerySchema(conn, *query); foreach(KDbField* field, *query->fields()) { qDebug() << *field; } foreach(KDbField* field, *query->fields()) { //append a new row QString tableName, fieldName, columnAlias, criteriaString; KDbBinaryExpression criteriaExpr; KDbExpression criteriaArgument; if (field->isQueryAsterisk()) { if (field->table()) {//single-table asterisk tableName = field->table()->name(); fieldName = "*"; } else {//all-tables asterisk tableName = "*"; fieldName = ""; } } else { columnAlias = query->columnAlias(row_num); if (field->isExpression()) { //! @todo ok? perhaps do not allow to omit aliases? fieldName = field->expression().toString(0).toString(); } else { tableName = field->table()->name(); fieldName = field->name(); criteriaArgument = criterias.value(fieldName); if (!criteriaArgument.isValid()) {//try table.field criteriaArgument = criterias.value(tableName + "." + fieldName); } if (criteriaArgument.isValid()) {//criteria expression is just a parent of argument criteriaExpr = criteriaArgument.parent().toBinary(); usedCriterias.insert(criteriaArgument.toString(0).toString()); //save info. about used criteria } } } //create new row data KDbRecordData *newRecord = createNewRow(tableName, fieldName, true /* visible*/); if (criteriaExpr.isValid()) { //! @todo fix for !INFIX operators if (criteriaExpr.token() == '=') criteriaString = criteriaArgument.toString(0).toString(); else criteriaString = criteriaExpr.token().toString() + " " + criteriaArgument.toString(0).toString(); (*newRecord)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); //OK, row inserted: create a new set for it KPropertySet &set = *createPropertySet(row_num, tableName, fieldName, true/*new one*/); if (!columnAlias.isEmpty()) set["alias"].setValue(columnAlias, KProperty::DefaultValueOptions ^ KProperty::ValueOption::RememberOld); if (!criteriaString.isEmpty()) set["criteria"].setValue(criteriaString, KProperty::DefaultValueOptions ^ KProperty::ValueOption::RememberOld); if (field->isExpression()) { if (!d->changeSingleCellValue(newRecord, COLUMN_ID_COLUMN, QVariant(columnAlias + ": " + field->expression().toString(0).toString()), &result)) return; //problems with setting column expression } row_num++; } //4. show ORDER BY information d->data->clearRecordEditBuffer(); const KDbOrderByColumnList* orderByColumns = query->orderByColumnList(); - QHash columnsOrder( - query->columnsOrder(KDbQuerySchema::UnexpandedListWithoutAsterisks)); + const QHash columnsOrder( + query->columnsOrder(conn, KDbQuerySchema::ColumnsOrderMode::UnexpandedListWithoutAsterisks)); for (auto orderByColumnIt(orderByColumns->constBegin()); orderByColumnIt != orderByColumns->constEnd(); ++orderByColumnIt) { KDbOrderByColumn* orderByColumn = *orderByColumnIt; KDbQueryColumnInfo *column = orderByColumn->column(); KDbRecordData *data = 0; KPropertySet *rowPropertySet = 0; if (column) { //sorting for visible column if (column->isVisible()) { if (columnsOrder.contains(column)) { const int columnPosition = columnsOrder.value(column); data = d->data->at(columnPosition); rowPropertySet = d->sets->at(columnPosition); qDebug() << "\tSetting \"" << *orderByColumn << "\" sorting for record #" << columnPosition; } } } else if (orderByColumn->field()) { //this will be presented as invisible field: create new row KDbField* field = orderByColumn->field(); QString tableName(field->table() ? field->table()->name() : QString()); data = createNewRow(tableName, field->name(), false /* !visible*/); d->dataTable->dataAwareObject()->insertItem(data, row_num); rowPropertySet = createPropertySet(row_num, tableName, field->name(), true /*newOne*/); propertySetSwitched(); qDebug() << "\tSetting \"" << *orderByColumn << "\" sorting for invisible field" << field->name() << ", table " << tableName << " -row #" << row_num; row_num++; } //alter sorting for either existing or new row if (data && rowPropertySet) { // this will automatically update "sorting" property d->data->updateRecordEditBuffer(data, COLUMN_ID_SORTING, orderByColumn->sortOrder() == KDbOrderByColumn::SortOrder::Ascending ? 1 : 2); // in slotBeforeCellChanged() d->data->saveRecordChanges(data, true); (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh" if (!(*data)[COLUMN_ID_VISIBLE].toBool()) //update (*rowPropertySet)["visible"].setValue(QVariant(false), KProperty::DefaultValueOptions ^ KProperty::ValueOption::RememberOld); } } //5. Show fields for unused criterias (with "Visible" column set to false) foreach(const KDbExpression &criteriaArgument, criterias) { // <-- contains field or table.field if (usedCriterias.contains(criteriaArgument.toString(0).toString())) continue; //unused: append a new row KDbBinaryExpression criteriaExpr = criteriaArgument.parent().toBinary(); if (!criteriaExpr.isValid()) { qWarning() << "criteriaExpr is not a binary expr"; continue; } KDbVariableExpression columnNameArgument = criteriaExpr.left().toVariable(); //left or right if (!columnNameArgument.isValid()) { columnNameArgument = criteriaExpr.right().toVariable(); if (!columnNameArgument.isValid()) { qWarning() << "columnNameArgument is not a variable (table or table.field) expr"; continue; } } KDbField* field = 0; if (!columnNameArgument.name().contains('.') && query->tables()->count() == 1) { //extreme case: only field name provided for one-table query: field = query->tables()->first()->field(columnNameArgument.name()); } else { field = query->findTableField(columnNameArgument.name()); } if (!field) { qWarning() << "no columnInfo found in the query for name" << columnNameArgument.name(); continue; } QString tableName, fieldName, /*columnAlias,*/ criteriaString; //! @todo what about ALIAS? tableName = field->table()->name(); fieldName = field->name(); //create new row data KDbRecordData *newRecord = createNewRow(tableName, fieldName, false /* !visible*/); if (criteriaExpr.isValid()) { //! @todo fix for !INFIX operators if (criteriaExpr.token() == '=') criteriaString = criteriaArgument.toString(0).toString(); else criteriaString = criteriaExpr.token().toString() + " " + criteriaArgument.toString(0).toString(); (*newRecord)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); //OK, row inserted: create a new set for it KPropertySet &set = *createPropertySet(row_num++, tableName, fieldName, true/*new one*/); //! @todo if (!columnAlias.isEmpty()) //! @todo set["alias"].setValue(columnAlias, false); //// if (!criteriaString.isEmpty()) set["criteria"].setValue(criteriaString, KProperty::DefaultValueOptions ^ KProperty::ValueOption::RememberOld); set["visible"].setValue(QVariant(false), KProperty::DefaultValueOptions ^ KProperty::ValueOption::RememberOld); } //current property set has most probably changed propertySetSwitched(); if (!was_dirty) setDirty(false); //move to 1st column, 1st row d->dataTable->dataAwareObject()->ensureCellVisible(0, 0); // tempData()->registerTableSchemaChanges(query); } bool KexiQueryDesignerGuiEditor::loadLayout() { + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); QString xml; //! @todo errmsg if (!loadDataBlock(&xml, "query_layout") || xml.isEmpty()) { //in a case when query layout was not saved, build layout by hand // -- dynamic cast because of a need for handling invalid queries // (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()): KDbQuerySchema * q = dynamic_cast(window()->schemaObject()); if (q) { showTablesForQuery(q); KDbResultInfo result; showRelationsForQuery(q, result); if (!result.success) { window()->setStatus(&result, xi18n("Query definition loading failed.")); return false; } } return true; } QDomDocument doc; doc.setContent(xml); QDomElement doc_el = doc.documentElement(), el; if (doc_el.tagName() != "query_layout") { //! @todo errmsg return false; } const bool was_dirty = isDirty(); //add tables and relations to the relation view for (el = doc_el.firstChild().toElement(); !el.isNull(); el = el.nextSibling().toElement()) { if (el.tagName() == "table") { - KDbTableSchema *t = d->conn->tableSchema(el.attribute("name")); + KDbTableSchema *t = conn->tableSchema(el.attribute("name")); int x = el.attribute("x", "-1").toInt(); int y = el.attribute("y", "-1").toInt(); int width = el.attribute("width", "-1").toInt(); int height = el.attribute("height", "-1").toInt(); QRect rect; if (x != -1 || y != -1 || width != -1 || height != -1) rect = QRect(x, y, width, height); d->relations->addTable(t, rect); } else if (el.tagName() == "conn") { SourceConnection src_conn; src_conn.masterTable = el.attribute("mtable"); src_conn.masterField = el.attribute("mfield"); src_conn.detailsTable = el.attribute("dtable"); src_conn.detailsField = el.attribute("dfield"); d->relations->addConnection(src_conn); } } if (!was_dirty) setDirty(false); return true; } bool KexiQueryDesignerGuiEditor::storeLayout() { + KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiQueryPartTempData * temp = tempData(); // Save SQL without driver-escaped keywords. if (window()->schemaObject()) //set this instance as obsolete (only if it's stored) - d->conn->setQuerySchemaObsolete(window()->schemaObject()->name()); + conn->setQuerySchemaObsolete(window()->schemaObject()->name()); KDbSelectStatementOptions options; options.setAddVisibleLookupColumns(false); KDbNativeStatementBuilder builder; KDbEscapedString sql; if (!builder.generateSelectStatement(&sql, temp->query(), options)) { return false; } if (!storeDataBlock(sql.toString(), "sql")) { return false; } //serialize detailed XML query definition QString xml = "", tmp; foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { /*! @todo what about query? */ tmp = QString("schema()->name()) + "\" x=\"" + QString::number(cont->x()) + "\" y=\"" + QString::number(cont->y()) + "\" width=\"" + QString::number(cont->width()) + "\" height=\"" + QString::number(cont->height()) + "\"/>"; xml += tmp; } foreach(KexiRelationsConnection *conn, *d->relations->relationsConnections()) { tmp = QString("masterTable()->schema()->name()) + "\" mfield=\"" + conn->masterField() + "\" dtable=\"" + QString(conn->detailsTable()->schema()->name()) + "\" dfield=\"" + conn->detailsField() + "\"/>"; xml += tmp; } xml += ""; if (!storeDataBlock(xml, "query_layout")) { return false; } return true; } QSize KexiQueryDesignerGuiEditor::sizeHint() const { QSize s1 = d->relations->sizeHint(); QSize s2 = d->head->sizeHint(); return QSize(qMax(s1.width(), s2.width()), s1.height() + s2.height()); } KDbRecordData* KexiQueryDesignerGuiEditor::createNewRow(const QString& tableName, const QString& fieldName, bool visible) const { KDbRecordData *newRecord = d->data->createItem(); QString key; if (tableName == "*") key = "*"; else { if (!tableName.isEmpty()) key = (tableName + "."); key += fieldName; } (*newRecord)[COLUMN_ID_COLUMN] = key; (*newRecord)[COLUMN_ID_TABLE] = tableName; (*newRecord)[COLUMN_ID_VISIBLE] = QVariant(visible); #ifndef KEXI_NO_QUERY_TOTALS (*newRecord)[COLUMN_ID_TOTALS] = QVariant(0); #endif return newRecord; } void KexiQueryDesignerGuiEditor::slotDragOverTableRecord( KDbRecordData * /*data*/, int /*record*/, QDragMoveEvent* e) { if (e->mimeData()->hasFormat("kexi/field")) { e->setAccepted(true); } } void KexiQueryDesignerGuiEditor::slotDroppedAtRecord(KDbRecordData * /*data*/, int /*record*/, QDropEvent *ev, KDbRecordData*& newRecord) { QString sourcePartClass; QString srcTable; QStringList srcFields; if (!KexiFieldDrag::decode(ev, &sourcePartClass, &srcTable, &srcFields)) { return; } if (srcFields.count() != 1) { return; } //insert new row at specific place newRecord = createNewRow(srcTable, srcFields[0], true /* visible*/); d->droppedNewRecord = newRecord; d->droppedNewTable = srcTable; d->droppedNewField = srcFields[0]; //! @todo } void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode() { KDbRecordData *data = d->data->last(); if (data) (*data)[COLUMN_ID_VISIBLE] = QVariant(false); //the same init as in initTableRows() } void KexiQueryDesignerGuiEditor::slotRecordInserted(KDbRecordData* data, int record, bool /*repaint*/) { if (d->droppedNewRecord && d->droppedNewRecord == data) { createPropertySet(record, d->droppedNewTable, d->droppedNewField, true); propertySetSwitched(); d->droppedNewRecord = 0; } tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotTableAdded(KDbTableSchema* /*t*/) { if (!d->slotTableAdded_enabled) return; updateColumnsData(); setDirty(); tempData()->setQueryChangedInView(true); d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotTableHidden(KDbTableSchema* /*t*/) { updateColumnsData(); setDirty(); tempData()->setQueryChangedInView(true); } QByteArray KexiQueryDesignerGuiEditor::generateUniqueAlias() const { //! @todo add option for using non-i18n'd "expr" prefix? const QByteArray expStr( xi18nc("short for 'expression' word (only latin letters, please)", "expr").toLatin1()); //! @todo optimization: cache it? QSet aliases; const int setsSize = d->sets->size(); for (int r = 0; r < setsSize; r++) { //! @todo use iterator here KPropertySet *set = d->sets->at(r); if (set) { const QByteArray a((*set)["alias"].value().toByteArray().toLower()); if (!a.isEmpty()) aliases.insert(a); } } int aliasNr = 1; for (;;aliasNr++) { if (!aliases.contains(expStr + QByteArray::number(aliasNr))) break; } return expStr + QByteArray::number(aliasNr); } //! @todo this is primitive, temporary: reuse SQL parser KDbExpression KexiQueryDesignerGuiEditor::parseExpressionString(const QString& fullString, KDbToken *token, bool allowRelationalOperator) { Q_ASSERT(token); QString str = fullString.trimmed(); int len = 0; //KDbExpression expr; //1. get token *token = KDbToken(); //2-char-long tokens if (str.startsWith(QLatin1String(">="))) { *token = KDbToken::GREATER_OR_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("<="))) { *token = KDbToken::LESS_OR_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("<>"))) { *token = KDbToken::NOT_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("!="))) { *token = KDbToken::NOT_EQUAL2; len = 2; } else if (str.startsWith(QLatin1String("=="))) { *token = '='; len = 2; } else if (str.startsWith(QLatin1String("LIKE "), Qt::CaseInsensitive)) { *token = KDbToken::LIKE; len = 5; } else if (str.startsWith(QLatin1String("NOT "), Qt::CaseInsensitive)) { str = str.mid(4).trimmed(); if (str.startsWith(QLatin1String("LIKE "), Qt::CaseInsensitive)) { *token = KDbToken::NOT_LIKE; len = 5; } else { return KDbExpression(); } } else { if (str.startsWith(QLatin1Char('=')) //1-char-long tokens || str.startsWith(QLatin1Char('<')) || str.startsWith(QLatin1Char('>'))) { *token = str[0].toLatin1(); len = 1; } else { if (allowRelationalOperator) *token = '='; } } if (!allowRelationalOperator && token->isValid()) return KDbExpression(); //1. get expression after token if (len > 0) str = str.mid(len).trimmed(); if (str.isEmpty()) return KDbExpression(); KDbExpression valueExpr; QRegularExpressionMatch match; if (str.length() >= 2 && ( (str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) ) { valueExpr = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, str.mid(1, str.length() - 2)); } else if (str.startsWith(QLatin1Char('[')) && str.endsWith(QLatin1Char(']'))) { valueExpr = KDbQueryParameterExpression(str.mid(1, str.length() - 2)); } else if ((match = QRegularExpression("^(\\d{1,4})-(\\d{1,2})-(\\d{1,2})$").match(str)).hasMatch()) { valueExpr = KDbConstExpression(KDbToken::DATE_CONST, QDate::fromString( match.captured(1).rightJustified(4, '0') + "-" + match.captured(2).rightJustified(2, '0') + "-" + match.captured(3).rightJustified(2, '0'), Qt::ISODate)); } else if ((match = QRegularExpression("^(\\d{1,2}):(\\d{1,2})$").match(str)).hasMatch() || (match = QRegularExpression("^(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$").match(str)).hasMatch()) { QString res = match.captured(1).rightJustified(2, '0') + ":" + match.captured(2).rightJustified(2, '0') + ":" + match.captured(3).rightJustified(2, '0'); // qDebug() << res; valueExpr = KDbConstExpression(KDbToken::TIME_CONST, QTime::fromString(res, Qt::ISODate)); } else if ((match = QRegularExpression("^(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})$").match(str)).hasMatch() || (match = QRegularExpression("^(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$").match(str)).hasMatch()) { QString res = match.captured(1).rightJustified(4, '0') + "-" + match.captured(2).rightJustified(2, '0') + "-" + match.captured(3).rightJustified(2, '0') + "T" + match.captured(4).rightJustified(2, '0') + ":" + match.captured(5).rightJustified(2, '0') + ":" + match.captured(6).rightJustified(2, '0'); // qDebug() << res; valueExpr = KDbConstExpression(KDbToken::DATETIME_CONST, QDateTime::fromString(res, Qt::ISODate)); } else if ((str[0] >= '0' && str[0] <= '9') || str[0] == '-' || str[0] == '+') { //number QLocale locale; const QChar decimalSym = locale.decimalPoint(); bool ok; int pos = str.indexOf('.'); if (pos == -1) {//second chance: local decimal symbol pos = str.indexOf(decimalSym); } if (pos >= 0) {//real const number const int left = str.leftRef(pos).toInt(&ok); if (!ok) return KDbExpression(); const int right = str.midRef(pos + 1).toInt(&ok); if (!ok) return KDbExpression(); valueExpr = KDbConstExpression(KDbToken::REAL_CONST, QPoint(left, right)); //decoded to QPoint } else { //integer const const qint64 val = str.toLongLong(&ok); if (!ok) return KDbExpression(); valueExpr = KDbConstExpression(KDbToken::INTEGER_CONST, val); } } else if (str.toLower() == "null") { valueExpr = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); } else {//identfier if (!KDb::isIdentifier(str)) return KDbExpression(); valueExpr = KDbVariableExpression(str); // the default is 'fieldname' //find first matching field for name 'str': foreach(KexiRelationsTableContainer *cont, *d->relations->tables()) { /*! @todo what about query? */ if (cont->schema()->table() && cont->schema()->table()->field(str)) { valueExpr = KDbVariableExpression(cont->schema()->table()->name() + '.' + str); // the expression is now: tablename.fieldname //! @todo KEXI3 check this we're calling KDbQuerySchema::validate() instead of this: valueExpr.toVariable().field = cont->schema()->table()->field(str); break; } } } return valueExpr; } void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KDbRecordData *data, int colnum, QVariant* newValue, KDbResultInfo* result) { switch (colnum) { case COLUMN_ID_COLUMN: slotBeforeColumnCellChanged(data, *newValue, result); break; case COLUMN_ID_TABLE: slotBeforeTableCellChanged(data, *newValue, result); break; case COLUMN_ID_VISIBLE: slotBeforeVisibleCellChanged(data, *newValue, result); break; #ifndef KEXI_NO_QUERY_TOTALS case COLUMN_ID_TOTALS: slotBeforeTotalsCellChanged(data, newValue, result); break; #endif case COLUMN_ID_SORTING: slotBeforeSortingCellChanged(data, *newValue, result); break; case COLUMN_ID_CRITERIA: slotBeforeCriteriaCellChanged(data, *newValue, result); break; default: Q_ASSERT_X(false, "colnum", "unhandled value"); } } void KexiQueryDesignerGuiEditor::slotBeforeColumnCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { if (newValue.isNull()) { d->data->updateRecordEditBuffer(data, COLUMN_ID_TABLE, QVariant(), false/*!allowSignals*/); d->data->updateRecordEditBuffer(data, COLUMN_ID_VISIBLE, QVariant(false));//invisible d->data->updateRecordEditBuffer(data, COLUMN_ID_SORTING, QVariant()); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRecordEditBuffer(data, COLUMN_ID_TOTALS, QVariant());//remove totals #endif d->data->updateRecordEditBuffer(data, COLUMN_ID_CRITERIA, QVariant());//remove crit. d->sets->eraseCurrentPropertySet(); return; } //auto fill 'table' column QString fieldId(newValue.toString().trimmed()); //tmp, can look like "table.field" QString fieldName; //"field" part of "table.field" or expression string QString tableName; //empty for expressions QByteArray alias; const bool isExpression = !d->fieldColumnIdentifiers.contains(fieldId.toLower()); if (isExpression) { //this value is entered by hand and doesn't match //any value in the combo box -- we're assuming this is an expression //-table remains null //-find "alias" in something like "alias : expr" const int id = fieldId.indexOf(':'); if (id > 0) { alias = fieldId.left(id).trimmed().toLatin1(); if (!KDb::isIdentifier(alias)) { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_COLUMN; result->message = xi18nc("@info", "Entered column alias %1 is not a valid identifier.", QString::fromLatin1(alias)); result->description = xi18n("Identifiers should start with a letter or '_' character"); return; } } fieldName = fieldId.mid(id + 1).trimmed(); //check expr. KDbExpression e; KDbToken dummyToken; if ((e = parseExpressionString(fieldName, &dummyToken, false/*allowRelationalOperator*/)).isValid()) { fieldName = e.toString(0).toString(); //print it prettier //this is just checking: destroy expr. object } else { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_COLUMN; result->message = xi18nc("@info", "Invalid expression %1", fieldName); return; } } else {//not expr. //this value is properly selected from combo box list if (fieldId == "*") { tableName = "*"; } else { if (!KDb::splitToTableAndFieldParts( fieldId, &tableName, &fieldName, KDb::SetFieldNameIfNoTableName)) { qWarning() << "no 'field' or 'table.field'"; return; } } } KProperty::ValueOptions valueOptions = KProperty::DefaultValueOptions; KPropertySet *set = d->sets->findPropertySetForItem(*data); if (!set) { valueOptions ^= KProperty::ValueOption::RememberOld; // no old val. const int row = d->data->indexOf(data); if (row < 0) { result->success = false; return; } set = createPropertySet(row, tableName, fieldName, true); propertySetSwitched(); } d->data->updateRecordEditBuffer(data, COLUMN_ID_TABLE, QVariant(tableName), false/*!allowSignals*/); d->data->updateRecordEditBuffer(data, COLUMN_ID_VISIBLE, QVariant(true)); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRecordEditBuffer(data, COLUMN_ID_TOTALS, QVariant(0)); #endif if (!sortingAllowed(fieldName, tableName)) { // sorting is not available for "*" or "table.*" rows //! @todo what about expressions? d->data->updateRecordEditBuffer(data, COLUMN_ID_SORTING, QVariant()); } //update properties (*set)["field"].setValue(fieldName, valueOptions); if (isExpression) { //-no alias but it's needed: if (alias.isEmpty()) //-try oto get old alias alias = (*set)["alias"].value().toByteArray(); if (alias.isEmpty()) //-generate smallest unique alias alias = generateUniqueAlias(); } (*set)["isExpression"].setValue(QVariant(isExpression), valueOptions); if (!alias.isEmpty()) { (*set)["alias"].setValue(alias, valueOptions); //pretty printed "alias: expr" newValue = QString(QString(alias) + ": " + fieldName); } (*set)["caption"].setValue(QString(), valueOptions); (*set)["table"].setValue(tableName, valueOptions); updatePropertiesVisibility(*set); } void KexiQueryDesignerGuiEditor::slotBeforeTableCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { Q_UNUSED(result) if (newValue.isNull()) { if (!(*data)[COLUMN_ID_COLUMN].toString().isEmpty()) { d->data->updateRecordEditBuffer(data, COLUMN_ID_COLUMN, QVariant(), false/*!allowSignals*/); } d->data->updateRecordEditBuffer(data, COLUMN_ID_VISIBLE, QVariant(false));//invisible #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRecordEditBuffer(data, COLUMN_ID_TOTALS, QVariant());//remove totals #endif d->data->updateRecordEditBuffer(data, COLUMN_ID_CRITERIA, QVariant());//remove crit. d->sets->eraseCurrentPropertySet(); } //update property KPropertySet *set = d->sets->findPropertySetForItem(*data); if (set) { if ((*set)["isExpression"].value().toBool() == false) { (*set)["table"] = newValue; (*set)["caption"] = QVariant(QString()); } else { //do not set table for expr. columns newValue = QVariant(); } updatePropertiesVisibility(*set); } } void KexiQueryDesignerGuiEditor::slotBeforeVisibleCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { Q_UNUSED(result) KProperty::ValueOptions valueOptions = KProperty::DefaultValueOptions; if (!propertySet()) { valueOptions ^= KProperty::ValueOption::RememberOld; // no old val. createPropertySet(d->dataTable->dataAwareObject()->currentRecord(), (*data)[COLUMN_ID_TABLE].toString(), (*data)[COLUMN_ID_COLUMN].toString(), true); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRecordEditBuffer(data, COLUMN_ID_TOTALS, QVariant(0));//totals #endif propertySetSwitched(); } KPropertySet &set = *propertySet(); set["visible"].setValue(newValue, valueOptions); } void KexiQueryDesignerGuiEditor::slotBeforeTotalsCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { #ifdef KEXI_NO_QUERY_TOTALS Q_UNUSED(data) Q_UNUSED(newValue) Q_UNUSED(result) #else //! @todo unused yet setDirty(true); tempData()->setQueryChangedInView(true); #endif } void KexiQueryDesignerGuiEditor::slotBeforeSortingCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { KProperty::ValueOptions valueOptions = KProperty::DefaultValueOptions; KPropertySet *set = d->sets->findPropertySetForItem(*data); if (!set) { valueOptions ^= KProperty::ValueOption::RememberOld; // no old val. set = createPropertySet(d->dataTable->dataAwareObject()->currentRecord(), (*data)[COLUMN_ID_TABLE].toString(), (*data)[COLUMN_ID_COLUMN].toString(), true); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRecordEditBuffer(data, COLUMN_ID_TOTALS, QVariant(0));//totals #endif propertySetSwitched(); } QString table(set->property("table").value().toString()); QString field(set->property("field").value().toString()); if (newValue.toInt() == 0 || sortingAllowed(field, table)) { KProperty &property = set->property("sorting"); QString key(property.listData()->keysAsStringList()[ newValue.toInt()]); qDebug() << "new key=" << key; property.setValue(key, valueOptions); } else { //show msg: sorting is not available result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_SORTING; result->message = xi18n("Could not set sorting for multiple columns (%1)", table == "*" ? table : (table + ".*")); } } void KexiQueryDesignerGuiEditor::slotBeforeCriteriaCellChanged(KDbRecordData *data, QVariant& newValue, KDbResultInfo* result) { //! @todo this is primitive, temporary: reuse SQL parser //QString operatorStr, argStr; KDbExpression e; const QString str = newValue.toString().trimmed(); KDbToken token; QString field, table; KPropertySet *set = d->sets->findPropertySetForItem(*data); if (set) { field = (*set)["field"].value().toString(); table = (*set)["table"].value().toString(); } if (!str.isEmpty() && (!set || table == "*" || field.contains("*"))) { //asterisk found! criteria not allowed result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_CRITERIA; if (propertySet()) result->message = xi18nc("@info", "Could not set criteria for %1", table == "*" ? table : field); else result->message = xi18n("Could not set criteria for empty record"); } else if (str.isEmpty() || (e = parseExpressionString(str, &token, true/*allowRelationalOperator*/)).isValid()) { if (e.isValid()) { QString tokenStr; if (token != '=') { tokenStr = token.toString() + " "; } if (set) { (*set)["criteria"] = QString(tokenStr + e.toString(0).toString()); //print it prettier } //this is just checking: destroy expr. object } else if (set && str.isEmpty()) { (*set)["criteria"] = QVariant(); //clear it } setDirty(true); tempData()->setQueryChangedInView(true); } else { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_CRITERIA; result->message = xi18nc("@info", "Invalid criteria %1", newValue.toString()); } } void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationsTableContainer*) { setDirty(true); // this is not needed here because only position has changed: tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationsConnection*) { setDirty(true); tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotAppendFields( KDbTableOrQuerySchema& tableOrQuery, const QStringList& fieldNames) { //! @todo how about query columns and multiple fields? KDbTableSchema *table = tableOrQuery.table(); if (!table || fieldNames.isEmpty()) return; QString fieldName(fieldNames.first()); if (fieldName != "*" && !table->field(fieldName)) return; int row_num; //find last filled row in the GUI table for (row_num = d->sets->size() - 1; row_num >= 0 && !d->sets->at(row_num); row_num--) { } row_num++; //after //add row KDbRecordData *newRecord = createNewRow(table->name(), fieldName, true /* visible*/); d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); d->dataTable->dataAwareObject()->setCursorPosition(row_num, 0); //create buffer createPropertySet(row_num, table->name(), fieldName, true/*new one*/); propertySetSwitched(); d->dataTable->setFocus(); } KPropertySet *KexiQueryDesignerGuiEditor::propertySet() { return d->sets->currentPropertySet(); } void KexiQueryDesignerGuiEditor::updatePropertiesVisibility(KPropertySet& set) { const bool asterisk = isAsterisk( set["table"].value().toString(), set["field"].value().toString() ); #ifdef KEXI_SHOW_UNFINISHED set["caption"].setVisible(!asterisk); #endif set["alias"].setVisible(!asterisk); /*always invisible #ifdef KEXI_SHOW_UNFINISHED set["sorting"].setVisible( !asterisk ); #endif*/ propertySetReloaded(true); } KPropertySet* KexiQueryDesignerGuiEditor::createPropertySet(int row, const QString& tableName, const QString& fieldName, bool newOne) { //const bool asterisk = isAsterisk(tableName, fieldName); KPropertySet *set = new KPropertySet(d->sets); KProperty *prop; //meta-info for property editor set->addProperty(prop = new KProperty("this:classString", xi18nc("Query column", "Column"))); prop->setVisible(false); //! \todo add table_field icon (add buff->addProperty(prop = new KexiProperty("this:iconName", KexiIconName("table_field")) ); // prop->setVisible(false); set->addProperty(prop = new KProperty("this:visibleObjectNameProperty", "visibleName")); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("this:objectNameReadOnly", true)); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("visibleName", QVariant(tableName + '.' + fieldName))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("table", QVariant(tableName))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("field", QVariant(fieldName))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("caption", QVariant(QString()), xi18n("Caption"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("alias", QVariant(QString()), xi18n("Alias"))); set->addProperty(prop = new KProperty("visible", QVariant(true))); prop->setVisible(false); /*! @todo set->addProperty(prop = new KexiProperty("totals", QVariant(QString())) ); prop->setVisible(false);*/ //sorting KPropertyListData *listData = new KPropertyListData( {"nosorting", "ascending", "descending"}, QVariantList{xi18n("None"), xi18n("Ascending"), xi18n("Descending")}); set->addProperty(prop = new KProperty("sorting", listData, listData->keys().first(), xi18n("Sorting"))); prop->setVisible(false); set->addProperty(prop = new KProperty("criteria", QVariant(QString()))); prop->setVisible(false); set->addProperty(prop = new KProperty("isExpression", QVariant(false))); prop->setVisible(false); d->sets->set(row, set, newOne); updatePropertiesVisibility(*set); return set; } void KexiQueryDesignerGuiEditor::setFocus() { d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotPropertyChanged(KPropertySet& set, KProperty& property) { const QByteArray pname(property.name()); /*! @todo use KexiProperty::setValidator(QString) when implemented as described in TODO #60 */ if (pname == "alias" || pname == "name") { const QVariant& v = property.value(); if (!v.toString().trimmed().isEmpty() && !KDb::isIdentifier(v.toString())) { KMessageBox::sorry(this, KDb::identifierExpectedMessage(property.caption(), v.toString())); property.resetValue(); } if (pname == "alias") { if (set["isExpression"].value().toBool() == true) { //update value in column #1 d->dataTable->dataAwareObject()->acceptEditor(); d->data->updateRecordEditBuffer(d->dataTable->dataAwareObject()->selectedRecord(), 0, QVariant(set["alias"].value().toString() + ": " + set["field"].value().toString())); d->data->saveRecordChanges(d->dataTable->dataAwareObject()->selectedRecord(), true); } } } tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item* item) { d->relations->objectCreated(item->pluginId(), item->name()); } void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item) { d->relations->objectDeleted(item.pluginId(), item.name()); } void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const QString& oldName) { d->relations->objectRenamed(item.pluginId(), oldName, item.name()); } diff --git a/src/plugins/queries/kexiquerypart.cpp b/src/plugins/queries/kexiquerypart.cpp index e39b2f48c..b1caf6feb 100644 --- a/src/plugins/queries/kexiquerypart.cpp +++ b/src/plugins/queries/kexiquerypart.cpp @@ -1,266 +1,268 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004-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. */ #include "kexiquerypart.h" #include "kexiqueryview.h" #include "kexiquerydesignerguieditor.h" #include "kexiquerydesignersql.h" #include #include #include #include #include #include #include #include KEXI_PLUGIN_FACTORY(KexiQueryPart, "kexi_queryplugin.json") KexiQueryPart::KexiQueryPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "query"), xi18nc("tooltip", "Create new query"), xi18nc("what's this", "Creates new query."), l) { setInternalPropertyValue("textViewModeCaption", xi18n("SQL")); } KexiQueryPart::~KexiQueryPart() { } KexiWindowData* KexiQueryPart::createWindowData(KexiWindow* window) { KexiQueryPartTempData *data = new KexiQueryPartTempData( window, KexiMainWindowIface::global()->project()->dbConnection()); data->setName(xi18nc("@info Object \"objectname\"", "%1 %2", window->part()->info()->name(), window->partItem()->name())); return data; } KexiView* KexiQueryPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(item); Q_UNUSED(window); //qDebug(); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiQueryView(parent); view->setObjectName("dataview"); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiQueryDesignerGuiEditor(parent); view->setObjectName("guieditor"); //needed for updating tables combo box: KexiProject *prj = KexiMainWindowIface::global()->project(); connect(prj, SIGNAL(newItemStored(KexiPart::Item*)), view, SLOT(slotNewItemStored(KexiPart::Item*))); connect(prj, SIGNAL(itemRemoved(KexiPart::Item)), view, SLOT(slotItemRemoved(KexiPart::Item))); connect(prj, SIGNAL(itemRenamed(KexiPart::Item,QString)), view, SLOT(slotItemRenamed(KexiPart::Item,QString))); } else if (viewMode == Kexi::TextViewMode) { view = new KexiQueryDesignerSqlView(parent); view->setObjectName("sqldesigner"); } return view; } tristate KexiQueryPart::remove(KexiPart::Item *item) { if (!KexiMainWindowIface::global()->project() || !KexiMainWindowIface::global()->project()->dbConnection()) return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbQuerySchema *sch = conn->querySchema(item->identifier()); if (sch) return conn->dropQuery(sch); //last chance: just remove item return conn->removeObject(item->identifier()); } void KexiQueryPart::initPartActions() { } void KexiQueryPart::initInstanceActions() { } KDbObject* KexiQueryPart::loadSchemaObject( KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { KexiQueryPartTempData * temp = static_cast(window->data()); QString sql; if (!loadDataBlock(window, &sql, "sql")) { return 0; } KDbEscapedString sqlText(sql); KDbParser *parser = KexiMainWindowIface::global()->project()->sqlParser(); KDbQuerySchema *query = 0; if (parser->parse(sqlText)) { query = parser->query(); } //error? if (!query) { if (viewMode == Kexi::TextViewMode) { //for SQL view, no parsing is initially needed: //-just make a copy: return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow); } /* Set this to true on data loading loadSchemaObject() to indicate that TextView mode could be used instead of DataView or DesignView, because there are problems with opening object. */ temp->proposeOpeningInTextViewModeBecauseOfProblems = true; //! @todo return 0; } - qDebug() << *query; + qDebug() << KDbConnectionAndQuerySchema( + KexiMainWindowIface::global()->project()->dbConnection(), *query); (KDbObject&)*query = object; //copy main attributes temp->registerTableSchemaChanges(query); if (ownedByWindow) *ownedByWindow = false; - qDebug() << *query; + qDebug() << KDbConnectionAndQuerySchema( + KexiMainWindowIface::global()->project()->dbConnection(), *query); return query; } KDbQuerySchema *KexiQueryPart::currentQuery(KexiView* view) { if (!view) return 0; KexiQueryView *qvp = 0; if (!(qvp = qobject_cast(view))) { return 0; } return static_cast(qvp->window()->data())->query(); } KLocalizedString KexiQueryPart::i18nMessage(const QString& englishMessage, KexiWindow* window) const { if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of query %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Query %1 already exists.")); return Part::i18nMessage(englishMessage, window); } tristate KexiQueryPart::rename(KexiPart::Item *item, const QString& newName) { Q_ASSERT(item); Q_UNUSED(newName); if (!KexiMainWindowIface::global()->project()->dbConnection()) return false; KexiMainWindowIface::global()->project()->dbConnection() ->setQuerySchemaObsolete(item->name()); return true; } //---------------- KexiQueryPartTempData::KexiQueryPartTempData(KexiWindow* window, KDbConnection *conn) : KexiWindowData(window) , KDbTableSchemaChangeListener() , m_query(0) , m_queryChangedInView(Kexi::NoViewMode) { this->conn = conn; } KexiQueryPartTempData::~KexiQueryPartTempData() { KDbTableSchemaChangeListener::unregisterForChanges(conn, this); } void KexiQueryPartTempData::clearQuery() { if (!m_query) return; unregisterForTablesSchemaChanges(); m_query->clear(); } void KexiQueryPartTempData::unregisterForTablesSchemaChanges() { KDbTableSchemaChangeListener::unregisterForChanges(conn, this); } void KexiQueryPartTempData::registerTableSchemaChanges(KDbQuerySchema *q) { if (!q) return; foreach(const KDbTableSchema* table, *q->tables()) { KDbTableSchemaChangeListener::registerForChanges(conn, this, table); } } tristate KexiQueryPartTempData::closeListener() { KexiWindow* window = static_cast(parent()); return KexiMainWindowIface::global()->closeWindow(window); } KDbQuerySchema *KexiQueryPartTempData::takeQuery() { KDbQuerySchema *query = m_query; m_query = 0; return query; } void KexiQueryPartTempData::setQuery(KDbQuerySchema *query) { if (m_query && m_query == query) return; if (m_query /* query not owned by window */ && (static_cast(parent())->schemaObject() != static_cast(m_query))) { delete m_query; } m_query = query; } Kexi::ViewMode KexiQueryPartTempData::queryChangedInView() const { return m_queryChangedInView; } void KexiQueryPartTempData::setQueryChangedInView(bool set) { m_queryChangedInView = set ? qobject_cast(parent())->currentViewMode() : Kexi::NoViewMode; } #include "kexiquerypart.moc" diff --git a/src/plugins/queries/kexiqueryview.cpp b/src/plugins/queries/kexiqueryview.cpp index 84e7e5410..ef6a10fbf 100644 --- a/src/plugins/queries/kexiqueryview.cpp +++ b/src/plugins/queries/kexiqueryview.cpp @@ -1,168 +1,167 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004, 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 "kexiqueryview.h" #include "kexiquerydesignersql.h" #include "kexiquerydesignerguieditor.h" #include "kexiquerypart.h" #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiQueryView::Private { public: Private() : cursor(0), currentParams() {} ~Private() {} KDbCursor *cursor; QList currentParams; /*! Used in storeNewData(), storeData() to decide whether we should ask other view to save changes. Stores information about view mode. */ }; //--------------------------------------------------------------------------------- KexiQueryView::KexiQueryView(QWidget *parent) : KexiDataTableView(parent) , d(new Private()) { // setup main menu actions QList mainMenuActions; mainMenuActions << sharedAction("project_export_data_table"); setMainMenuActions(mainMenuActions); tableView()->setInsertingEnabled(false); //default } KexiQueryView::~KexiQueryView() { if (d->cursor) d->cursor->connection()->deleteCursor(d->cursor); delete d; } tristate KexiQueryView::executeQuery(KDbQuerySchema *query) { if (!query) return false; KexiUtils::WaitCursor wait; KDbCursor *oldCursor = d->cursor; - qDebug() << query->parameters(); - bool ok; KDbConnection * conn = KexiMainWindowIface::global()->project()->dbConnection(); + qDebug() << query->parameters(conn); + bool ok; { KexiUtils::WaitCursorRemover remover; - d->currentParams = KexiQueryParameters::getParameters(this, - *conn->driver(), query, &ok); + d->currentParams = KexiQueryParameters::getParameters(this, conn, query, &ok); } if (!ok) {//input cancelled return cancelled; } d->cursor = conn->executeQuery(query, d->currentParams); if (!d->cursor) { window()->setStatus( conn, xi18n("Query executing failed.")); //! @todo also provide server result and sql statement return false; } setData(d->cursor); //! @todo remove close() when dynamic cursors arrive if (!d->cursor->close()) { return false; } if (oldCursor) oldCursor->connection()->deleteCursor(oldCursor); //! @todo maybe allow writing and inserting for single-table relations? tableView()->setReadOnly(true); //! @todo maybe allow writing and inserting for single-table relations? //set data model itself read-only too tableView()->data()->setReadOnly(true); tableView()->setInsertingEnabled(false); return true; } tristate KexiQueryView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode) { KDbQuerySchema *querySchema = static_cast(window()->schemaObject()); const tristate result = executeQuery(querySchema); if (true != result) return result; } else if (mode == Kexi::DesignViewMode || mode == Kexi::TextViewMode) { KexiQueryPartTempData * temp = static_cast(window()->data()); const tristate result = executeQuery(temp->query()); if (true != result) return result; } return true; } KDbObject* KexiQueryView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { KexiView * view = window()->viewThatRecentlySetDirtyFlag(); KexiQueryDesignerGuiEditor *guiView = dynamic_cast(view); if (guiView) { return guiView->storeNewData(object, options, cancel); } KexiQueryDesignerSqlView *sqlView = dynamic_cast(view); if (sqlView) { return sqlView->storeNewData(object, options, cancel); } return 0; } tristate KexiQueryView::storeData(bool dontAsk) { KexiView * view = window()->viewThatRecentlySetDirtyFlag(); KexiQueryDesignerGuiEditor *guiView = dynamic_cast(view); if (guiView) { return guiView->storeData(dontAsk); } KexiQueryDesignerSqlView *sqlView = dynamic_cast(view); if (sqlView) { return sqlView->storeData(dontAsk); } return false; } QList KexiQueryView::currentParameters() const { return d->currentParams; } diff --git a/src/plugins/reports/KexiDBReportDataSource.cpp b/src/plugins/reports/KexiDBReportDataSource.cpp index 9d063f3e6..cc76cbfc4 100644 --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,333 +1,334 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "KexiDBReportDataSource.h" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiDBReportDataSource::Private { public: explicit Private(KDbConnection *pDb) : cursor(0), connection(pDb), originalSchema(0), copySchema(0) { } ~Private() { delete copySchema; delete originalSchema; } QString objectName; KDbCursor *cursor; KDbConnection *connection; KDbQuerySchema *originalSchema; KDbQuerySchema *copySchema; }; KexiDBReportDataSource::KexiDBReportDataSource (const QString &objectName, KDbConnection * pDb) : d(new Private(pDb)) { d->objectName = objectName; getSchema(); } KexiDBReportDataSource::KexiDBReportDataSource(const QString& objectName, const QString& pluginId, KDbConnection* pDb) : d(new Private(pDb)) { d->objectName = objectName; getSchema(pluginId); } void KexiDBReportDataSource::setSorting(const QList& sorting) { if (d->copySchema) { if (sorting.isEmpty()) return; KDbOrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { - if (!order.appendField(d->copySchema, sorting[i].field(), + if (!order.appendField(d->connection, d->copySchema, sorting[i].field(), KDbOrderByColumn::fromQt(sorting[i].order()))) { qWarning() << "Cannot set sort field" << i << sorting[i].field(); return; } } d->copySchema->setOrderByColumnList(order); } else { qWarning() << "Unable to sort null schema"; } } void KexiDBReportDataSource::addCondition(const QString &field, const QVariant &value, const QString& relation) { if (d->copySchema) { KDbField *fld = d->copySchema->findTableField(field); if (fld) { if (relation.length() == 1) { QString errorMessage; QString errorDescription; if (!d->copySchema->addToWhereExpression(fld, value, KDbToken(relation.toLatin1()[0]), &errorMessage, &errorDescription)) { qWarning() << "Invalid expression cannot be added to WHERE:" << fld << relation << value; qWarning() << "addToWhereExpression() failed, message=" << errorMessage << "description=" << errorDescription; } } else { qWarning() << "Invalid relation passed in:" << relation; } } } else { qDebug() << "Unable to add expresstion to null schema"; } } KexiDBReportDataSource::~KexiDBReportDataSource() { close(); delete d; } bool KexiDBReportDataSource::open() { if ( d->connection && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { return false; } else if ( d->copySchema) { - qDebug() << "Opening cursor.." << *d->copySchema; + qDebug() << "Opening cursor.." + << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); d->cursor = d->connection->executeQuery(d->copySchema, KDbCursor::Option::Buffered); } if ( d->cursor ) { qDebug() << "Moving to first record.."; return d->cursor->moveFirst(); } else return false; } return false; } bool KexiDBReportDataSource::close() { if (d->cursor) { const bool ok = d->cursor->close(); d->connection->deleteCursor(d->cursor); d->cursor = nullptr; return ok; } return true; } bool KexiDBReportDataSource::getSchema(const QString& pluginId) { if (d->connection) { delete d->originalSchema; d->originalSchema = 0; delete d->copySchema; d->copySchema = 0; if ((pluginId.isEmpty() || pluginId == "org.kexi-project.table") && d->connection->tableSchema(d->objectName)) { qDebug() << d->objectName << "is a table.."; d->originalSchema = new KDbQuerySchema(d->connection->tableSchema(d->objectName)); } else if ((pluginId.isEmpty() || pluginId == "org.kexi-project.query") && d->connection->querySchema(d->objectName)) { qDebug() << d->objectName << "is a query.."; - qDebug() << *d->connection->querySchema(d->objectName); - d->originalSchema = new KDbQuerySchema(*(d->connection->querySchema(d->objectName))); + qDebug() << KDbConnectionAndQuerySchema(d->connection, + *d->connection->querySchema(d->objectName)); + d->originalSchema + = new KDbQuerySchema(*(d->connection->querySchema(d->objectName)), d->connection); } if (d->originalSchema) { const KDbNativeStatementBuilder builder(d->connection); KDbEscapedString sql; if (builder.generateSelectStatement(&sql, d->originalSchema)) { qDebug() << "Original:" << sql; } else { qDebug() << "Original: ERROR"; } - qDebug() << *d->originalSchema; + qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->originalSchema); - d->copySchema = new KDbQuerySchema(*d->originalSchema); - qDebug() << *d->copySchema; + d->copySchema = new KDbQuerySchema(*d->originalSchema, d->connection); + qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); if (builder.generateSelectStatement(&sql, d->copySchema)) { qDebug() << "Copy:" << sql; } else { qDebug() << "Copy: ERROR"; } } return true; } return false; } QString KexiDBReportDataSource::sourceName() const { return d->objectName; } int KexiDBReportDataSource::fieldNumber ( const QString &fld ) const { - if (!d->cursor || !d->cursor->query()) { return -1; } - const KDbQueryColumnInfo::Vector fieldsExpanded( - d->cursor->query()->fieldsExpanded(KDbQuerySchema::Unique)); - for (int i = 0; i < fieldsExpanded.size() ; ++i) { + const KDbQueryColumnInfo::Vector fieldsExpanded(d->cursor->query()->fieldsExpanded( + d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); + for (int i = 0; i < fieldsExpanded.size(); ++i) { if (0 == QString::compare(fld, fieldsExpanded[i]->aliasOrName(), Qt::CaseInsensitive)) { return i; } } return -1; } QStringList KexiDBReportDataSource::fieldNames() const { if (!d->originalSchema) { return QStringList(); } QStringList names; - const KDbQueryColumnInfo::Vector fieldsExpanded( - d->originalSchema->fieldsExpanded(KDbQuerySchema::Unique)); + const KDbQueryColumnInfo::Vector fieldsExpanded(d->originalSchema->fieldsExpanded( + d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); i++) { -//! @todo in some Kexi mode captionOrAliasOrName() would be used here (more user-friendly) + //! @todo in some Kexi mode captionOrAliasOrName() would be used here (more user-friendly) names.append(fieldsExpanded[i]->aliasOrName()); } return names; } QVariant KexiDBReportDataSource::value (int i) const { if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } QVariant KexiDBReportDataSource::value ( const QString &fld ) const { int i = fieldNumber ( fld ); if (d->cursor && i >= 0) return d->cursor->value ( i ); return QVariant(); } bool KexiDBReportDataSource::moveNext() { if ( d->cursor ) return d->cursor->moveNext(); return false; } bool KexiDBReportDataSource::movePrevious() { if ( d->cursor ) return d->cursor->movePrev(); return false; } bool KexiDBReportDataSource::moveFirst() { if ( d->cursor ) return d->cursor->moveFirst(); return false; } bool KexiDBReportDataSource::moveLast() { if ( d->cursor ) return d->cursor->moveLast(); return false; } qint64 KexiDBReportDataSource::at() const { if ( d->cursor ) return d->cursor->at(); return 0; } qint64 KexiDBReportDataSource::recordCount() const { - if ( d->copySchema ) - { - return KDb::recordCount ( d->copySchema ); + if (d->copySchema) { + return d->connection->recordCount(d->copySchema); } return 1; } QStringList KexiDBReportDataSource::dataSourceNames() const { //Get the list of queries in the database QStringList qs; if (d->connection && d->connection->isConnected()) { QList tids = d->connection->tableIds(); qs << ""; for (int i = 0; i < tids.size(); ++i) { KDbTableSchema* tsc = d->connection->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } QList qids = d->connection->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { KDbQuerySchema* qsc = d->connection->querySchema(qids[i]); if (qsc) qs << qsc->name(); } } return qs; } KReportDataSource* KexiDBReportDataSource::create(const QString& source) const { return new KexiDBReportDataSource(source, d->connection); } diff --git a/src/plugins/scripting/kexidb/kexidbcursor.cpp b/src/plugins/scripting/kexidb/kexidbcursor.cpp index b202953a5..6a3176710 100644 --- a/src/plugins/scripting/kexidb/kexidbcursor.cpp +++ b/src/plugins/scripting/kexidb/kexidbcursor.cpp @@ -1,158 +1,158 @@ /*************************************************************************** * kexidbcursor.cpp * This file is part of the KDE project * copyright (C)2004-2006 by Sebastian Sauer (mail@dipe.org) * * 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 "kexidbcursor.h" #include "kexidbconnection.h" #include #include #include using namespace Scripting; KexiDBCursor::KexiDBCursor(QObject* parent, KDbCursor* cursor, bool owner) : QObject(parent) , m_cursor(cursor) , m_owner(owner) { setObjectName("KexiDBCursor"); } KexiDBCursor::~KexiDBCursor() { clearBuffers(); if (m_owner) { m_cursor->close(); } } void KexiDBCursor::clearBuffers() { QMap::ConstIterator it(m_modifiedrecords.constBegin()), end(m_modifiedrecords.constEnd()); for (; it != end; ++it) delete it.value(); m_modifiedrecords.clear(); } bool KexiDBCursor::open() { return m_cursor->open(); } bool KexiDBCursor::isOpened() { return m_cursor->isOpened(); } bool KexiDBCursor::reopen() { return m_cursor->reopen(); } bool KexiDBCursor::close() { return m_cursor->close(); } bool KexiDBCursor::moveFirst() { return m_cursor->moveFirst(); } bool KexiDBCursor::moveLast() { return m_cursor->moveLast(); } bool KexiDBCursor::movePrev() { return m_cursor->movePrev(); } bool KexiDBCursor::moveNext() { return m_cursor->moveNext(); } bool KexiDBCursor::bof() { return m_cursor->bof(); } bool KexiDBCursor::eof() { return m_cursor->eof(); } int KexiDBCursor::at() { return m_cursor->at(); } int KexiDBCursor::fieldCount() { return m_cursor->fieldCount(); } QVariant KexiDBCursor::value(int index) { return m_cursor->value(index); } bool KexiDBCursor::setValue(int index, QVariant value) { KDbQuerySchema* query = m_cursor->query(); if (! query) { qWarning() << "Invalid query, index=" << index << " value=" << value; return false; } - KDbQueryColumnInfo* column = query->fieldsExpanded().at(index); + KDbQueryColumnInfo* column = query->fieldsExpanded(m_cursor->connection()).at(index); if (! column) { qWarning() << "Invalid column, index=" << index << " value=" << value; return false; } const qint64 position = m_cursor->at(); if (! m_modifiedrecords.contains(position)) m_modifiedrecords.insert(position, new Record(m_cursor)); m_modifiedrecords[position]->buffer->insert(column, value); return true; } bool KexiDBCursor::save() { if (m_modifiedrecords.count() < 1) return true; //It is needed to close the cursor before we are able to update the rows //since else the database could be locked (e.g. at the case of SQLite a //KDb: Object ERROR: 6: SQLITE_LOCKED would prevent updating). //Maybe it works fine with other drivers like MySQL or Postqre? m_cursor->close(); bool ok = true; QMap::ConstIterator it(m_modifiedrecords.constBegin()), end(m_modifiedrecords.constEnd()); for (; it != end; ++it) { bool b = m_cursor->updateRecord(&it.value()->rowdata, it.value()->buffer, m_cursor->isBuffered()); if (ok) { ok = b; //break; } } clearBuffers(); return ok; } diff --git a/src/widget/dataviewcommon/kexidataawareobjectiface.cpp b/src/widget/dataviewcommon/kexidataawareobjectiface.cpp index 5b126dae8..de0df8ee7 100644 --- a/src/widget/dataviewcommon/kexidataawareobjectiface.cpp +++ b/src/widget/dataviewcommon/kexidataawareobjectiface.cpp @@ -1,1988 +1,1988 @@ /* This file is part of the KDE project Copyright (C) 2005-2015 Jarosław Staniek Based on KexiTableView code. Copyright (C) 2002 Till Busch Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger 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 "kexidataawareobjectiface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KexiUtils; KexiDataAwareObjectInterface::KexiDataAwareObjectInterface() { m_data = 0; m_owner = false; m_readOnly = -1; //don't know m_insertingEnabled = -1; //don't know m_isSortingEnabled = true; m_isFilteringEnabled = true; m_deletionPolicy = AskDelete; m_inside_acceptEditor = false; m_inside_acceptRecordEdit = false; m_acceptsRecordEditAfterCellAccepting = false; m_internal_acceptsRecordEditingAfterCellAccepting = false; m_contentsMousePressEvent_dblClick = false; m_navPanel = 0; m_initDataContentsOnShow = false; m_cursorPositionSetExplicityBeforeShow = false; m_insertRecord = 0; m_spreadSheetMode = false; m_navPanelEnabled = true; m_dropsAtRecordEnabled = false; m_updateEntireRecordWhenMovingToOtherRecord = false; m_dragIndicatorLine = -1; m_emptyRecordInsertingEnabled = false; m_contextMenu = 0; m_contextMenuEnabled = true; m_recordWillBeDeleted = -1; m_alsoUpdateNextRecord = false; m_verticalScrollBarValueChanged_enabled = true; m_scrollbarToolTipsEnabled = true; m_recentSearchDirection = KexiSearchAndReplaceViewInterface::Options::DefaultSearchDirection; m_lengthExceededMessageVisible = false; m_acceptRecordEditing_in_setCursorPosition_enabled = true; clearVariables(); } KexiDataAwareObjectInterface::~KexiDataAwareObjectInterface() { delete m_insertRecord; } void KexiDataAwareObjectInterface::clearVariables() { m_editor = 0; m_recordEditing = -1; m_newRecordEditing = false; m_curRecord = -1; m_curColumn = -1; m_currentRecord = 0; } void KexiDataAwareObjectInterface::setData(KDbTableViewData *data, bool owner) { const bool theSameData = m_data && m_data == data; if (m_owner && m_data && m_data != data/*don't destroy if it's the same*/) { qDebug() << "destroying old data (owned)"; delete m_data; //destroy old data m_data = 0; m_itemIterator = KDbTableViewDataIterator(); } m_owner = owner; m_data = data; if (m_data) m_itemIterator = m_data->begin(); //qDebug() << "using shared data"; //add columns clearColumnsInternal(false); // set column widths if (m_data && horizontalHeader()) { int i = 0; horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); // set before using resizeSection() foreach(KDbTableViewColumn *col, *m_data->columns()) { if (col->isVisible()) { int w = col->width(); if (w == 0) { w = KEXI_DEFAULT_DATA_COLUMN_WIDTH; //default col width in pixels } //! @todo add col width configuration and storage horizontalHeader()->resizeSection(i, w); i++; } } } if (verticalHeader()) { //TODO verticalHeader()->update(); verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); if (m_data) { verticalHeader()->headerDataChanged(Qt::Vertical, 0, m_data->count() - 1); } } //!Change the following: if (m_data && m_data->count() == 0 && m_navPanel) m_navPanel->setCurrentRecordNumber(0 + 1); if (m_data && !theSameData) { //! @todo: store sorting settings? setSorting(-1); connectToReloadDataSlot(m_data, SIGNAL(reloadRequested())); QObject* thisObject = dynamic_cast(this); if (thisObject) { QObject::connect(m_data, SIGNAL(destroying()), thisObject, SLOT(slotDataDestroying())); QObject::connect(m_data, SIGNAL(recordsDeleted(QList)), thisObject, SLOT(slotRecordsDeleted(QList))); QObject::connect(m_data, SIGNAL(aboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool)), thisObject, SLOT(slotAboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool))); QObject::connect(m_data, SIGNAL(recordDeleted()), thisObject, SLOT(slotRecordDeleted())); QObject::connect(m_data, SIGNAL(recordInserted(KDbRecordData*,bool)), thisObject, SLOT(slotRecordInserted(KDbRecordData*,bool))); QObject::connect(m_data, SIGNAL(recordInserted(KDbRecordData*,int,bool)), thisObject, SLOT(slotRecordInserted(KDbRecordData*,int,bool))); //not db-aware QObject::connect(m_data, SIGNAL(recordRepaintRequested(KDbRecordData*)), thisObject, SLOT(slotRecordRepaintRequested(KDbRecordData*))); QObject::connect(verticalScrollBar(), SIGNAL(valueChanged(int)), thisObject, SLOT(verticalScrollBarValueChanged(int))); } } if (!m_data) { cancelRecordEditing(); clearVariables(); } else { if (!m_insertRecord) {//first setData() call - add 'insert' item m_insertRecord = m_data->createItem(); } else {//just reinit m_insertRecord->resize(m_data->columnCount()); } } //update gui mode if (m_navPanel) { m_navPanel->setInsertingEnabled(m_data && isInsertingEnabled()); m_navPanel->setInsertingButtonVisible(m_data && isInsertingEnabled()); } initDataContents(); updateIndicesForVisibleValues(); if (m_data) /*emit*/ dataSet(m_data); } void KexiDataAwareObjectInterface::initDataContents() { m_editor = 0; if (m_navPanel) m_navPanel->setRecordCount(recordCount()); if (m_data && !m_cursorPositionSetExplicityBeforeShow) { //set current row: m_currentRecord = 0; int curRow = -1, curCol = -1; if (m_data->columnCount() > 0) { if (recordCount() > 0) { m_itemIterator = m_data->begin(); m_currentRecord = *m_itemIterator; curRow = 0; curCol = 0; } else {//no data if (isInsertingEnabled()) { m_currentRecord = m_insertRecord; curRow = 0; curCol = 0; } } } setCursorPosition(curRow, curCol, ForceSetCursorPosition); } ensureCellVisible(m_curRecord, m_curColumn); //OK? updateWidgetContents(); m_cursorPositionSetExplicityBeforeShow = false; /*emit*/ dataRefreshed(); } void KexiDataAwareObjectInterface::setSortingEnabled(bool set) { if (m_isSortingEnabled && !set) setSorting(-1); m_isSortingEnabled = set; /*emit*/ reloadActions(); } void KexiDataAwareObjectInterface::setSorting(int column, KDbOrderByColumn::SortOrder order) { if (!m_data || !m_isSortingEnabled) return; setLocalSortOrder(column, order); m_data->setSorting(column, order); } int KexiDataAwareObjectInterface::dataSortColumn() const { if (m_data && m_isSortingEnabled) return m_data->sortColumn(); return -1; } KDbOrderByColumn::SortOrder KexiDataAwareObjectInterface::dataSortOrder() const { return m_data ? m_data->sortOrder() : KDbOrderByColumn::SortOrder::Ascending; } bool KexiDataAwareObjectInterface::sort() { if (!m_data || !m_isSortingEnabled) return false; if (recordCount() < 2) return true; if (!acceptRecordEditing()) return false; const int oldRow = m_curRecord; if (m_data->sortColumn() != -1) m_data->sort(); //locate current record if (!m_currentRecord) { m_itemIterator = m_data->begin(); m_currentRecord = *m_itemIterator; m_curRecord = 0; if (!m_currentRecord) return true; } if (m_currentRecord != m_insertRecord) { m_curRecord = m_data->indexOf(m_currentRecord); int jump = m_curRecord - oldRow; if (jump < 0) m_itemIterator -= -jump; else m_itemIterator += jump; } updateGUIAfterSorting(oldRow); editorShowFocus(m_curRecord, m_curColumn); if (m_navPanel) m_navPanel->setCurrentRecordNumber(m_curRecord + 1); return true; } void KexiDataAwareObjectInterface::sortAscending() { if (currentColumn() < 0) return; sortColumnInternal(currentColumn(), 1); } void KexiDataAwareObjectInterface::sortDescending() { if (currentColumn() < 0) return; sortColumnInternal(currentColumn(), -1); } void KexiDataAwareObjectInterface::sortColumnInternal(int col, int order) { //-select sorting bool asc; if (order == 0) {// invert if (col == dataSortColumn() && dataSortOrder() == KDbOrderByColumn::SortOrder::Ascending) { asc = false; // invert } else { asc = true; } } else { asc = (order == 1); } const KDbOrderByColumn::SortOrder prevSortOrder = currentLocalSortOrder(); const int prevSortColumn = currentLocalSortColumn(); setSorting(col, asc ? KDbOrderByColumn::SortOrder::Ascending : KDbOrderByColumn::SortOrder::Descending); //-perform sorting if (!sort()) { setLocalSortOrder(prevSortColumn, prevSortOrder); //this will also remove indicator } if (col != prevSortColumn) { /*emit*/ sortedColumnChanged(col); } } bool KexiDataAwareObjectInterface::isInsertingEnabled() const { if (isReadOnly()) return false; if (m_insertingEnabled == 1 || m_insertingEnabled == 0) return (bool)m_insertingEnabled; if (!hasData()) return true; return m_data->isInsertingEnabled(); } void KexiDataAwareObjectInterface::setFilteringEnabled(bool set) { m_isFilteringEnabled = set; } bool KexiDataAwareObjectInterface::isDeleteEnabled() const { return (m_deletionPolicy != NoDelete) && !isReadOnly(); } void KexiDataAwareObjectInterface::setDeletionPolicy(DeletionPolicy policy) { m_deletionPolicy = policy; } void KexiDataAwareObjectInterface::setReadOnly(bool set) { if (isReadOnly() == set || (m_data && m_data->isReadOnly() && !set)) return; //not allowed! m_readOnly = (set ? 1 : 0); if (set) setInsertingEnabled(false); updateWidgetContents(); /*emit*/ reloadActions(); } bool KexiDataAwareObjectInterface::isReadOnly() const { if (!hasData()) return true; if (m_readOnly == 1 || m_readOnly == 0) return (bool)m_readOnly; return m_data->isReadOnly(); } void KexiDataAwareObjectInterface::setInsertingEnabled(bool set) { if (isInsertingEnabled() == set || (m_data && !m_data->isInsertingEnabled() && set)) return; //not allowed! m_insertingEnabled = (set ? 1 : 0); if (m_navPanel) { m_navPanel->setInsertingEnabled(set); m_navPanel->setInsertingButtonVisible(set); } if (set) setReadOnly(false); updateWidgetContents(); /*emit*/ reloadActions(); } void KexiDataAwareObjectInterface::setSpreadSheetMode(bool set) { m_spreadSheetMode = set; setSortingEnabled(!set); setInsertingEnabled(!set); setAcceptsRecordEditAfterCellAccepting(set); setFilteringEnabled(!set); setEmptyRecordInsertingEnabled(set); m_navPanelEnabled = !set; } void KexiDataAwareObjectInterface::selectNextRecord() { selectRecord(qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), m_curRecord + 1)); } void KexiDataAwareObjectInterface::selectPreviousPage() { selectRecord( qMax(0, m_curRecord - recordsPerPage()) ); } void KexiDataAwareObjectInterface::selectNextPage() { selectRecord( qMin( recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), m_curRecord + recordsPerPage() ) ); } void KexiDataAwareObjectInterface::selectFirstRecord() { selectRecord(0); } void KexiDataAwareObjectInterface::selectLastRecord() { selectRecord(recordCount() > 0 ? (recordCount() - 1) : 0); } void KexiDataAwareObjectInterface::selectRecord(int record) { m_verticalScrollBarValueChanged_enabled = false; //disable tooltip setCursorPosition(record, -1); m_verticalScrollBarValueChanged_enabled = true; } void KexiDataAwareObjectInterface::selectPreviousRecord() { selectRecord(qMax(0, m_curRecord - 1)); } void KexiDataAwareObjectInterface::clearSelection() { int oldRow = m_curRecord; m_curRecord = -1; m_curColumn = -1; m_currentRecord = 0; updateRecord(oldRow); if (m_navPanel) m_navPanel->setCurrentRecordNumber(0); } // #define setCursorPosition_DEBUG void KexiDataAwareObjectInterface::setCursorPosition(int record, int col/*=-1*/, CursorPositionFlags flags) { int newRecord = record; int newCol = col; if (recordCount() <= 0) { if (isInsertingEnabled()) { m_currentRecord = m_insertRecord; newRecord = 0; if (col >= 0) newCol = col; else newCol = 0; } else { m_currentRecord = 0; m_curRecord = -1; m_curColumn = -1; return; } } if (col >= 0) { newCol = qMax(0, col); newCol = qMin(columnCount() - 1, newCol); } else { newCol = m_curColumn; //no changes newCol = qMax(0, newCol); //may not be < 0 ! } newRecord = qMax(0, record); newRecord = qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), newRecord); // qDebug() << "setCursorPosition(): d->curRow=" << d->curRow << " oldRow=" << oldRow << " d->curCol=" << d->curCol << " oldCol=" << oldCol; const bool forceSet = flags & ForceSetCursorPosition; if (forceSet || m_curRecord != newRecord || m_curColumn != newCol) { #ifdef setCursorPosition_DEBUG qDebug() << QString("old:%1,%2 new:%3,%4").arg(m_curColumn) .arg(m_curRecord).arg(newcol).arg(newrow); #endif // cursor moved: get rid of editor if (m_editor) { if (!m_contentsMousePressEvent_dblClick && m_acceptRecordEditing_in_setCursorPosition_enabled) { if (!acceptEditor()) { return; } //update row num. again newRecord = qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), newRecord); } } if (m_errorMessagePopup) { m_errorMessagePopup->animatedHide(); } if ((m_curRecord != newRecord || forceSet) && m_navPanel) {//update current row info m_navPanel->setCurrentRecordNumber(newRecord + 1); } // cursor moved to other row: end of row editing bool newRecordInserted = false; if (m_recordEditing >= 0 && m_curRecord != newRecord) { newRecordInserted = m_newRecordEditing; if (m_acceptRecordEditing_in_setCursorPosition_enabled && !acceptRecordEditing()) { //accepting failed: cancel setting the cursor return; } //update row number, because number of rows changed newRecord = qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), newRecord); if (m_navPanel) m_navPanel->setCurrentRecordNumber(newRecord + 1); //refresh } //change position int oldRow = m_curRecord; int oldCol = m_curColumn; m_curRecord = newRecord; m_curColumn = newCol; //show editor-dependent focus, if we're changing the current column if (oldCol >= 0 && oldCol < columnCount() && m_curColumn != oldCol) { //find the editor for this column KexiDataItemInterface *edit = editor(oldCol); if (edit) { edit->hideFocus(); } } // position changed, so subsequent searching should be started from scratch // (e.g. from the current cell or the top-left cell) m_positionOfRecentlyFoundValue.exists = false; //show editor-dependent focus, if needed editorShowFocus(m_curRecord, m_curColumn); if (m_updateEntireRecordWhenMovingToOtherRecord) updateRecord(oldRow); else updateCell(oldRow, oldCol); if (m_updateEntireRecordWhenMovingToOtherRecord) updateRecord(m_curRecord); else updateCell(m_curRecord, m_curColumn); if (m_curColumn != oldCol || m_curRecord != oldRow || forceSet) {//ensure this is also refreshed if (!m_updateEntireRecordWhenMovingToOtherRecord) //only if entire row has not been updated updateCell(oldRow, m_curColumn); } //update row if (forceSet || m_curRecord != oldRow) { if (isInsertingEnabled() && m_curRecord == recordCount()) { #ifdef setCursorPosition_DEBUG qDebug() << "NOW insert item is current"; #endif m_currentRecord = m_insertRecord; m_itemIterator = KDbTableViewDataIterator(); } else { #ifdef setCursorPosition_DEBUG qDebug() << QString("NOW item at %1 (%2) is current") .arg(m_curRecord).arg((ulong)itemAt(m_curRecord)); int _i = 0; qDebug() << "m_curRecord:" << m_curRecord; for (KDbTableViewDataIterator ii = m_data->begin(); ii != m_data->end(); ++ii) { qDebug() << _i << (ulong)(*ii) << (ii == m_itemIterator ? "CURRENT" : "") << *(*ii); _i++; } qDebug() << "~" << m_curRecord << (ulong)(*m_itemIterator) << *(*m_itemIterator); #endif if ( !newRecordInserted && isInsertingEnabled() && m_currentRecord == m_insertRecord && m_curRecord == (recordCount() - 1)) { //moving from the 'insert item' to the last item m_itemIterator = m_data->begin(); m_itemIterator += (m_data->count() - 1); } else if (!newRecordInserted && !forceSet && m_currentRecord != m_insertRecord && 0 == m_curRecord) { m_itemIterator = m_data->begin(); } else if ( !newRecordInserted && !forceSet && m_currentRecord != m_insertRecord && oldRow >= 0 && (oldRow + 1) == m_curRecord) { ++m_itemIterator; // just move next } else if ( !newRecordInserted && !forceSet && m_currentRecord != m_insertRecord && oldRow >= 0 && (oldRow - 1) == m_curRecord) { --m_itemIterator; // just move back } else { //move at: m_itemIterator = m_data->begin(); m_itemIterator += m_curRecord; } if (!*m_itemIterator) { //sanity m_itemIterator = m_data->begin(); m_itemIterator += m_curRecord; } m_currentRecord = *m_itemIterator; #ifdef setCursorPosition_DEBUG qDebug() << "new~" << m_curRecord << (ulong)(*m_itemIterator) << (*m_itemIterator)->debugString(); #endif } } //quite clever: ensure the cell is visible: ensureCellVisible(m_curRecord, m_curColumn); /*emit*/ itemSelected(m_currentRecord); /*emit*/ cellSelected(m_curRecord, m_curColumn); selectCellInternal(oldRow, oldCol); } else { if (!(flags & DontEnsureCursorVisibleIfPositionUnchanged) && m_curRecord >= 0 && m_curRecord < recordCount() && m_curColumn >= 0 && m_curColumn < columnCount()) { // the same cell but may need a bit of scrolling to make it visible ensureCellVisible(m_curRecord, m_curColumn); } //qDebug() << "NO CHANGE"; } if (m_initDataContentsOnShow) { m_cursorPositionSetExplicityBeforeShow = true; } } void KexiDataAwareObjectInterface::selectCellInternal(int previousRecord, int previousColumn) { Q_UNUSED(previousRecord); Q_UNUSED(previousColumn); } bool KexiDataAwareObjectInterface::acceptRecordEditing() { if (!m_data || m_recordEditing == -1 || /*sanity*/ !m_data->recordEditBuffer() || m_inside_acceptRecordEdit) { return true; } if (m_inside_acceptEditor) { m_internal_acceptsRecordEditingAfterCellAccepting = true; return true; } QScopedValueRollback acceptRecordEditingSetter(m_inside_acceptRecordEdit, true); // avoid recursion m_internal_acceptsRecordEditingAfterCellAccepting = false; const int columnEditedBeforeAccepting = m_editor ? currentColumn() : -1; if (!acceptEditor()) { return false; } //qDebug() << "EDIT RECORD ACCEPTING..."; bool success = true; const bool inserting = m_newRecordEditing; if (m_data->recordEditBuffer()->isEmpty() && !m_newRecordEditing) { //qDebug() << "-- NOTHING TO ACCEPT!!!"; } else {//not empty edit buffer or new row to insert: if (m_newRecordEditing) { qDebug() << "-- INSERTING:" << *m_data->recordEditBuffer(); success = m_data->saveNewRecord(m_currentRecord); } else { if (success) { //accept changes for this row: qDebug() << "-- UPDATING:" << *m_data->recordEditBuffer(); qDebug() << "-- BEFORE:" << *m_currentRecord; success = m_data->saveRecordChanges(m_currentRecord); qDebug() << "-- AFTER:" << *m_currentRecord; } } } if (success) { //editing is finished: if (m_newRecordEditing) { //update current-item-iterator setCursorPosition(m_curRecord, -1, ForceSetCursorPosition); } m_recordEditing = -1; m_newRecordEditing = false; updateAfterAcceptRecordEditing(); qDebug() << "EDIT RECORD ACCEPTED:"; if (inserting) { //update navigator's data if (m_navPanel) m_navPanel->setRecordCount(recordCount()); } /*emit*/ recordEditingTerminated(m_curRecord); } else { int faultyColumn = -1; if (m_data->result().column >= 0 && m_data->result().column < columnCount()) faultyColumn = m_data->result().column; else if (columnEditedBeforeAccepting >= 0) faultyColumn = columnEditedBeforeAccepting; if (faultyColumn >= 0) { setCursorPosition(m_curRecord, faultyColumn); } const int button = showErrorMessageForResult(m_data->result()); if (KMessageBox::No == button) { //discard changes cancelRecordEditing(); } else { if (faultyColumn >= 0) { //edit this cell startEditCurrentCell(); } } } //indicate on the vheader that we are not editing if (verticalHeader()) { //qDebug() << currentRecord(); updateVerticalHeaderSection(currentRecord()); } return success; } bool KexiDataAwareObjectInterface::cancelRecordEditing() { if (!hasData()) return true; if (m_recordEditing == -1) return true; cancelEditor(); m_recordEditing = -1; m_alsoUpdateNextRecord = m_newRecordEditing; if (m_newRecordEditing) { m_newRecordEditing = false; beginRemoveItem(m_currentRecord, m_curRecord); //remove current edited row (it is @ the end of list) m_data->removeLast(); endRemoveItem(m_curRecord); //current item is now empty, last row m_currentRecord = m_insertRecord; //update visibility updateWidgetContents(); updateWidgetContentsSize(); //--no cancel action is needed for datasource, // because the row was not yet stored. } m_data->clearRecordEditBuffer(); updateAfterCancelRecordEditing(); //indicate on the vheader that we are not editing if (verticalHeader()) { //qDebug() << currentRecord(); updateVerticalHeaderSection(currentRecord()); } //! \todo (js): cancel changes for this row! qDebug() << "EDIT RECORD CANCELLED."; /*emit*/ recordEditingTerminated(m_curRecord); return true; } void KexiDataAwareObjectInterface::updateAfterCancelRecordEditing() { updateRecord(m_curRecord); if (m_alsoUpdateNextRecord) updateRecord(m_curRecord + 1); m_alsoUpdateNextRecord = false; } void KexiDataAwareObjectInterface::updateAfterAcceptRecordEditing() { updateRecord(m_curRecord); } void KexiDataAwareObjectInterface::removeEditor() { if (!m_editor) return; m_editor->hideWidget(); m_editor = 0; } bool KexiDataAwareObjectInterface::cancelEditor() { if (m_errorMessagePopup) { m_errorMessagePopup->animatedHide(); } if (!m_editor) return true; removeEditor(); return true; } bool KexiDataAwareObjectInterface::acceptEditor() { if (!hasData()) return true; if (!m_editor || m_inside_acceptEditor) return true; QScopedValueRollback acceptRecordEditingSetter(m_inside_acceptEditor, true); // avoid recursion QVariant newval; KDbValidator::Result res = KDbValidator::Ok; QString msg, desc; bool setNull = false; //autoincremented field can be omitted (left as null or empty) if we're inserting a new row const bool autoIncColumnCanBeOmitted = m_newRecordEditing && m_editor->field()->isAutoIncrement(); bool valueChanged = m_editor->valueChanged(); if (valueChanged) { if (!m_editor->valueIsValid()) { //used e.g. for date or time values - the value can be null but not necessary invalid res = KDbValidator::Error; //! @todo allow displaying user-defined warning showEditorContextMessage( m_editor, xi18nc("Question", "Error: %1?", m_editor->columnInfo()->field()->typeName()), KMessageWidget::Error, KMessageWidget::Up); } else if (m_editor->valueIsNull()) {//null value entered if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { qDebug() << "NULL NOT ALLOWED!"; res = KDbValidator::Error; msg = KDbValidator::messageColumnNotEmpty().arg(m_editor->field()->captionOrName()) + "\n\n" + KDbTableViewData::messageYouCanImproveData(); desc = xi18n("The column's constraint is declared as NOT NULL (required)."); } else { qDebug() << "NULL VALUE WILL BE SET"; //ok, just leave newval as NULL setNull = true; } } else if (m_editor->valueIsEmpty()) {//empty value entered if (m_editor->field()->hasEmptyProperty()) { if (m_editor->field()->isNotEmpty() && !autoIncColumnCanBeOmitted) { qDebug() << "EMPTY NOT ALLOWED!"; res = KDbValidator::Error; msg = KDbValidator::messageColumnNotEmpty().arg(m_editor->field()->captionOrName()) + "\n\n" + KDbTableViewData::messageYouCanImproveData(); desc = xi18n("The column's constraint is declared as NOT EMPTY (text should be filled)."); } else { qDebug() << "EMPTY VALUE WILL BE SET"; } } else { if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { qDebug() << "NEITHER NULL NOR EMPTY VALUE CAN BE SET!"; res = KDbValidator::Error; msg = KDbValidator::messageColumnNotEmpty().arg(m_editor->field()->captionOrName()) + "\n\n" + KDbTableViewData::messageYouCanImproveData(); desc = xi18n("The column's constraint is declared as NOT EMPTY and NOT NULL."); } else { qDebug() << "NULL VALUE WILL BE SET BECAUSE EMPTY IS NOT ALLOWED"; //ok, just leave newval as NULL setNull = true; } } } else { // try to fixup the value before accepting, e.g. trim the text if (!m_editor->fixup()) { res = KDbValidator::Error; } if (m_errorMessagePopup) { m_errorMessagePopup->animatedHide(); } if (res != KDbValidator::Ok) { //! @todo display message related to failed fixup if needed } //! @todo after fixup we may want to apply validation rules again } }//changed const int realFieldNumber = fieldNumberForColumn(m_curColumn); if (realFieldNumber < 0) { qWarning() << "fieldNumberForColumn(m_curColumn) < 0"; return false; } KDbTableViewColumn *currentTVColumn = column(m_curColumn); //try to get the value entered: if (res == KDbValidator::Ok) { if ( (!setNull && !valueChanged) || (m_editor->field()->type() != KDbField::Boolean && setNull && m_currentRecord->at(realFieldNumber).isNull())) { qDebug() << "VALUE NOT CHANGED."; removeEditor(); if (m_acceptsRecordEditAfterCellAccepting || m_internal_acceptsRecordEditingAfterCellAccepting) acceptRecordEditing(); return true; } if (!setNull) {//get the new value newval = m_editor->value(); //! @todo validation rules for this value? } //Check other validation rules: //1. check using validator KDbValidator *validator = currentTVColumn->validator(); if (validator) { res = validator->check(currentTVColumn->field()->captionOrName(), newval, &msg, &desc); } } //show the validation result if not OK: if (res == KDbValidator::Error) { if (!msg.isEmpty()) { if (desc.isEmpty()) KMessageBox::sorry(dynamic_cast(this), msg); else KMessageBox::detailedSorry(dynamic_cast(this), msg, desc); } } else if (res == KDbValidator::Warning) { //! @todo: message KMessageBox::messageBox(dynamic_cast(this), KMessageBox::Sorry, msg + "\n" + desc); } if (res == KDbValidator::Ok) { //2. check using signal //send changes to the backend QVariant visibleValue; if ( !newval.isNull()/* visible value should be null if value is null */ && currentTVColumn->visibleLookupColumnInfo()) { visibleValue = m_editor->visibleValue(); //visible value for lookup field } //should be also added to the buffer if (m_data->updateRecordEditBufferRef(m_currentRecord, m_curColumn, currentTVColumn, &newval, /*allowSignals*/true, currentTVColumn->visibleLookupColumnInfo() ? &visibleValue : 0)) { qDebug() << "------ EDIT BUFFER CHANGED TO:" << *m_data->recordEditBuffer(); } else { qDebug() << "------ CHANGE FAILED"; res = KDbValidator::Error; //now: there might be called cancelEditor() in updateRecordEditBuffer() handler, //if this is true, d->pEditor is NULL. if (m_editor && m_data->result().column >= 0 && m_data->result().column < columnCount()) { //move to faulty column (if m_editor is not cleared) setCursorPosition(m_curRecord, m_data->result().column); } if (!m_data->result().message.isEmpty()) { const int button = showErrorMessageForResult(m_data->result()); if (KMessageBox::No == button) { //discard changes cancelEditor(); if (m_acceptsRecordEditAfterCellAccepting) cancelRecordEditing(); return false; } } } } if (res == KDbValidator::Ok) { removeEditor(); /*emit*/ itemChanged(m_currentRecord, m_curRecord, m_curColumn, m_currentRecord->at(realFieldNumber)); /*emit*/ itemChanged(m_currentRecord, m_curRecord, m_curColumn); } if (res == KDbValidator::Ok) { if (m_acceptsRecordEditAfterCellAccepting || m_internal_acceptsRecordEditingAfterCellAccepting) { m_inside_acceptEditor = false; acceptRecordEditing(); m_inside_acceptEditor = true; } return true; } if (m_editor) { //allow to edit the cell again, (if m_pEditor is not cleared) if (m_editor->hasFocusableWidget()) { m_editor->showWidget(); m_editor->setFocus(); } } return false; } void KexiDataAwareObjectInterface::startEditCurrentCell(const QString &setText, CreateEditorFlags flags) { //qDebug() << "setText:" << setText; if (isReadOnly() || !columnEditable(m_curColumn)) return; if (m_editor) { if (m_editor->hasFocusableWidget()) { m_editor->showWidget(); m_editor->setFocus(); } } else { if (!setText.isEmpty()) { flags |= ReplaceOldValue; } createEditor(m_curRecord, m_curColumn, setText, flags); } } void KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell() { if (isReadOnly() || !columnEditable(m_curColumn)) return; if (m_editor) {//if we've editor - just clear it m_editor->clear(); return; } if (m_curRecord < (recordCount() - 1) || !spreadSheetMode()) { ensureCellVisible(m_curRecord + 1, m_curColumn); } createEditor(m_curRecord, m_curColumn); if (!m_editor) return; m_editor->clear(); if (m_editor->acceptEditorAfterDeleteContents()) acceptEditor(); if (!m_editor || !m_editor->hasFocusableWidget()) updateCell(m_curRecord, m_curColumn); } void KexiDataAwareObjectInterface::deleteCurrentRecord() { if (m_newRecordEditing) {//we're editing fresh new row: just cancel this! cancelRecordEditing(); return; } if (!isDeleteEnabled() || !m_currentRecord || m_currentRecord == m_insertRecord) { return; } ensureCellVisible(m_curRecord, m_curColumn); if (!acceptRecordEditing()) return; switch (m_deletionPolicy) { case NoDelete: return; case ImmediateDelete: break; case AskDelete: if (KMessageBox::Yes != KMessageBox::questionYesNo( dynamic_cast(this), xi18n("Do you want to delete selected record?"), QString(), KGuiItem(xi18nc("@action:button", "&Delete Record"), KexiIconName("edit-table-delete-row")), KStandardGuiItem::cancel(), "AskBeforeDeleteRow"/*config entry*/, KMessageBox::Notify | KMessageBox::Dangerous)) { return; } break; case SignalDelete: /*emit*/ itemDeleteRequest(m_currentRecord, m_curRecord, m_curColumn); /*emit*/ currentItemDeleteRequest(); return; default: return; } if (!deleteItem(m_currentRecord)) {//nothing } } KDbRecordData* KexiDataAwareObjectInterface::insertEmptyRecord(int pos) { if (!acceptRecordEditing() || !isEmptyRecordInsertingEnabled() || (pos != -1 && pos >= (recordCount() + (isInsertingEnabled() ? 1 : 0)))) return 0; KDbRecordData *newRecord = m_data->createItem(); insertItem(newRecord, pos); return newRecord; } void KexiDataAwareObjectInterface::beginInsertItem(KDbRecordData *newRecord, int pos) { Q_UNUSED(newRecord); Q_UNUSED(pos); } void KexiDataAwareObjectInterface::endInsertItem(KDbRecordData *newRecord, int pos) { Q_UNUSED(newRecord); Q_UNUSED(pos); } void KexiDataAwareObjectInterface::insertItem(KDbRecordData *data, int pos) { const bool changeCurrentRecord = pos == -1 || pos == m_curRecord; if (changeCurrentRecord) { //change current record pos = (m_curRecord >= 0 ? m_curRecord : 0); m_currentRecord = data; m_curRecord = pos; } else if (m_curRecord >= pos) { m_curRecord++; } beginInsertItem(data, pos); m_data->insertRecord(data, pos, true /*repaint*/); // always update iterator since the list was modified... m_itemIterator = m_data->begin(); m_itemIterator += m_curRecord; endInsertItem(data, pos); } void KexiDataAwareObjectInterface::slotRecordInserted(KDbRecordData* data, bool repaint) { slotRecordInserted(data, m_data->indexOf(data), repaint); } void KexiDataAwareObjectInterface::slotRecordInserted(KDbRecordData * /*data*/, int pos, bool repaint) { if (repaint && (int)pos < recordCount()) { updateWidgetContentsSize(); updateAllVisibleRecordsBelow(pos); //update navigator's data if (m_navPanel) m_navPanel->setRecordCount(recordCount()); if (m_curRecord >= (int)pos) { //update editorShowFocus(m_curRecord, m_curColumn); } } } tristate KexiDataAwareObjectInterface::deleteAllRecords(bool ask, bool repaint) { if (!hasData()) return true; if (m_data->count() < 1) return true; if (ask) { QString tableName = m_data->dbTableName(); if (!tableName.isEmpty()) { tableName.prepend(" \""); tableName.append("\""); } if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast(this), xi18n("Do you want to clear the contents of table %1?", tableName), 0, KGuiItem(xi18nc("@action:button", "&Clear Contents"), koIcon("edit-table-clear")))) { return cancelled; } } cancelRecordEditing(); const bool repaintLater = repaint && m_spreadSheetMode; const int oldRows = recordCount(); bool res = m_data->deleteAllRecords(repaint && !repaintLater); if (res) { if (m_spreadSheetMode) { for (int i = 0; i < oldRows; i++) { m_data->append(m_data->createItem()); } } } if (repaintLater) m_data->reload(); return res; } void KexiDataAwareObjectInterface::clearColumns(bool repaint) { cancelRecordEditing(); if (m_data) { m_data->clearInternal(); } clearColumnsInternal(repaint); updateIndicesForVisibleValues(); if (repaint) updateWidgetContents(); } void KexiDataAwareObjectInterface::reloadData() { acceptRecordEditing(); if (m_curColumn >= 0 && m_curColumn < columnCount()) { //find the editor for this column KexiDataItemInterface *edit = editor(m_curColumn); if (edit) { edit->hideFocus(); } } clearVariables(); const QWidget* thisWidget = dynamic_cast(this); if (thisWidget && thisWidget->isVisible()) initDataContents(); else m_initDataContentsOnShow = true; } int KexiDataAwareObjectInterface::columnType(int col) { KDbTableViewColumn* c = m_data ? column(col) : 0; return c ? c->field()->type() : KDbField::InvalidType; } bool KexiDataAwareObjectInterface::columnEditable(int col) { KDbTableViewColumn* c = m_data ? column(col) : 0; - return c ? (! c->isReadOnly()) : false; + return c ? (!isReadOnly() && !c->isReadOnly()) : false; } QHeaderView* KexiDataAwareObjectInterface::horizontalHeader() const { return 0; } int KexiDataAwareObjectInterface::horizontalHeaderHeight() const { return 0; } QHeaderView* KexiDataAwareObjectInterface::verticalHeader() const { return 0; } int KexiDataAwareObjectInterface::recordCount() const { if (!hasData()) return 0; return m_data->count(); } int KexiDataAwareObjectInterface::columnCount() const { return dataColumns(); } int KexiDataAwareObjectInterface::dataColumns() const { if (!hasData()) return 0; return m_data->columnCount(); } QVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const { return QVariant(0); //! @todo return m_data->columns[col].defaultValue; } void KexiDataAwareObjectInterface::setAcceptsRecordEditAfterCellAccepting(bool set) { m_acceptsRecordEditAfterCellAccepting = set; } void KexiDataAwareObjectInterface::setDropsAtRecordEnabled(bool set) { if (!set) m_dragIndicatorLine = -1; if (m_dropsAtRecordEnabled && !set) { m_dropsAtRecordEnabled = false; updateWidgetContents(); } else { m_dropsAtRecordEnabled = set; } } void KexiDataAwareObjectInterface::setEmptyRecordInsertingEnabled(bool set) { m_emptyRecordInsertingEnabled = set; /*emit*/ reloadActions(); } void KexiDataAwareObjectInterface::slotAboutToDeleteRecord(KDbRecordData* data, KDbResultInfo* /*result*/, bool repaint) { if (repaint) { m_recordWillBeDeleted = m_data->indexOf(data); } } void KexiDataAwareObjectInterface::slotRecordDeleted() { if (m_recordWillBeDeleted >= 0) { if (m_recordWillBeDeleted > 0 && m_recordWillBeDeleted >= (recordCount() - 1) && !m_spreadSheetMode) m_recordWillBeDeleted = recordCount() - 1; //move up if it's the last row updateWidgetContentsSize(); if (!(m_spreadSheetMode && m_recordWillBeDeleted >= (recordCount() - 1))) setCursorPosition(m_recordWillBeDeleted, m_curColumn, ForceSetCursorPosition); updateAllVisibleRecordsBelow(m_curRecord); //needed for KexiTableView //update navigator's data if (m_navPanel) m_navPanel->setRecordCount(recordCount()); m_recordWillBeDeleted = -1; } } bool KexiDataAwareObjectInterface::beforeDeleteItem(KDbRecordData*) { //always return return true; } void KexiDataAwareObjectInterface::beginRemoveItem(KDbRecordData *data, int pos) { Q_UNUSED(data); Q_UNUSED(pos); } void KexiDataAwareObjectInterface::endRemoveItem(int pos) { Q_UNUSED(pos); } bool KexiDataAwareObjectInterface::deleteItem(KDbRecordData* data) { if (!data || !beforeDeleteItem(data)) return false; const int pos = m_data->indexOf(data); beginRemoveItem(data, pos); bool result = m_data->deleteRecord(data, true /*repaint*/); endRemoveItem(pos); if (!result) { showErrorMessageForResult(m_data->result()); return false; } if (m_spreadSheetMode) { //append empty row for spreadsheet mode insertItem(m_data->createItem(), m_data->count()); setCursorPosition(m_curRecord, m_curColumn, ForceSetCursorPosition); /*emit*/ newItemAppendedForAfterDeletingInSpreadSheetMode(); } return true; } KDbTableViewColumn* KexiDataAwareObjectInterface::column(int column) { return m_data->column(column); } bool KexiDataAwareObjectInterface::hasDefaultValueAt(const KDbTableViewColumn& tvcol) { if (m_recordEditing >= 0 && m_data->recordEditBuffer() && m_data->recordEditBuffer()->isDBAware()) { return m_data->recordEditBuffer()->hasDefaultValueAt(*tvcol.columnInfo()); } return false; } const QVariant* KexiDataAwareObjectInterface::bufferedValueAt(int record, int col, bool useDefaultValueIfPossible) { KDbRecordData *currentRecord = record < int(m_data->count()) ? m_data->at(record) : m_insertRecord; //qDebug() << m_insertItem << m_currentRecord << currentRecord; if (m_recordEditing >= 0 && record == m_recordEditing && m_data->recordEditBuffer()) { KDbTableViewColumn* tvcol = column(col); if (tvcol->isDBAware()) { //get the stored value const int realFieldNumber = fieldNumberForColumn(col); if (realFieldNumber < 0) { qWarning() << "fieldNumberForColumn(m_curColumn) < 0"; return 0; } const QVariant *storedValue = ¤tRecord->at(realFieldNumber); //db-aware data: now, try to find a buffered value (or default one) const QVariant *cv = m_data->recordEditBuffer()->at(tvcol->columnInfo(), storedValue->isNull() && useDefaultValueIfPossible); if (cv) return cv; return storedValue; } //not db-aware data: const QVariant *cv = m_data->recordEditBuffer()->at(tvcol->field()->name()); if (cv) return cv; } //not db-aware data: const int realFieldNumber = fieldNumberForColumn(col); if (realFieldNumber < 0) { qWarning() << "fieldNumberForColumn(m_curColumn) < 0"; return 0; } return ¤tRecord->at(realFieldNumber); } void KexiDataAwareObjectInterface::startEditOrToggleValue() { if (!isReadOnly() && columnEditable(m_curColumn)) { if (columnType(m_curColumn) == KDbField::Boolean) { boolToggled(); } else { startEditCurrentCell(); return; } } } void KexiDataAwareObjectInterface::boolToggled() { startEditCurrentCell(); if (m_editor) { m_editor->clickedOnContents(); } acceptEditor(); updateCell(m_curRecord, m_curColumn); } void KexiDataAwareObjectInterface::slotDataDestroying() { m_data = 0; m_itemIterator = KDbTableViewDataIterator(); } void KexiDataAwareObjectInterface::addNewRecordRequested() { if (!isInsertingEnabled()) return; if (m_recordEditing >= 0) { if (!acceptRecordEditing()) return; } if (!hasData()) return; // find first column that is not autoincrement int columnToSelect = 0; int i = 0; foreach(KDbTableViewColumn *col, *data()->columns()) { if (!col->field()->isAutoIncrement()) { columnToSelect = i; break; } ++i; } CreateEditorFlags flags = DefaultCreateEditorFlags; flags ^= EnsureCellVisible; const int recordToAdd = recordCount(); createEditor(recordToAdd, columnToSelect, QString(), flags); if (m_editor) m_editor->setFocus(); const bool orig_acceptRecordEditing_in_setCursorPosition_enabled = m_acceptRecordEditing_in_setCursorPosition_enabled; m_acceptRecordEditing_in_setCursorPosition_enabled = false; setCursorPosition(recordToAdd, columnToSelect); m_acceptRecordEditing_in_setCursorPosition_enabled = orig_acceptRecordEditing_in_setCursorPosition_enabled; } bool KexiDataAwareObjectInterface::handleKeyPress(QKeyEvent *e, int *currentRecord, int *currentColumn, bool fullRecordSelection, bool *moveToFirstField, bool *moveToLastField) { Q_ASSERT(currentRecord); Q_ASSERT(currentColumn); if (moveToFirstField) *moveToFirstField = false; if (moveToLastField) *moveToLastField = false; const bool nobtn = e->modifiers() == Qt::NoModifier; const int k = e->key(); if ( (k == Qt::Key_Up) || (k == Qt::Key_PageUp && e->modifiers() == Qt::ControlModifier)) { selectPreviousRecord(); e->accept(); } else if ( (k == Qt::Key_Down) || (k == Qt::Key_PageDown && e->modifiers() == Qt::ControlModifier)) { selectNextRecord(); e->accept(); } else if (k == Qt::Key_PageUp && nobtn) { selectPreviousPage(); e->accept(); } else if (k == Qt::Key_PageDown && nobtn) { selectNextPage(); e->accept(); } else if (k == Qt::Key_Home) { if (fullRecordSelection) { //we're in record-selection mode: home key always moves to 1st record currentRecord = 0;//to 1st record } else {//cell selection mode: different actions depending on ctrl and shift keys mods if (nobtn) { *currentColumn = 0;//to 1st col } else if (e->modifiers() == Qt::ControlModifier) { *currentRecord = 0;//to 1st record and column *currentColumn = 0; } else return false; } if (moveToFirstField) *moveToFirstField = true; //do not accept yet e->ignore(); } else if (k == Qt::Key_End) { if (fullRecordSelection) { //we're in record-selection mode: home key always moves to the last record *currentRecord = m_data->count() - 1 + (isInsertingEnabled() ? 1 : 0);//to last the record } else {//cell selection mode: different actions depending on ctrl and shift keys mods if (nobtn) { *currentColumn = columnCount() - 1;//to last col } else if (e->modifiers() == Qt::ControlModifier) { *currentRecord = m_data->count() - 1 /*+(isInsertingEnabled()?1:0)*/; //to the last record and col *currentColumn = columnCount() - 1;//to last col } else return false; } if (moveToLastField) *moveToLastField = true; //do not accept yet e->ignore(); } else if (isInsertingEnabled() && ( (e->modifiers() == Qt::ControlModifier && k == Qt::Key_Equal) || (e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier) && k == Qt::Key_Equal) ) ) { *currentRecord = m_data->count(); //to the new record *currentColumn = 0;//to first col if (moveToFirstField) *moveToFirstField = true; //do not accept yet e->ignore(); } else { return false; } return true; } void KexiDataAwareObjectInterface::verticalScrollBarValueChanged(int v) { Q_UNUSED(v); if (!m_verticalScrollBarValueChanged_enabled) return; if (m_scrollbarToolTipsEnabled && verticalScrollBar()->isSliderDown()) { QWidget* thisWidget = dynamic_cast(this); const int record = lastVisibleRecord() + 1; if (thisWidget && record > 0) { const QString toolTipText( xi18n("Record: %1", record) ); QToolTip::showText( QPoint( verticalScrollBar()->mapToGlobal(QPoint(0, 0)).x() //thisWidget->mapToGlobal(verticalScrollBar()->pos()).x() - thisWidget->fontMetrics().width(toolTipText+" "), QCursor::pos().y() - thisWidget->fontMetrics().height() / 2 - thisWidget->fontMetrics().height() // because: "shown with a platform specific offset from the point of interest" ), toolTipText, 0, QRect() ); } } } void KexiDataAwareObjectInterface::setContextMenuTitle(const QIcon &icon, const QString &text) { m_contextMenuTitleIcon = icon; m_contextMenuTitleText = text; /*emit*/ reloadActions(); } bool KexiDataAwareObjectInterface::scrollbarToolTipsEnabled() const { return m_scrollbarToolTipsEnabled; } void KexiDataAwareObjectInterface::setScrollbarToolTipsEnabled(bool set) { m_scrollbarToolTipsEnabled = set; } void KexiDataAwareObjectInterface::focusOutEvent(QFocusEvent* e) { Q_UNUSED(e); updateCell(m_curRecord, m_curColumn); } int KexiDataAwareObjectInterface::showErrorMessageForResult(const KDbResultInfo& resultInfo) { QWidget *thisWidget = dynamic_cast(this); if (resultInfo.allowToDiscardChanges) { return KMessageBox::questionYesNo(thisWidget, resultInfo.message + (resultInfo.description.isEmpty() ? QString() : ("\n" + resultInfo.description)), QString(), KGuiItem(xi18nc("@action:button Correct Changes", "Correct"), QString(), xi18n("Correct changes")), KGuiItem(xi18nc("@action:button", "Discard Changes"))); } if (resultInfo.description.isEmpty()) { KMessageBox::sorry(thisWidget, resultInfo.message); } else { KMessageBox::detailedSorry(thisWidget, resultInfo.message, resultInfo.description); } return KMessageBox::Ok; } void KexiDataAwareObjectInterface::updateIndicesForVisibleValues() { m_indicesForVisibleValues.resize(m_data ? m_data->columnCount() : 0); if (!m_data) return; for (int i = 0; i < m_data->columnCount(); i++) { KDbTableViewColumn* tvCol = m_data->column(i); if (tvCol->columnInfo() && tvCol->columnInfo()->indexForVisibleLookupValue() != -1) // retrieve visible value from lookup field m_indicesForVisibleValues[ i ] = tvCol->columnInfo()->indexForVisibleLookupValue(); else m_indicesForVisibleValues[ i ] = i; } } /*! Performs searching \a stringValue in \a where string. \a matchAnyPartOfField, \a matchWholeField, \a wholeWordsOnly options are used to control how to search. If \a matchWholeField is true, \a wholeWordsOnly is not checked. \a firstCharacter is in/out parameter. If \a matchAnyPartOfField is true and \a matchWholeField is false, \a firstCharacter >= 0, the search will be performed after skipping first \a firstCharacter characters. If \a forward is false, we are searching backward from \a firstCharacter position. \a firstCharacter == -1 means then the last character. \a firstCharacter == INT_MAX means "before first" place, so searching fails immediately. On success, true is returned and \a firstCharacter is set to position of the matched string. */ static inline bool findInString(const QString& stringValue, int stringLength, const QString& where, int& firstCharacter, bool matchAnyPartOfField, bool matchWholeField, Qt::CaseSensitivity caseSensitivity, bool wholeWordsOnly, bool forward) { if (where.isEmpty()) { firstCharacter = -1; return false; } if (matchAnyPartOfField) { if (forward) { int pos = firstCharacter == -1 ? 0 : firstCharacter; if (wholeWordsOnly) { const int whereLength = where.length(); while (true) { pos = where.indexOf(stringValue, pos, caseSensitivity); if (pos == -1) break; if ( (pos > 0 && where.at(pos - 1).isLetterOrNumber()) || ((pos + stringLength - 1) < (whereLength - 1) && where.at(pos + stringLength - 1 + 1).isLetterOrNumber())) { pos++; // invalid match because before or after the string there is non-white space } else break; }//while firstCharacter = pos; } else {// !wholeWordsOnly firstCharacter = where.indexOf(stringValue, pos, caseSensitivity); } return firstCharacter != -1; } else { // !matchAnyPartOfField if (firstCharacter == INT_MAX) { firstCharacter = -1; //next time we'll be looking at different cell return false; } int pos = firstCharacter; if (wholeWordsOnly) { const int whereLength = where.length(); while (true) { pos = where.lastIndexOf(stringValue, pos, caseSensitivity); if (pos == -1) break; if ( (pos > 0 && where.at(pos - 1).isLetterOrNumber()) || ((pos + stringLength - 1) < (whereLength - 1) && where.at(pos + stringLength - 1 + 1).isLetterOrNumber())) { // invalid match because before or after the string there is non-white space pos--; if (pos < 0) // it can make pos < 0 break; } else break; }//while firstCharacter = pos; } else {// !wholeWordsOnly firstCharacter = where.lastIndexOf(stringValue, pos, caseSensitivity); } return firstCharacter != -1; } } else if (matchWholeField) { if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char firstCharacter = -1; } else if ((caseSensitivity == Qt::CaseSensitive ? where : where.toLower()) == stringValue) { firstCharacter = 0; return true; } } else {// matchStartOfField if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char firstCharacter = -1; } else if (where.startsWith(stringValue, caseSensitivity)) { if (wholeWordsOnly) { // If where.length() < stringValue.length(), true will be returned too - fine. return !where.at(stringValue.length()).isLetterOrNumber(); } firstCharacter = 0; return true; } } return false; } tristate KexiDataAwareObjectInterface::find(const QVariant& valueToFind, const KexiSearchAndReplaceViewInterface::Options& options, bool next) { if (!hasData()) return cancelled; const QVariant prevSearchedValue(m_recentlySearchedValue); m_recentlySearchedValue = valueToFind; const KexiSearchAndReplaceViewInterface::Options::SearchDirection prevSearchDirection = m_recentSearchDirection; m_recentSearchDirection = options.searchDirection; if (valueToFind.isNull() || valueToFind.toString().isEmpty()) return cancelled; const bool forward = (options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchUp) ? !next : next; //direction can be reversed if ((!prevSearchedValue.isNull() && prevSearchedValue != valueToFind) || (prevSearchDirection != options.searchDirection && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRecords)) { // restart searching when value has been changed or new direction is SearchAllRecords m_positionOfRecentlyFoundValue.exists = false; } const bool startFrom1stRecordAndCol = !m_positionOfRecentlyFoundValue.exists && next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRecords; const bool startFromLastRecordAndCol = ( !m_positionOfRecentlyFoundValue.exists && !next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRecords) || (m_curRecord >= recordCount() && !forward); //we're at "insert" record, and searching backwards: move to the last cell if (!startFrom1stRecordAndCol && !startFromLastRecordAndCol && m_curRecord >= recordCount()) { //we're at "insert" record, and searching forward: no chances to find something return false; } KDbTableViewDataIterator it((startFrom1stRecordAndCol || startFromLastRecordAndCol) ? m_data->begin() : m_itemIterator /*start from the current cell*/); if (startFromLastRecordAndCol) it += (m_data->columnCount() - 1); int firstCharacter; if (m_positionOfRecentlyFoundValue.exists) {// start after the next/prev char position if (forward) firstCharacter = m_positionOfRecentlyFoundValue.lastCharacter + 1; else { firstCharacter = (m_positionOfRecentlyFoundValue.firstCharacter > 0) ? (m_positionOfRecentlyFoundValue.firstCharacter - 1) : INT_MAX /* this means 'before first'*/; } } else { firstCharacter = -1; //forward ? -1 : INT_MAX; } const int columnCount = m_data->columnCount(); int record, column; if (startFrom1stRecordAndCol) { record = 0; column = 0; } else if (startFromLastRecordAndCol) { record = recordCount() - 1; column = columnCount - 1; } else { record = m_curRecord; column = m_curColumn; } //sache some flags for efficiency const bool matchAnyPartOfField = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchAnyPartOfField; const bool matchWholeField = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchWholeField; const Qt::CaseSensitivity caseSensitivity = options.caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; const bool wholeWordsOnly = options.wholeWordsOnly; int columnNumber = (options.columnNumber == KexiSearchAndReplaceViewInterface::Options::CurrentColumn) ? m_curColumn : options.columnNumber; if (columnNumber >= 0) column = columnNumber; const bool lookInAllColumns = columnNumber == KexiSearchAndReplaceViewInterface::Options::AllColumns; int firstColumn; // real number of the first column, can be smaller than lastColumn if forward==true int lastColumn; // real number of the last column if (lookInAllColumns) { firstColumn = forward ? 0 : columnCount - 1; lastColumn = forward ? columnCount - 1 : 0; } else { firstColumn = columnNumber; lastColumn = columnNumber; } const QString stringValue( caseSensitivity == Qt::CaseSensitive ? valueToFind.toString() : valueToFind.toString().toLower()); const int stringLength = stringValue.length(); // search const int prevRecord = m_curRecord; KDbRecordData *data = 0; while ((it != m_data->end() && (data = *it))) { for (; forward ? column <= lastColumn : column >= lastColumn; column = forward ? (column + 1) : (column - 1)) { const QVariant cell(data->at(m_indicesForVisibleValues[ column ])); if (findInString(stringValue, stringLength, cell.toString(), firstCharacter, matchAnyPartOfField, matchWholeField, caseSensitivity, wholeWordsOnly, forward)) { setCursorPosition(record, column, ForceSetCursorPosition); if (prevRecord != m_curRecord) updateRecord(prevRecord); // remember the exact position for the found value m_positionOfRecentlyFoundValue.exists = true; m_positionOfRecentlyFoundValue.firstCharacter = firstCharacter; //! @todo for regexp lastCharacter should be computed m_positionOfRecentlyFoundValue.lastCharacter = firstCharacter + stringLength - 1; return true; } }//for if (forward) { ++it; ++record; } else { if (m_data->begin() == it) { break; } else { --it; --record; } } column = firstColumn; }//while return false; } tristate KexiDataAwareObjectInterface::findNextAndReplace( const QVariant& valueToFind, const QVariant& replacement, const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) { Q_UNUSED(replacement); Q_UNUSED(options); Q_UNUSED(replaceAll); if (isReadOnly()) return cancelled; if (valueToFind.isNull() || valueToFind.toString().isEmpty()) return cancelled; //! @todo implement KexiDataAwareObjectInterface::findAndReplace() return false; } void KexiDataAwareObjectInterface::setRecordEditing(int record) { if (record == m_recordEditing) { return; } if (m_recordEditing >= 0 && record >= 0) { qWarning() << "Cannot set editing for row" << record << "before editing of row" << m_recordEditing << "is accepted or cancelled"; return; } m_recordEditing = record; if (record >= 0) { emit recordEditingStarted(record); } else { emit recordEditingTerminated(record); } } void KexiDataAwareObjectInterface::showEditorContextMessage( KexiDataItemInterface *item, const QString &message, KMessageWidget::MessageType type, KMessageWidget::CalloutPointerDirection direction) { QWidget *par = dynamic_cast(this) ? dynamic_cast(this)->widget() : dynamic_cast(this); QWidget *edit = dynamic_cast(item); if (par && edit) { delete m_errorMessagePopup; KexiContextMessage msg(message); m_errorMessagePopup = new KexiContextMessageWidget(dynamic_cast(this), 0, 0, msg); QPoint arrowPos = par->mapToGlobal(edit->pos()) + QPoint(12, edit->height() + 6); if (verticalHeader()) { arrowPos += QPoint(verticalHeader()->width(), horizontalHeaderHeight()); } m_errorMessagePopup->setMessageType(type); m_errorMessagePopup->setCalloutPointerDirection(direction); m_errorMessagePopup->setCalloutPointerPosition(arrowPos); m_errorMessagePopup->setWordWrap(false); m_errorMessagePopup->setClickClosesMessage(true); m_errorMessagePopup->resizeToContents(); QObject::connect(m_errorMessagePopup, SIGNAL(animatedHideFinished()), edit, SLOT(setFocus())); m_errorMessagePopup->animatedShow(); edit->setFocus(); } } static QString lengthExceededMessage(KexiDataItemInterface *item) { return xi18np( "Limit of %2 characters for %3 field has been exceeded by %1 character.\n" "Fix the text or it will be truncated upon saving changes.", "Limit of %2 characters for %3 field has been exceeded by %1 characters.\n" "Fix the text or it will be truncated upon saving changes.", item->value().toString().length() - item->columnInfo()->field()->maxLength(), item->columnInfo()->field()->maxLength(), item->columnInfo()->captionOrAliasOrName()); } void KexiDataAwareObjectInterface::showLengthExceededMessage(KexiDataItemInterface *item, bool exceeded) { if (exceeded) { if (item) { showEditorContextMessage( item, lengthExceededMessage(item), KMessageWidget::Warning, KMessageWidget::Up); m_lengthExceededMessageVisible = true; } } else { if (m_errorMessagePopup) { m_errorMessagePopup->animatedHide(); m_lengthExceededMessageVisible = false; } } } void KexiDataAwareObjectInterface::showUpdateForLengthExceededMessage(KexiDataItemInterface *item) { if (m_errorMessagePopup && m_lengthExceededMessageVisible) { m_errorMessagePopup->setText(lengthExceededMessage(item)); m_errorMessagePopup->resizeToContents(); } } diff --git a/src/widget/dataviewcommon/kexidataprovider.cpp b/src/widget/dataviewcommon/kexidataprovider.cpp index 182311b5c..cfca165d6 100644 --- a/src/widget/dataviewcommon/kexidataprovider.cpp +++ b/src/widget/dataviewcommon/kexidataprovider.cpp @@ -1,253 +1,253 @@ /* 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 "kexidataprovider.h" #include #include #include #include #include #include #include KexiFormDataProvider::KexiFormDataProvider() : KexiDataItemChangesListener() , m_mainWidget(0) , m_duplicatedItems(0) , m_disableFillDuplicatedDataItems(false) { } KexiFormDataProvider::~KexiFormDataProvider() { delete m_duplicatedItems; } void KexiFormDataProvider::setMainDataSourceWidget(QWidget* mainWidget) { m_mainWidget = mainWidget; m_dataItems.clear(); m_usedDataSources.clear(); m_fieldNumbersForDataItems.clear(); if (!m_mainWidget) return; //find widgets whose will work as data items const QList widgets(m_mainWidget->findChildren()); QSet tmpSources; foreach(QWidget *widget, widgets) { KexiFormDataItemInterface* const formDataItem = dynamic_cast(widget); if (!formDataItem) continue; if (formDataItem->parentDataItemInterface()) //item with parent interface: collect parent instead... continue; QString dataSource(formDataItem->dataSource().toLower()); if (dataSource.isEmpty()) continue; qDebug() << widget->objectName(); m_dataItems.append(formDataItem); formDataItem->installListener(this); tmpSources.insert(dataSource); } //now we've got a set (unique list) of field names in tmpSources //remember it in m_usedDataSources foreach(const QString& source, tmpSources) { m_usedDataSources += source; } } void KexiFormDataProvider::fillDataItems(KDbRecordData *data, bool cursorAtNewRecord) { Q_ASSERT(data); qDebug() << "record.count=" << data->count() << "\nRECORD=" << *data; for (KexiFormDataItemInterfaceToIntMap::ConstIterator it = m_fieldNumbersForDataItems.constBegin(); it != m_fieldNumbersForDataItems.constEnd(); ++it) { KexiFormDataItemInterface *itemIface = it.key(); if (!itemIface->columnInfo()) { qDebug() << "itemIface->columnInfo() == 0"; continue; } //1. Is this a value with a combo box (lookup)? int indexForVisibleLookupValue = itemIface->columnInfo()->indexForVisibleLookupValue(); if (indexForVisibleLookupValue<0 && indexForVisibleLookupValue >= data->count()) //sanity indexForVisibleLookupValue = -1; //no const QVariant value(data->at(it.value())); QVariant visibleLookupValue; if (indexForVisibleLookupValue != -1 && (int)data->count() > indexForVisibleLookupValue) { visibleLookupValue = data->at(indexForVisibleLookupValue); } qDebug() << "fill data of" << itemIface->dataSource() << "at idx" << it.value() << "data=" << KDbUtils::squeezedValue(value) << "indexForVisibleLookupValue=" << indexForVisibleLookupValue << "visibleLookupValue=" << KDbUtils::squeezedValue(visibleLookupValue); const bool displayDefaultValue = cursorAtNewRecord && (value.isNull() && visibleLookupValue.isNull()) && !itemIface->columnInfo()->field()->defaultValue().isNull() && !itemIface->columnInfo()->field()->isAutoIncrement(); //no value to set but there is default value defined itemIface->setValue( displayDefaultValue ? itemIface->columnInfo()->field()->defaultValue() : value, QVariant(), /*add*/ /*!remove old*/false, indexForVisibleLookupValue == -1 ? 0 : &visibleLookupValue //pass visible value if available ); // now disable/enable "display default value" if needed (do it after setValue(), before setValue() turns it off) if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue) itemIface->setDisplayDefaultValue(dynamic_cast(itemIface), displayDefaultValue); } } void KexiFormDataProvider::fillDuplicatedDataItems( KexiFormDataItemInterface* item, const QVariant& value) { if (m_disableFillDuplicatedDataItems) return; if (!m_duplicatedItems) { //build (once) a set of duplicated data items (having the same fields assigned) //so we can later check if an item is duplicated with a cost of o(1) QHash tmpDuplicatedItems; QHash::const_iterator it_dup; foreach(KexiFormDataItemInterface *dataItemIface, m_dataItems) { if (!dataItemIface->columnInfo() || !dataItemIface->columnInfo()->field()) continue; qDebug() << " ** " << dataItemIface->columnInfo()->field()->name(); it_dup = tmpDuplicatedItems.constFind(dataItemIface->columnInfo()->field()); int count; if (it_dup == tmpDuplicatedItems.constEnd()) count = 0; else count = it_dup.value(); tmpDuplicatedItems.insert(dataItemIface->columnInfo()->field(), ++count); } m_duplicatedItems = new QSet(); for (it_dup = tmpDuplicatedItems.constBegin(); it_dup != tmpDuplicatedItems.constEnd(); ++it_dup) { if (it_dup.value() > 1) { m_duplicatedItems->insert(it_dup.key()); qDebug() << "duplicated item: " << static_cast(it_dup.key())->name() << " (" << it_dup.value() << " times)"; } } } if (item->columnInfo() && m_duplicatedItems->contains(item->columnInfo()->field())) { foreach(KexiFormDataItemInterface *dataItemIface, m_dataItems) { if (dataItemIface != item && item->columnInfo()->field() == dataItemIface->columnInfo()->field()) { qDebug() << "- setting a copy of value for item '" << dynamic_cast(dataItemIface)->objectName() << "' == " << value; dataItemIface->setValue(value); } } } } void KexiFormDataProvider::valueChanged(KexiDataItemInterface* item) { Q_UNUSED(item); } bool KexiFormDataProvider::cursorAtNewRecord() const { return false; } -void KexiFormDataProvider::invalidateDataSources(const QSet& invalidSources, - KDbQuerySchema* query) +void KexiFormDataProvider::invalidateDataSources(const QSet &invalidSources, + KDbConnection *conn, KDbQuerySchema *query) { //fill m_fieldNumbersForDataItems mapping from data item to field number //(needed for fillDataItems) KDbQueryColumnInfo::Vector fieldsExpanded; // int dataFieldsCount; // == fieldsExpanded.count() if query is available or else == m_dataItems.count() - if (query) { - fieldsExpanded = query->fieldsExpanded(KDbQuerySchema::WithInternalFields); + if (conn && query) { + fieldsExpanded = query->fieldsExpanded(conn, KDbQuerySchema::FieldsExpandedMode::WithInternalFields); // dataFieldsCount = fieldsExpanded.count(); - QHash columnsOrder(query->columnsOrder()); + QHash columnsOrder(query->columnsOrder(conn)); for (QHash::const_iterator it = columnsOrder.constBegin(); it != columnsOrder.constEnd(); ++it) { qDebug() << "query->columnsOrder()[ " << it.key()->field()->name() << " ] = " << it.value(); } foreach(KexiFormDataItemInterface *item, m_dataItems) { - KDbQueryColumnInfo* ci = query->columnInfo(item->dataSource()); + KDbQueryColumnInfo* ci = query->columnInfo(conn, item->dataSource()); int index = ci ? columnsOrder[ ci ] : -1; qDebug() << "query->columnsOrder()[ " << (ci ? ci->field()->name() : QString()) << " ] = " << index << " (dataSource: " << item->dataSource() << ", name=" << dynamic_cast(item)->objectName() << ")"; if (index != -1 && !m_fieldNumbersForDataItems[ item ]) m_fieldNumbersForDataItems.insert(item, index); //! @todo //WRONG: not only used data sources can be fetched! // m_fieldNumbersForDataItems.insert( item, // m_usedDataSources.findIndex(item->dataSource().toLower()) ); } } //update data sources set (some of them may be removed) QSet tmpUsedDataSources; if (query) { - qDebug() << *query; + qDebug() << KDbConnectionAndQuerySchema(conn, *query); } m_disableFillDuplicatedDataItems = true; // temporary disable fillDuplicatedDataItems() // because setColumnInfo() can activate it for (QList::iterator it(m_dataItems.begin()); it != m_dataItems.end();) { KexiFormDataItemInterface *item = *it; Q_ASSERT(item); if (invalidSources.contains(item->dataSource().toLower())) { item->setInvalidState(QString::fromLatin1("#%1?").arg(xi18n("NAME"))); it = m_dataItems.erase(it); continue; } int fieldNumber = m_fieldNumbersForDataItems[ item ]; if (query) { KDbQueryColumnInfo *ci = fieldsExpanded[fieldNumber]; - item->setColumnInfo(ci); + item->setColumnInfo(conn, ci); qDebug() << "- item=" << dynamic_cast(item)->objectName() << " dataSource=" << item->dataSource() << " field=" << ci->field()->name(); const int indexForVisibleLookupValue = ci->indexForVisibleLookupValue(); if (-1 != indexForVisibleLookupValue && indexForVisibleLookupValue < (int)fieldsExpanded.count()) { //there's lookup column defined: set visible column as well KDbQueryColumnInfo *visibleColumnInfo = fieldsExpanded[ indexForVisibleLookupValue ]; if (visibleColumnInfo) { item->setVisibleColumnInfo(visibleColumnInfo); if (item->isComboBox() && m_mainWidget && item->internalEditor()) { // m_mainWidget (dbform) should filter the (just created using setVisibleColumnInfo()) // combo box' internal editor (actually, only if the combo is in 'editable' mode) item->internalEditor()->installEventFilter(m_mainWidget); } qDebug() << "ALSO SET visibleColumn=" << *visibleColumnInfo << "\n at position " << indexForVisibleLookupValue; } } } tmpUsedDataSources.insert(item->dataSource().toLower()); ++it; } m_disableFillDuplicatedDataItems = false; m_usedDataSources.clear(); foreach(const QString& source, tmpUsedDataSources) { m_usedDataSources += source; } } diff --git a/src/widget/dataviewcommon/kexidataprovider.h b/src/widget/dataviewcommon/kexidataprovider.h index 3cd208bf5..ee65ec088 100644 --- a/src/widget/dataviewcommon/kexidataprovider.h +++ b/src/widget/dataviewcommon/kexidataprovider.h @@ -1,94 +1,94 @@ /* This file is part of the KDE project Copyright (C) 2005 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 KEXIFORMDATAPROVIDER_H #define KEXIFORMDATAPROVIDER_H #include "kexidataviewcommon_export.h" #include "kexiformdataiteminterface.h" #include #include class KDbQuerySchema; class KDbRecordData; //! @short The KexiFormDataProvider class is a data provider for Kexi Forms /*! This provider collects data-aware widgets using setMainWidget(). Then, usedDataSources() unique list of required field names is available. On every call of fillDataItems() method, the provider will fill data items with appropriate data from a database cursor. Field names are collected effectively, so eg. having widgets using data sources: ("name", "surname", "surname", "name") - "name" and "surname" repeated - will only return ("name", "surname") list, so the cursor's query can be simplified and thus more effective. */ class KEXIDATAVIEWCOMMON_EXPORT KexiFormDataProvider : public KexiDataItemChangesListener { public: KexiFormDataProvider(); virtual ~KexiFormDataProvider(); /*! sets \a mainWidget to be a main widget for this data provider. Also find widgets whose will work as data items (all of them must implement KexiFormDataItemInterface), so these could be filled with data on demand. */ void setMainDataSourceWidget(QWidget* mainWidget); QStringList usedDataSources() const { return m_usedDataSources; } /*! Fills data items with appropriate data fetched from \a data. \a cursorAtNewRecord == true means that we are at new (not yet inserted) database row. */ void fillDataItems(KDbRecordData *data, bool cursorAtNewRecord); /*! Implementation for KexiDataItemChangesListener. Reaction for change of \a item. Does nothing here. */ virtual void valueChanged(KexiDataItemInterface* item); /*! Implementation for KexiDataItemChangesListener. Implement this to return information whether we're currently at new record or not. This can be used e.g. by data-aware widgets to determine if "(autonumber)" label should be displayed. Returns false here. */ virtual bool cursorAtNewRecord() const; /*! Invalidates data sources collected by this provided. \a invalidSources is the set of data sources that should be omitted for fillDataItems(). Used by KexiFormView::initDataSource(). */ void invalidateDataSources(const QSet& invalidSources, - KDbQuerySchema* query = 0); + KDbConnection *conn, KDbQuerySchema* query); /*! Fills the same data provided by \a value to every data item (other than \a item) having the same data source as \a item. This method is called immediately when \a value is changed, so duplicated data items are quickly updated. */ void fillDuplicatedDataItems(KexiFormDataItemInterface* item, const QVariant& value); protected: QWidget *m_mainWidget; QSet *m_duplicatedItems; typedef QMap KexiFormDataItemInterfaceToIntMap; QList m_dataItems; QStringList m_usedDataSources; KexiFormDataItemInterfaceToIntMap m_fieldNumbersForDataItems; bool m_disableFillDuplicatedDataItems; }; #endif diff --git a/src/widget/dataviewcommon/kexiformdataiteminterface.h b/src/widget/dataviewcommon/kexiformdataiteminterface.h index 080e1bc10..ab336f898 100644 --- a/src/widget/dataviewcommon/kexiformdataiteminterface.h +++ b/src/widget/dataviewcommon/kexiformdataiteminterface.h @@ -1,170 +1,172 @@ /* 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. */ #ifndef KEXIFORMDATAITEMINTERFACE_H #define KEXIFORMDATAITEMINTERFACE_H #include "kexidataviewcommon_export.h" #include #include #include +class KDbConnection; class KDbField; //! An interface for declaring form widgets to be data-aware. class KEXIDATAVIEWCOMMON_EXPORT KexiFormDataItemInterface : public KexiDataItemInterface { public: KexiFormDataItemInterface(); virtual ~KexiFormDataItemInterface(); //! \return the name of the data source for this widget. //! Data source usually means here a table or query, a field name or an expression. inline QString dataSource() const { return m_dataSource; } //! Sets the name of the data source for this widget. //! Data source usually means here a table or query or field name name. inline void setDataSource(const QString &ds) { m_dataSource = ds; } /*! \return the plugin ID of the part for the widget's data source. It means IDs like "org.kexi-project.table" or "org.kexi-project.query" if the data source is set to object (as within form) or is empty if the data source is set to table field or query column. */ inline QString dataSourcePluginId() const { return m_dataSourcePartClass; } /*! Sets the plugin ID of the part for the data widget's data source. It usually means here a "org.kexi-project.table" or "org.kexi-project.query". @see dataSourcePluginId() */ inline void setDataSourcePluginId(const QString &pluginId) { m_dataSourcePartClass = pluginId; } /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). \a widget is equal to 'this'. You can reimplement this in the widget. Always call the superclass' implementation. setDisplayDefaultValue(.., false) is called in KexiFormScrollView::valueChanged() as a response on data change performed by user. */ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); /*! \return true if default value is displayed for this item. */ virtual bool hasDisplayedDefaultValue() const { return m_displayDefaultValue; } /*! Convenience function: casts this item to a QWidget. Can return 0 if the item is not a QWidget-derived object. */ virtual QWidget* widget() { return dynamic_cast(this); } /*! Sets 'invalid' state, e.g. a text editor widget should display text \a displayText and become read only to prevent entering data, because updating at the database backend is not available. \a displayText is usually set to something i18n'd like "#NAME?". Note: that even widgets that usually do not display texts (e.g. pixmaps) should display \a displayText too. */ virtual void setInvalidState(const QString& displayText) = 0; /*! Changes 'read only' flag, for this widget. Typically this flag can be passed to a widget itself, e.g. QLineEdit::setReadOnly(bool). */ virtual void setReadOnly(bool readOnly) = 0; //! \return database column information for this item KDbField* field() override; //! \return database column information for this item KDbQueryColumnInfo* columnInfo() { return m_columnInfo; } /*! Used internally to set database column information. Reimplement if you need to do additional actions, e.g. set data validator based on field type. Don't forget about calling superclass implementation. */ - virtual void setColumnInfo(KDbQueryColumnInfo* cinfo) { + virtual void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { + Q_UNUSED(conn) m_columnInfo = cinfo; } /*! Used internally to set visible database column information. Reimplemented in KexiDBComboBox: except for combo box, this does nothing. */ virtual void setVisibleColumnInfo(KDbQueryColumnInfo* cinfo) { Q_UNUSED(cinfo); } /*! \return visible database column information for this item. Except for combo box, this is exactly the same as columnInfo(). */ virtual KDbQueryColumnInfo* visibleColumnInfo() { return columnInfo(); } /*! Does nothing, because within forms, widgets are always visible. */ virtual void hideWidget() { } /*! Does nothing, because within forms, widgets are always visible. */ virtual void showWidget() { } /*! Undoes changes made to this item - just resets the widget to original value. Note: This is internal method called by KexiFormScrollView::cancelEditor(). To cancel editing of the widget's data from the widget's code, use KexiFormDataItemInterface::cancelEditor(). Reimplemented in KexiDBComboBox to also revert the visible value (i.e. text) to the original state. */ virtual void undoChanges(); /* Cancels editing of the widget's data. This method just looks for the (grand)parent KexiFormScrollView object and calls KexiFormScrollView::cancelEditor(). */ void cancelEditor(); /*! @internal Called by top-level form on key press event. Default implementation does nothing. Implement this if you want to handle key presses from within the editor widget item. \return true if \a ke should be accepted by the widget item. This method is used e.g. in KexiDBImageBox for Key_Escape to if the popup is visible, so the key press won't be consumed to perform "cancel editing". */ virtual bool keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; } //! Selects contents of the widget if there is such behaviour set (it is by default). //! @todo add option for not selecting the field virtual void selectAllOnFocusIfNeeded(); protected: QString m_dataSource; QString m_dataSourcePartClass; KDbQueryColumnInfo* m_columnInfo; KexiDisplayUtils::DisplayParameters *m_displayParametersForEnteredValue; //!< used in setDisplayDefaultValue() KexiDisplayUtils::DisplayParameters *m_displayParametersForDefaultValue; //!< used in setDisplayDefaultValue() bool m_displayDefaultValue; //!< used by setDisplayDefaultValue() friend class KexiDBAutoField; }; #endif diff --git a/src/widget/fields/KexiFieldComboBox.cpp b/src/widget/fields/KexiFieldComboBox.cpp index a3004ef68..bd9e7501f 100644 --- a/src/widget/fields/KexiFieldComboBox.cpp +++ b/src/widget/fields/KexiFieldComboBox.cpp @@ -1,221 +1,221 @@ /* 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 "KexiFieldComboBox.h" #include #include #include #include "KexiFieldListModel.h" #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiFieldComboBox::Private { public: Private() : table(true) , insideSetFieldOrExpression(false) { } ~Private() { } QPointer prj; QPointer model; QString tableOrQueryName; QString fieldOrExpression; bool table; bool insideSetFieldOrExpression; }; //------------------------ KexiFieldComboBox::KexiFieldComboBox(QWidget *parent) : KComboBox(true/*rw*/, parent) , d(new Private()) { setInsertPolicy(NoInsert); setCompletionMode(KCompletion::CompletionPopupAuto); setMaxVisibleItems(16); connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int))); connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); } KexiFieldComboBox::~KexiFieldComboBox() { delete d; } void KexiFieldComboBox::setProject(KexiProject *prj) { if ((KexiProject*)d->prj == prj) return; d->prj = prj; setTableOrQuery(QString(), true); } KexiProject* KexiFieldComboBox::project() const { return d->prj; } void KexiFieldComboBox::setTableOrQuery(const QString& name, bool table) { d->tableOrQueryName = name; d->table = table; clear(); if (d->tableOrQueryName.isEmpty() || !d->prj) return; KDbTableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.toLatin1(), d->table ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) return; delete d->model; d->model = new KexiFieldListModel(this, ShowEmptyItem); - d->model->setSchema(&tableOrQuery); + d->model->setSchema(d->prj->dbConnection(), &tableOrQuery); setModel(d->model); //update selection setFieldOrExpression(d->fieldOrExpression); } QString KexiFieldComboBox::tableOrQueryName() const { return d->tableOrQueryName; } bool KexiFieldComboBox::isTableAssigned() const { return d->table; } void KexiFieldComboBox::setFieldOrExpression(const QString& string) { if (d->insideSetFieldOrExpression) { return; } KexiUtils::BoolBlocker guard(&d->insideSetFieldOrExpression, true); const QString name(string); const int pos = name.indexOf('.'); if (pos == -1) { d->fieldOrExpression = name; } else { QString objectName = name.left(pos); if (d->tableOrQueryName != objectName) { d->fieldOrExpression = name; setEditText(name); //! @todo show error qWarning() << "invalid table/query name in" << name; return; } d->fieldOrExpression = name.mid(pos + 1); } //! @todo show 'the item doesn't match' info? setEditText(d->fieldOrExpression); } void KexiFieldComboBox::setFieldOrExpression(int index) { if (index >= 0) { index++; //skip 1st empty item } if (index >= count()) { qWarning() << "index" << index << "out of range 0.." << (count() - 1); index = -1; } if (index <= 0) { setCurrentIndex(0); d->fieldOrExpression.clear(); } else { setCurrentIndex(index); d->fieldOrExpression = itemData(currentIndex(), Qt::DisplayRole).toString(); lineEdit()->setText(d->fieldOrExpression); } } QString KexiFieldComboBox::fieldOrExpression() const { return d->fieldOrExpression; } int KexiFieldComboBox::indexOfField() const { KDbTableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.toLatin1(), d->table ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) return -1; return currentIndex() > 0 ? (currentIndex() - 1) : -1; } QString KexiFieldComboBox::fieldOrExpressionCaption() const { return itemData(currentIndex()).toString(); } void KexiFieldComboBox::slotActivated(int i) { d->fieldOrExpression = itemData(i, Qt::DisplayRole).toString(); setFieldOrExpression(d->fieldOrExpression); emit selected(); } void KexiFieldComboBox::slotReturnPressed(const QString & text) { //text is available: select item for this text: int index; if (text.isEmpty()) { index = 0; } else { index = findText(text, Qt::MatchExactly); if (index < 1) return; } setCurrentIndex(index); slotActivated(index); } void KexiFieldComboBox::focusOutEvent(QFocusEvent *e) { KComboBox::focusOutEvent(e); // accept changes if the focus is moved if (!KDbUtils::hasParent(this, focusWidget())) { //(a check needed because drop-down listbox also causes a focusout) slotReturnPressed(currentText()); } } diff --git a/src/widget/fields/KexiFieldListModel.cpp b/src/widget/fields/KexiFieldListModel.cpp index d886ea8ef..775185948 100644 --- a/src/widget/fields/KexiFieldListModel.cpp +++ b/src/widget/fields/KexiFieldListModel.cpp @@ -1,200 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2010 Adam Pigg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KexiFieldListModel.h" #include "KexiFieldListModelItem.h" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiFieldListModel::Private { public: Private(); ~Private(); KDbTableOrQuerySchema* schema; KexiFieldListOptions options; KexiFieldListModelItem *allColumnsItem; QList items; }; KexiFieldListModel::Private::Private() : schema(0), allColumnsItem(0) { } KexiFieldListModel::Private::~Private() { qDeleteAll(items); } KexiFieldListModel::KexiFieldListModel(QObject* parent, KexiFieldListOptions options): QAbstractTableModel(parent) , d(new Private()) { d->options = options; } KexiFieldListModel::~KexiFieldListModel() { delete d; } -void KexiFieldListModel::setSchema(KDbTableOrQuerySchema* schema) +void KexiFieldListModel::setSchema(KDbConnection *conn, KDbTableOrQuerySchema* schema) { if (schema && d->schema == schema) return; delete d->schema; d->schema = schema; if (!d->schema) return; qDeleteAll(d->items); d->items.clear(); KexiFieldListModelItem *item = 0; - KDbQueryColumnInfo::Vector columns = d->schema->columns(true /*unique*/); + KDbQueryColumnInfo::Vector columns = d->schema->columns(conn, KDbTableOrQuerySchema::ColumnsMode::Unique); const int count = columns.count(); for (int i = -2; i < count; i++) { KDbQueryColumnInfo *colinfo = 0; if (i == -2) { if (!(d->options & ShowEmptyItem)) continue; item = new KexiFieldListModelItem(QString(), QString(), false); } else if (i == -1) { if (!(d->options & ShowAsterisk)) continue; item = new KexiFieldListModelItem("*", "", false); d->allColumnsItem = item; } else { colinfo = columns[i]; item = new KexiFieldListModelItem( colinfo->aliasOrName(), colinfo->field()->typeName(), (colinfo->field()->isPrimaryKey() || colinfo->field()->isUniqueKey())); item->setCaption(colinfo->captionOrAliasOrName()); } d->items.append(item); //qDebug() << item->data(0); } } QVariant KexiFieldListModel::data(const QModelIndex& index, int role) const { KexiFieldListModelItem *item = 0; if (index.isValid() && index.row() < d->items.count()) { item = d->items[index.row()]; } if (item) { if (role == Qt::DisplayRole) { return item->data(index.column()); } else if (role == Qt::DecorationRole) { if (index.column() == 0) { // icon for field name only return item->icon(); } else { return QVariant(); } } else if (role == Qt::UserRole) { //qDebug() << item->caption(); return item->caption(); } } return QVariant(); } int KexiFieldListModel::columnCount(const QModelIndex& /*parent*/) const { return (d->options & ShowDataTypes) ? 2 : 1; } int KexiFieldListModel::rowCount(const QModelIndex& /*parent*/) const { return d->items.count(); } QVariant KexiFieldListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { if (section == 0) { return xi18n("Field Name"); } else if (section == 1) { return xi18n("Data Type"); } } return QVariant(); } QStringList KexiFieldListModel::mimeTypes() const { QStringList types; types << "kexi/fields"; return types; } QMimeData* KexiFieldListModel::mimeData(const QModelIndexList& indexes) const { if (!d->schema) { return new QMimeData(); } QString sourceMimeType; QString sourceName; QStringList fields; QMimeData *mimedata = new QMimeData(); QByteArray fielddata; QDataStream stream1(&fielddata, QIODevice::WriteOnly); if (d->schema->table()) { sourceMimeType = "kexi/table"; } else if (d->schema->query()) { sourceMimeType = "kexi/query"; } sourceName = d->schema->name(); foreach (const QModelIndex &idx, indexes) { fields << data(idx, Qt::DisplayRole).toString(); } stream1 << sourceMimeType << sourceName << fields; mimedata->setData("kexi/fields", fielddata); return mimedata; } Qt::ItemFlags KexiFieldListModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index); if (index.isValid()) return d->items[index.row()]->flags()| defaultFlags; else return defaultFlags; } diff --git a/src/widget/fields/KexiFieldListModel.h b/src/widget/fields/KexiFieldListModel.h index 8184b3c3e..4bc866278 100644 --- a/src/widget/fields/KexiFieldListModel.h +++ b/src/widget/fields/KexiFieldListModel.h @@ -1,67 +1,68 @@ /* This file is part of the KDE project Copyright (C) 2010 Adam Pigg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KEXIFIELDLISTMODEL_H #define KEXIFIELDLISTMODEL_H #include "kexiextwidgets_export.h" #include +class KDbConnection; class KDbTableOrQuerySchema; //! Flags used to alter models behaviour and appearance enum KexiFieldListOption { ShowDataTypes = 1, //!< if set, 'data type' column is added ShowAsterisk = 2, //!< if set, asterisk ('*') item is prepended to the list AllowMultiSelection = 4, //!< if set, multiple selection is allowed ShowEmptyItem = 8 //!< if set, an empty item is prepended to the list }; Q_DECLARE_FLAGS(KexiFieldListOptions, KexiFieldListOption); Q_DECLARE_OPERATORS_FOR_FLAGS ( KexiFieldListOptions ) Q_FLAGS(KexiFieldListOption); class KEXIEXTWIDGETS_EXPORT KexiFieldListModel : public QAbstractTableModel { Q_OBJECT public: explicit KexiFieldListModel(QObject* parent = 0, KexiFieldListOptions options = ShowDataTypes | AllowMultiSelection); virtual ~KexiFieldListModel(); /*! Sets table or query schema \a schema. The schema object will be owned by the KexiFieldListView object. */ - void setSchema(KDbTableOrQuerySchema* schema); + void setSchema(KDbConnection *conn, KDbTableOrQuerySchema* schema); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual QStringList mimeTypes() const; virtual QMimeData* mimeData(const QModelIndexList& indexes) const; virtual Qt::ItemFlags flags(const QModelIndex& index) const; private: class Private; Private * const d; }; #endif // KEXIFIELDLISTMODEL_H diff --git a/src/widget/fields/KexiFieldListView.cpp b/src/widget/fields/KexiFieldListView.cpp index 3605cacf4..f7cb04ee2 100644 --- a/src/widget/fields/KexiFieldListView.cpp +++ b/src/widget/fields/KexiFieldListView.cpp @@ -1,125 +1,125 @@ /* This file is part of the KDE project Copyright (C) 2003-2005 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 "KexiFieldListView.h" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiFieldListView::Private { public: explicit Private(KexiFieldListOptions options_) : schema(0) , model(0) , options(options_) { } ~Private() { delete schema; } KDbTableOrQuerySchema* schema; KexiFieldListModel *model; KexiFieldListOptions options; }; KexiFieldListView::KexiFieldListView(QWidget *parent, KexiFieldListOptions options) : QListView(parent) , d(new Private(options)) { setAcceptDrops(true); viewport()->setAcceptDrops(true); setDragEnabled(true); setDropIndicatorShown(true); setAlternatingRowColors(true); connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotDoubleClicked(QModelIndex))); } KexiFieldListView::~KexiFieldListView() { delete d; } -void KexiFieldListView::setSchema(KDbTableOrQuerySchema* schema) +void KexiFieldListView::setSchema(KDbConnection *conn, KDbTableOrQuerySchema* schema) { if (schema && d->schema == schema) return; delete d->schema; d->schema = schema; if (!d->schema) return; if (!schema->table() && !schema->query()) return; delete d->model; d->model = new KexiFieldListModel(this, d->options); - d->model->setSchema(schema); + d->model->setSchema(conn, schema); setModel(d->model); } KDbTableOrQuerySchema* KexiFieldListView::schema() const { return d->schema; } QStringList KexiFieldListView::selectedFieldNames() const { if (!schema()) return QStringList(); QStringList selectedFields; QModelIndexList idxlist = selectedIndexes(); foreach (const QModelIndex &idx, idxlist) { QString field = model()->data(idx).toString(); if (field.startsWith('*')) { selectedFields.append("*"); } else { selectedFields.append(field); } } return selectedFields; } void KexiFieldListView::slotDoubleClicked(const QModelIndex &idx) { if (schema() && idx.isValid()) { //! @todo what about query fields/aliases? it.current()->text(0) can be not enough emit fieldDoubleClicked(schema()->table() ? "kexi/table" : "kexi/query", schema()->name(), model()->data(idx).toString()); } } diff --git a/src/widget/fields/KexiFieldListView.h b/src/widget/fields/KexiFieldListView.h index 117449348..177c25f6d 100644 --- a/src/widget/fields/KexiFieldListView.h +++ b/src/widget/fields/KexiFieldListView.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2003-2005 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 KEXIFIELDLISTVIEW_H #define KEXIFIELDLISTVIEW_H #include "kexiextwidgets_export.h" #include #include "KexiFieldListModel.h" #include class KDbTableOrQuerySchema; /*! This widget provides a list of fields from a table or query. */ class KEXIEXTWIDGETS_EXPORT KexiFieldListView : public QListView { Q_OBJECT public: KexiFieldListView(QWidget *parent, KexiFieldListOptions options); virtual ~KexiFieldListView(); /*! Sets table or query schema \a schema. The schema object will be owned by the KexiFieldListView object. */ - void setSchema(KDbTableOrQuerySchema* schema); + void setSchema(KDbConnection *conn, KDbTableOrQuerySchema* schema); /*! \return table or query schema schema set for this widget. */ KDbTableOrQuerySchema* schema() const; /*! \return list of selected field names. */ QStringList selectedFieldNames() const; Q_SIGNALS: /*! Emitted when a field is double clicked */ void fieldDoubleClicked(const QString& sourcePartClass, const QString& sourceName, const QString& fieldName); protected Q_SLOTS: void slotDoubleClicked(const QModelIndex &idx); protected: private: class Private; Private * const d; }; #endif diff --git a/src/widget/kexiqueryparameters.cpp b/src/widget/kexiqueryparameters.cpp index 48f96b49c..4e3b9ab43 100644 --- a/src/widget/kexiqueryparameters.cpp +++ b/src/widget/kexiqueryparameters.cpp @@ -1,149 +1,148 @@ /* This file is part of the KDE project Copyright (C) 2006-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 "kexiqueryparameters.h" #include #include "utils/kexidatetimeformatter.h" #include #include #include #include #include #include -//static -QList KexiQueryParameters::getParameters(QWidget *parent, - const KDbDriver &driver, KDbQuerySchema* querySchema, bool *ok) +// static +QList KexiQueryParameters::getParameters(QWidget *parent, KDbConnection *conn, + KDbQuerySchema *querySchema, bool *ok) { Q_ASSERT(ok); Q_ASSERT(querySchema); - Q_UNUSED(driver); *ok = false; - const QList params(querySchema->parameters()); + const QList params(querySchema->parameters(conn)); QList values; const QString caption(xi18nc("@title:window Enter Query Parameter Value", "Enter Parameter Value")); foreach(const KDbQuerySchemaParameter ¶meter, params) { switch (parameter.type()) { case KDbField::Byte: case KDbField::ShortInteger: case KDbField::Integer: case KDbField::BigInteger: { //! @todo problem for ranges in case of BigInteger - will disappear when we remove use of QInputDialog qlonglong minValue, maxValue; //! @todo add support for unsigned parameter here KDb::getLimitsForFieldType(parameter.type(), &minValue, &maxValue); const int result = QInputDialog::getInt(parent, caption, parameter.message(), 0, minValue, maxValue, 1/*step*/, ok); if (!*ok) return QList(); //cancelled values.append(result); break; } case KDbField::Boolean: { QStringList list; list << xi18nc("Boolean True - Yes", "Yes") << xi18nc("Boolean False - No", "No"); const QString result = QInputDialog::getItem(parent, caption, parameter.message(), list, 0/*current*/, false /* !editable*/, ok); if (!*ok || result.isEmpty()) return QList(); //cancelled values.append(result == list.first()); break; } case KDbField::Date: { //! @todo KEXI3 re-add the KexiDateFormatter's inputMask for QInputDialog KexiDateFormatter df; const QString result = QInputDialog::getText(parent, caption, parameter.message(), QLineEdit::Normal, QString(), ok); //! @todo KEXI3 add time validator // 0/*validator*/, df.inputMask()); if (!*ok) return QList(); //cancelled values.append(df.fromString(result)); break; } case KDbField::DateTime: { //! @todo KEXI3 re-add the KexiDateTimeFormatter's inputMask for QInputDialog KexiDateFormatter df; KexiTimeFormatter tf; const QString result = QInputDialog::getText(parent, caption, parameter.message(), QLineEdit::Normal, QString(), ok); //! @todo KEXI3 add date time validator // 0/*validator*/, KexiDateTimeFormatter::inputMask(df, tf)); if (!*ok) return QList(); //cancelled values.append(KexiDateTimeFormatter::fromString(df, tf, result)); break; } case KDbField::Time: { //! @todo KEXI3 re-add the KexiTimeFormatter's inputMask for QInputDialog KexiTimeFormatter tf; const QString result = QInputDialog::getText(parent, caption, parameter.message(), QLineEdit::Normal, QString(), ok); //! @todo add validator // 0/*validator*/, tf.inputMask()); if (!*ok) return QList(); //cancelled values.append(tf.fromString(result)); break; } case KDbField::Float: case KDbField::Double: { // QInputDialog::getDouble() does not work well, use getText and double validator //! @todo KEXI3 re-add double validator // QDoubleValidator validator(0); const QString textResult = QInputDialog::getText(parent, caption, parameter.message(), QLineEdit::Normal, QString(), ok); if (!*ok || textResult.isEmpty()) return QList(); //cancelled //! @todo this value will be still rounded: consider storing them as a decimal type //! (e.g. using a special qint64+decimalplace class) const double result = textResult.toDouble(ok); //this is also good for float (to avoid rounding) if (!*ok) return QList(); values.append(result); break; } case KDbField::Text: case KDbField::LongText: { const QString result = QInputDialog::getText(parent, caption, parameter.message(), QLineEdit::Normal, QString(), ok); if (!*ok) return QList(); //cancelled values.append(result); break; } case KDbField::BLOB: { //! @todo BLOB input unsupported values.append(QByteArray()); break; } default: qWarning() << "unsupported type " << KDbField::typeName(parameter.type()) << "for parameter \"" << parameter.message() << "\" - aborting query execution!"; return QList(); } } *ok = true; return values; } diff --git a/src/widget/kexiqueryparameters.h b/src/widget/kexiqueryparameters.h index be79b3c05..661bad802 100644 --- a/src/widget/kexiqueryparameters.h +++ b/src/widget/kexiqueryparameters.h @@ -1,46 +1,46 @@ /* This file is part of the KDE project Copyright (C) 2006-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. */ #ifndef KEXIQUERYPARAMETERS_H #define KEXIQUERYPARAMETERS_H #include "kexiextwidgets_export.h" #include //! @short Utilities providing GUI for getting query parameters class KEXIEXTWIDGETS_EXPORT KexiQueryParameters { public: /*! Asks for query parameters using a QInputDialog, one dialog per query parameter (see @ref KDbQuerySchema::parameters()). The type of each dialog depends on the type of query parameter. \return list of values obtained from the user \a ok is set to true on success and to false on failure. */ //! @todo do not use QInputDialog - switch to more powerful custom dialog //! @todo offer option to display one dialog (form) with all the parameters //! @todo support more types (using validators) //! @todo support defaults //! @todo support validation rules, e.g. min/max value, unsigned //! @todo support Enum type (list of strings, need support for keys and user-visible strings) - static QList getParameters(QWidget *parent, const KDbDriver &driver, - KDbQuerySchema* querySchema, bool *ok); + static QList getParameters(QWidget *parent, KDbConnection *conn, + KDbQuerySchema *querySchema, bool *ok); }; #endif // KEXIDBCONNECTIONWIDGET_H diff --git a/src/widget/relations/KexiRelationsScrollArea.cpp b/src/widget/relations/KexiRelationsScrollArea.cpp index 46c288bb0..807663c8b 100644 --- a/src/widget/relations/KexiRelationsScrollArea.cpp +++ b/src/widget/relations/KexiRelationsScrollArea.cpp @@ -1,616 +1,621 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003-2007 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 "KexiRelationsScrollArea.h" #include "KexiRelationsView.h" #include "KexiRelationsConnection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiRelationsScrollArea::Private { public: Private() : readOnly(false) , selectedConnection(0) , focusedTableContainer(0) { autoScrollTimer.setSingleShot(true); } + KDbConnection *connection = nullptr; QWidget *areaWidget; TablesHash tables; bool readOnly; QSet relationsConnections; KexiRelationsConnection* selectedConnection; QPointer focusedTableContainer; QPointer movedTableContainer; QTimer autoScrollTimer; }; //------------- //! @internal scroll area widget that draws connections class KexiRelationsScrollAreaWidget : public QWidget { Q_OBJECT public: explicit KexiRelationsScrollAreaWidget(KexiRelationsScrollArea* parent); ~KexiRelationsScrollAreaWidget(); protected: virtual void paintEvent(QPaintEvent *event); virtual void mousePressEvent(QMouseEvent *ev); private: KexiRelationsScrollArea *scrollArea() { return static_cast(parentWidget()->parentWidget()); } }; KexiRelationsScrollAreaWidget::KexiRelationsScrollAreaWidget( KexiRelationsScrollArea* parent) : QWidget(parent) { setAutoFillBackground(true); setBackgroundRole(QPalette::Mid); resize(10240, 7680); } KexiRelationsScrollAreaWidget::~KexiRelationsScrollAreaWidget() { } void KexiRelationsScrollAreaWidget::paintEvent(QPaintEvent *event) { scrollArea()->handlePaintEvent(event); } void KexiRelationsScrollAreaWidget::mousePressEvent(QMouseEvent *ev) { scrollArea()->handleMousePressEvent(ev); QWidget::mousePressEvent(ev); } //------------- KexiRelationsScrollArea::KexiRelationsScrollArea(QWidget *parent) : QScrollArea(parent) , d(new Private) { d->areaWidget = new KexiRelationsScrollAreaWidget(this); setWidget(d->areaWidget); setFocusPolicy(Qt::WheelFocus); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); #if 0 d->removeSelectedTableQueryAction = new QAction(xi18n("&Hide Selected Table/Query"), "edit-delete", "", this, SLOT(removeSelectedTableQuery()), parent->actionCollection(), "relationsview_removeSelectedTableQuery"); d->removeSelectedConnectionAction = new QAction(xi18n("&Remove Selected Relationship"), "dialog-cancel", "", this, SLOT(removeSelectedConnection()), parent->actionCollection(), "relationsview_removeSelectedConnection"); d->openSelectedTableQueryAction = new QAction(xi18n("&Open Selected Table/Query"), "", "", this, SLOT(openSelectedTableQuery()), 0/*parent->actionCollection()*/, "relationsview_openSelectedTableQuery"); #endif #if 0 d->popup = new QMenu(this, "popup"); d->openSelectedTableQueryAction->plug(d->popup); d->removeSelectedTableQueryAction->plug(d->popup); d->removeSelectedConnectionAction->plug(d->popup); invalidateActions(); #endif setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); //true); connect(&d->autoScrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScrollTimeout())); } KexiRelationsScrollArea::~KexiRelationsScrollArea() { clearSelection(); //sanity qDeleteAll(d->relationsConnections); d->relationsConnections.clear(); delete d; } KexiRelationsTableContainer * KexiRelationsScrollArea::tableContainer(KDbTableSchema *t) const { return t ? d->tables.value(t->name()) : 0; } KexiRelationsTableContainer* KexiRelationsScrollArea::addTableContainer(KDbTableSchema *t, const QRect &rect) { - if (!t) + if (!t || !d->connection) return 0; qDebug() << t->name(); KexiRelationsTableContainer* c = tableContainer(t); if (c) { qWarning() << "table already added"; return c; } - c = new KexiRelationsTableContainer(d->areaWidget, this, + c = new KexiRelationsTableContainer(this, d->connection, /*! @todo what about query? */ - new KDbTableOrQuerySchema(t) - ); + new KDbTableOrQuerySchema(t), d->areaWidget); connect(c, SIGNAL(endDrag()), this, SLOT(slotTableViewEndDrag())); connect(c, SIGNAL(gotFocus()), this, SLOT(slotTableViewGotFocus())); connect(c, SIGNAL(contextMenuRequest(QPoint)), this, SIGNAL(tableContextMenuRequest(QPoint))); if (rect.isValid()) {//predefined size QSize finalSize = c->size().expandedTo(c->sizeHint()); QRect r = rect; r.setSize(finalSize + QSize(0, 10)); c->move(rect.left(), rect.top()); //we're doing this instead of setGeometry(rect) //because the geomenty might be saved on other system with bigger fonts :) c->resize(c->sizeHint()); } else { c->move(100, 100); } updateGeometry(); if (!rect.isValid()) { c->updateGeometry(); c->resize(c->sizeHint()); } int x, y; if (d->tables.count() > 0) { int place = -10; foreach(KexiRelationsTableContainer* container, d->tables) { int right = container->x() + container->width(); if (right > place) place = right; } x = place + 30; } else { x = 5; } y = 5; if (!rect.isValid()) { c->move(x, y); } d->tables.insert(t->name(), c); connect(c, SIGNAL(moved(KexiRelationsTableContainer*)), this, SLOT(containerMoved(KexiRelationsTableContainer*))); c->show(); if (hasFocus()) //ok? c->setFocus(); return c; } void KexiRelationsScrollArea::addConnection(const SourceConnection& _conn) { SourceConnection conn = _conn; KexiRelationsTableContainer *master = d->tables[conn.masterTable]; KexiRelationsTableContainer *details = d->tables[conn.detailsTable]; if (!master || !details) return; /*! @todo what about query? */ KDbTableSchema *masterTable = master->schema()->table(); /*! @todo what about query? */ KDbTableSchema *detailsTable = details->schema()->table(); if (!masterTable || !detailsTable) return; // ok, but we need to know where is the 'master' and where is the 'details' side: KDbField *masterFld = masterTable->field(conn.masterField); KDbField *detailsFld = detailsTable->field(conn.detailsField); if (!masterFld || !detailsFld) return; if (!masterFld->isUniqueKey()) { if (detailsFld->isUniqueKey()) { //SWAP: KDbField *tmpFld = masterFld; masterFld = detailsFld; detailsFld = tmpFld; KDbTableSchema *tmpTable = masterTable; masterTable = detailsTable; detailsTable = tmpTable; KexiRelationsTableContainer *tmp = master; master = details; details = tmp; QString tmp_masterTable = conn.masterTable; conn.masterTable = conn.detailsTable; conn.detailsTable = tmp_masterTable; QString tmp_masterField = conn.masterField; conn.masterField = conn.detailsField; conn.detailsField = tmp_masterField; } } // qDebug() << "finalSRC =" << d->tables[conn.srcTable]; KexiRelationsConnection *connView = new KexiRelationsConnection(master, details, conn, this); d->relationsConnections.insert(connView); qDebug() << "connView->connectionRect() " << connView->connectionRect(); d->areaWidget->update(); /*! @todo will be moved up to relation/query part as this is only visual class KDbTableSchema *mtable = d->conn->tableSchema(conn.srcTable); KDbTableSchema *ftable = d->conn->tableSchema(conn.rcvTable); KDbIndexSchema *forign = new KDbIndexSchema; ftable->addIndex(forign); forign->addField(mtable->field(conn.srcField)); new KDbReference(forign, mtable->primaryKey()); */ #if 0 if (!interactive) { qDebug() << "adding self"; RelationList l = d->relation->projectRelations(); l.append(conn); d->relation->updateRelationList(this, l); } #endif } void KexiRelationsScrollArea::containerMoved(KexiRelationsTableContainer *c) { d->movedTableContainer = c; QRect r; foreach(KexiRelationsConnection* cview, d->relationsConnections) { //! @todo optimize if (cview->masterTable() == c || cview->detailsTable() == c || cview->connectionRect().intersects(r)) { r |= cview->oldRect(); //qDebug() << r; r |= cview->connectionRect(); //qDebug() << r; } } //! @todo optimize! //scroll if needed: if (horizontalScrollBar()->maximum() > c->geometry().right()) { bool beyondBorder = c->geometry().left() > (horizontalScrollBar()->value() + width() - verticalScrollBar()->width()); if (!d->autoScrollTimer.isActive()) { if (beyondBorder) { d->autoScrollTimer.setInterval(200); d->autoScrollTimer.start(); // horizontalScrollBar()->setValue( horizontalScrollBar()->value() + 50 ); } } else { if (!beyondBorder) { d->autoScrollTimer.stop(); } } } else d->autoScrollTimer.stop(); d->areaWidget->update(); emit tablePositionChanged(c); } void KexiRelationsScrollArea::slotAutoScrollTimeout() { int delay = 100; if (d->movedTableContainer) { delay = qMin( 100, 100 - (d->movedTableContainer->geometry().left() - (horizontalScrollBar()->value() + width() - verticalScrollBar()->width())) ); if (delay < 0) delay = 0; delay = delay * delay / 100; qDebug() << delay; int add = 16; if (horizontalScrollBar()->maximum() > (d->movedTableContainer->geometry().right() + add)) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + add); d->movedTableContainer->move(d->movedTableContainer->x() + add, d->movedTableContainer->y()); } } d->areaWidget->update(); if (d->movedTableContainer) { d->autoScrollTimer.setInterval(delay); d->autoScrollTimer.start(); } } void KexiRelationsScrollArea::setReadOnly(bool b) { d->readOnly = b; //! @todo } void KexiRelationsScrollArea::slotListUpdate(QObject *) { #if 0 if (s != this) { d->connectionViews.clear(); RelationList rl = d->relation->projectRelations(); if (!rl.isEmpty()) { RelationList::ConstIterator it, end(rl.constEnd()); for (it = rl.begin(); it != end; ++it) { addConnection((*it), true); } } } updateContents(); #endif } void KexiRelationsScrollArea::handleMousePressEvent(QMouseEvent *ev) { foreach(KexiRelationsConnection* cview, d->relationsConnections) { if (!cview->matchesPoint(ev->pos(), 3)) continue; clearSelection(); setFocus(); cview->setSelected(true); d->areaWidget->update(cview->connectionRect()); d->selectedConnection = cview; emit connectionViewGotFocus(); if (ev->button() == Qt::RightButton) {//show popup qDebug() << "context"; emit connectionContextMenuRequest(ev->globalPos()); } return; } //connection not found clearSelection(); if (ev->button() == Qt::RightButton) {//show popup on view background area emit emptyAreaContextMenuRequest(ev->globalPos()); } else { emit emptyAreaGotFocus(); } setFocus(); } void KexiRelationsScrollArea::handlePaintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter p(d->areaWidget); p.setWindow( horizontalScrollBar() ? horizontalScrollBar()->value() : 0, verticalScrollBar() ? verticalScrollBar()->value() : 0, d->areaWidget->width(), d->areaWidget->height()); QRect clipping( horizontalScrollBar() ? horizontalScrollBar()->value() : 0, verticalScrollBar() ? verticalScrollBar()->value() : 0, width(), height()); foreach(KexiRelationsConnection *cview, d->relationsConnections) { cview->drawConnection(&p); } } void KexiRelationsScrollArea::clearSelection() { if (d->focusedTableContainer) { d->focusedTableContainer->unsetFocus(); d->focusedTableContainer = 0; } if (d->selectedConnection) { d->selectedConnection->setSelected(false); d->areaWidget->update(d->selectedConnection->connectionRect()); d->selectedConnection = 0; } } void KexiRelationsScrollArea::contextMenuEvent(QContextMenuEvent* event) { Q_UNUSED(event); if (d->selectedConnection) { emit connectionContextMenuRequest( mapToGlobal(d->selectedConnection->connectionRect().center())); } } void KexiRelationsScrollArea::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Delete) { removeSelectedObject(); } } void KexiRelationsScrollArea::slotTableViewEndDrag() { qDebug() << "END DRAG!"; d->autoScrollTimer.stop(); } void KexiRelationsScrollArea::removeSelectedObject() { if (d->selectedConnection) { KexiRelationsConnection* tmp = d->selectedConnection; d->selectedConnection = 0; removeConnection(tmp); #if 0 RelationList l = d->relation->projectRelations(); RelationList nl; for (RelationList::Iterator it = l.begin(); it != l.end(); ++it) { if ((*it).srcTable == d->selectedConnection->connection().srcTable && (*it).rcvTable == d->selectedConnection->connection().rcvTable && (*it).srcField == d->selectedConnection->connection().srcField && (*it).rcvField == d->selectedConnection->connection().rcvField) { qDebug() << "matching found!"; } else { nl.append(*it); } } d->relation->updateRelationList(this, nl); #endif } else if (d->focusedTableContainer) { KexiRelationsTableContainer *tmp = d->focusedTableContainer; d->focusedTableContainer = 0; hideTable(tmp); } } +void KexiRelationsScrollArea::setConnection(KDbConnection *conn) +{ + d->connection = conn; +} + void KexiRelationsScrollArea::hideTable(KexiRelationsTableContainer* container) { Q_ASSERT(container); /*! @todo what about query? */ TablesHashMutableIterator it(d->tables); if (!it.findNext(container)) return; hideTableInternal(&it); } void KexiRelationsScrollArea::hideTableInternal(TablesHashMutableIterator* it) { Q_ASSERT(it); KexiRelationsTableContainer* container = it->value(); KDbTableSchema *ts = container->schema()->table(); //for all connections: find and remove all connected with this table for (ConnectionSetMutableIterator itConn(d->relationsConnections);itConn.hasNext();) { KexiRelationsConnection* conn = itConn.next(); if (conn->masterTable() == container || conn->detailsTable() == container) { //remove this removeConnectionInternal(&itConn); } } it->remove(); container->deleteLater(); emit tableHidden(ts); } void KexiRelationsScrollArea::hideAllTablesExcept(QList* tables) { Q_ASSERT(tables); //! @todo what about queries? for (TablesHashMutableIterator it(d->tables); it.hasNext();) { it.next(); KDbTableSchema *table = it.value()->schema()->table(); if (!table || tables->contains(table)) continue; hideTableInternal(&it); } } void KexiRelationsScrollArea::removeConnection(KexiRelationsConnection *conn) { Q_ASSERT(conn); ConnectionSetMutableIterator it(d->relationsConnections); if (!it.findNext(conn)) return; removeConnectionInternal(&it); } void KexiRelationsScrollArea::removeConnectionInternal(ConnectionSetMutableIterator* it) { Q_ASSERT(it); KexiRelationsConnection *conn = it->value(); emit aboutConnectionRemove(conn); it->remove(); d->areaWidget->update(conn->connectionRect()); delete conn; } void KexiRelationsScrollArea::slotTableViewGotFocus() { if (d->focusedTableContainer == sender()) return; qDebug() << "GOT FOCUS!"; clearSelection(); d->focusedTableContainer = (KexiRelationsTableContainer*)sender(); emit tableViewGotFocus(); } QSize KexiRelationsScrollArea::sizeHint() const { return QSize(QScrollArea::sizeHint()); } void KexiRelationsScrollArea::clear() { removeAllConnections(); qDeleteAll(d->tables); d->tables.clear(); d->areaWidget->update(); } void KexiRelationsScrollArea::removeAllConnections() { clearSelection(); //sanity qDeleteAll(d->relationsConnections); d->relationsConnections.clear(); d->areaWidget->update(); } TablesHash* KexiRelationsScrollArea::tables() const { return &d->tables; } KexiRelationsConnection* KexiRelationsScrollArea::selectedConnection() const { return d->selectedConnection; } KexiRelationsTableContainer* KexiRelationsScrollArea::focusedTableContainer() const { return d->focusedTableContainer; } const QSet* KexiRelationsScrollArea::relationsConnections() const { return &d->relationsConnections; } #include "KexiRelationsScrollArea.moc" diff --git a/src/widget/relations/KexiRelationsScrollArea.h b/src/widget/relations/KexiRelationsScrollArea.h index b713b4c32..e9d467e1f 100644 --- a/src/widget/relations/KexiRelationsScrollArea.h +++ b/src/widget/relations/KexiRelationsScrollArea.h @@ -1,156 +1,158 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003-2007 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 KEXIRELATIONSSCROLLAREA_H #define KEXIRELATIONSSCROLLAREA_H #include #include #include #include #include #include #include #include #include "KexiRelationsConnection.h" #include "KexiRelationsTableContainer.h" class KDbConnection; typedef QHash TablesHash; typedef QMutableHashIterator TablesHashMutableIterator; typedef QHash::ConstIterator TablesHashConstIterator; typedef QMutableSetIterator ConnectionSetMutableIterator; typedef QSet::ConstIterator ConnectionSetIterator; //! A data structure describing connection struct SourceConnection { QString masterTable; QString detailsTable; QString masterField; QString detailsField; }; /*! @short Provides a view for displaying relations between database tables. It is currently used for two purposes: - displaying global database relations - displaying relations defined for a database query The class is for displaying only - retrieving data and updating data on the backend side is implemented in KexiRelationWidget, and more specifically in: Kexi Relation Part and Kexi Query Part. */ class KEXIRELATIONSVIEW_EXPORT KexiRelationsScrollArea : public QScrollArea { Q_OBJECT public: explicit KexiRelationsScrollArea(QWidget *parent = 0); virtual ~KexiRelationsScrollArea(); //! \return a hash of added tables TablesHash* tables() const; /*! Adds a table \a t to the area. This changes only visual representation. If \a rect is valid, table widget geometry will be initialized. \return added table container or 0 on failure. */ KexiRelationsTableContainer* addTableContainer(KDbTableSchema *t, const QRect &rect = QRect()); /*! \return table container for table \a t. */ KexiRelationsTableContainer * tableContainer(KDbTableSchema *t) const; //! Adds a connection \a _conn to the area. This changes only visual representation. void addConnection(const SourceConnection& _conn); void setReadOnly(bool); KexiRelationsConnection* selectedConnection() const; KexiRelationsTableContainer* focusedTableContainer() const; virtual QSize sizeHint() const; const QSet* relationsConnections() const; //! @internal Handles mouse press event for area widget void handleMousePressEvent(QMouseEvent *ev); //! @internal Handles paint event for area widget void handlePaintEvent(QPaintEvent *event); Q_SIGNALS: void tableContextMenuRequest(const QPoint& pos); void connectionContextMenuRequest(const QPoint& pos); void emptyAreaContextMenuRequest(const QPoint& pos); void tableViewGotFocus(); void connectionViewGotFocus(); void emptyAreaGotFocus(); void tableHidden(KDbTableSchema* t); void tablePositionChanged(KexiRelationsTableContainer*); void aboutConnectionRemove(KexiRelationsConnection*); public Q_SLOTS: //! Clears current selection - table/query or connection void clearSelection(); /*! Removes all tables and connections from the view. Does not emit signals like tableHidden(). */ void clear(); /*! Removes all connections from the view. */ void removeAllConnections(); /*! Hides all tables except \a tables. */ void hideAllTablesExcept(QList* tables); //! removes selected table or connection void removeSelectedObject(); + void setConnection(KDbConnection *conn); + protected Q_SLOTS: void containerMoved(KexiRelationsTableContainer *c); void slotListUpdate(QObject *s); void slotTableViewEndDrag(); void slotTableViewGotFocus(); void slotAutoScrollTimeout(); protected: void contentsMousePressEvent(QMouseEvent *ev); virtual void keyPressEvent(QKeyEvent *ev); virtual void contextMenuEvent(QContextMenuEvent* event); void hideTable(KexiRelationsTableContainer* tableView); void removeConnection(KexiRelationsConnection *conn); //! Removes current value of iterator \a it, also deleted the container object. void hideTableInternal(TablesHashMutableIterator* it); //! Removes current value of iterator \a it, also deleted the connection object. void removeConnectionInternal(ConnectionSetMutableIterator* it); private: class Private; Private* const d; }; #endif diff --git a/src/widget/relations/KexiRelationsTableContainer.cpp b/src/widget/relations/KexiRelationsTableContainer.cpp index 38fc1cf35..fc134dba2 100644 --- a/src/widget/relations/KexiRelationsTableContainer.cpp +++ b/src/widget/relations/KexiRelationsTableContainer.cpp @@ -1,178 +1,177 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Lucijan Busch Copyright (C) 2003-2007 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 #include "KexiRelationsTableContainer.h" #include "KexiRelationsTableContainer_p.h" #include "KexiRelationsScrollArea.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiRelationsTableContainer::Private { public: Private() { } KexiRelationViewTableContainerHeader *tableHeader; KexiRelationsTableFieldList *fieldList; KexiRelationsScrollArea *scrollArea; }; //---------------------- -KexiRelationsTableContainer::KexiRelationsTableContainer( - QWidget* parent, - KexiRelationsScrollArea *scrollArea, - KDbTableOrQuerySchema *schema) - : QFrame(parent) - , d(new Private) +KexiRelationsTableContainer::KexiRelationsTableContainer(KexiRelationsScrollArea *scrollArea, + KDbConnection *conn, + KDbTableOrQuerySchema *schema, + QWidget *parent) + : QFrame(parent), d(new Private) { d->scrollArea = scrollArea; setObjectName("KexiRelationsTableContainer"); setVisible(false); // scroll area will show it later setAutoFillBackground(true); setBackgroundRole(QPalette::Window); setFrameStyle(QFrame::WinPanel | QFrame::Raised); QVBoxLayout *lyr = new QVBoxLayout(this); lyr->setContentsMargins(2, 2, 2, 2); lyr->setSpacing(1); d->tableHeader = new KexiRelationViewTableContainerHeader(schema->name(), this); d->tableHeader->unsetFocus(); d->tableHeader->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); lyr->addWidget(d->tableHeader); connect(d->tableHeader, SIGNAL(moved()), this, SLOT(moved())); connect(d->tableHeader, SIGNAL(endDrag()), this, SIGNAL(endDrag())); - d->fieldList = new KexiRelationsTableFieldList(schema, d->scrollArea, this); + d->fieldList = new KexiRelationsTableFieldList(conn, schema, d->scrollArea, this); d->fieldList->setObjectName("KexiRelationsTableFieldList"); d->fieldList->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); d->fieldList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->fieldList->setMaximumSize(d->fieldList->sizeHint()); d->fieldList->setContextMenuPolicy(Qt::CustomContextMenu); lyr->addWidget(d->fieldList); connect(d->fieldList, SIGNAL(tableScrolling()), this, SLOT(moved())); connect(d->fieldList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotContextMenu(QPoint))); connect(d->fieldList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotFieldsDoubleClicked(QModelIndex))); } KexiRelationsTableContainer::~KexiRelationsTableContainer() { delete d; } KDbTableOrQuerySchema* KexiRelationsTableContainer::schema() const { return d->fieldList->schema(); } void KexiRelationsTableContainer::slotContextMenu(const QPoint &p) { emit gotFocus(); emit contextMenuRequest(d->fieldList->mapToGlobal(p)); } void KexiRelationsTableContainer::moved() { // qDebug()<<"finally emitting moved"; emit moved(this); } int KexiRelationsTableContainer::globalY(const QString &field) { // qDebug(); QPoint o(0, d->fieldList->globalY(field) + d->scrollArea->verticalScrollBar()->value()); //d->scrollArea->contentsY()); // qDebug() << "db2"; return d->scrollArea->widget()->mapFromGlobal(o).y(); } void KexiRelationsTableContainer::focusInEvent(QFocusEvent* event) { QFrame::focusInEvent(event); setFocus(); } void KexiRelationsTableContainer::setFocus() { //qDebug() << "SET FOCUS"; //select 1st: //!TODO #if 0 if (d->fieldList->firstChild()) { if (d->fieldList->selectedItems().isEmpty()) d->fieldList->setSelected(d->fieldList->firstChild(), true); } d->tableHeader->setFocus(); d->fieldList->setFocus(); #endif raise(); repaint(); emit gotFocus(); } void KexiRelationsTableContainer::focusOutEvent(QFocusEvent* event) { QFrame::focusOutEvent(event); if (!d->fieldList->hasFocus()) unsetFocus(); } void KexiRelationsTableContainer::unsetFocus() { //qDebug() << "UNSET FOCUS"; d->tableHeader->unsetFocus(); d->fieldList->clearSelection(); clearFocus(); repaint(); } void KexiRelationsTableContainer::slotFieldsDoubleClicked(const QModelIndex &idx) { Q_UNUSED(idx); if (!KexiUtils::objectIsA(sender(), "KexiRelationsTableFieldList")) return; const KexiRelationsTableFieldList* t = static_cast(sender()); emit fieldsDoubleClicked(*t->schema(), t->selectedFieldNames()); } QStringList KexiRelationsTableContainer::selectedFieldNames() const { return d->fieldList->selectedFieldNames(); } diff --git a/src/widget/relations/KexiRelationsTableContainer.h b/src/widget/relations/KexiRelationsTableContainer.h index 3e16c89f7..c18c06510 100644 --- a/src/widget/relations/KexiRelationsTableContainer.h +++ b/src/widget/relations/KexiRelationsTableContainer.h @@ -1,91 +1,89 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Lucijan Busch Copyright (C) 2003-2007 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 KEXIRELATIONSTABLECONTAINER_H #define KEXIRELATIONSTABLECONTAINER_H #include "kexirelationsview_export.h" #include #include #include class KDbTableOrQuerySchema; class KexiRelationsScrollArea; class KexiRelationViewTable; class KexiRelationViewTableContainerHeader; //! @short Provides a frame displaying single table or query in relation view. class KEXIRELATIONSVIEW_EXPORT KexiRelationsTableContainer : public QFrame { Q_OBJECT public: - KexiRelationsTableContainer( - QWidget* parent, - KexiRelationsScrollArea *scrollArea, - KDbTableOrQuerySchema *schema); + KexiRelationsTableContainer(KexiRelationsScrollArea *scrollArea, KDbConnection *conn, + KDbTableOrQuerySchema *schema, QWidget *parent = nullptr); virtual ~KexiRelationsTableContainer(); int globalY(const QString &field); KDbTableOrQuerySchema* schema() const; int right() const { return x() + width() - 1; } int bottom() const { return y() + height() - 1; } /*! \return list of selected field names. */ QStringList selectedFieldNames() const; Q_SIGNALS: void moved(KexiRelationsTableContainer *); void endDrag(); void gotFocus(); void contextMenuRequest(const QPoint& pos); void fieldsDoubleClicked(KDbTableOrQuerySchema& tableOrQuery, const QStringList& fieldNames); public Q_SLOTS: void setFocus(); void unsetFocus(); protected Q_SLOTS: void moved(); void slotContextMenu(const QPoint& p); void slotFieldsDoubleClicked(const QModelIndex &idx); friend class KexiRelationViewTableContainerHeader; protected: virtual void focusInEvent(QFocusEvent* event); virtual void focusOutEvent(QFocusEvent* event); private: class Private; Private* const d; }; #endif diff --git a/src/widget/relations/KexiRelationsTableContainer_p.cpp b/src/widget/relations/KexiRelationsTableContainer_p.cpp index e3ab8361e..717b1eb0b 100644 --- a/src/widget/relations/KexiRelationsTableContainer_p.cpp +++ b/src/widget/relations/KexiRelationsTableContainer_p.cpp @@ -1,313 +1,313 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Lucijan Busch Copyright (C) 2003-2007 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 "KexiRelationsTableContainer_p.h" #include "KexiRelationsScrollArea.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include KexiRelationViewTableContainerHeader::KexiRelationViewTableContainerHeader( const QString& text, QWidget *parent) : QLabel(text, parent), m_dragging(false) { setAutoFillBackground(true); setContentsMargins(2, 2, 2, 2); m_activeBG = KexiUtils::activeTitleColor(); m_activeFG = KexiUtils::activeTextColor(); m_inactiveBG = KexiUtils::inactiveTitleColor(); m_inactiveFG = KexiUtils::inactiveTextColor(); installEventFilter(this); } KexiRelationViewTableContainerHeader::~KexiRelationViewTableContainerHeader() { } void KexiRelationViewTableContainerHeader::setFocus() { QPalette pal(palette()); pal.setColor(QPalette::Window, m_activeBG); pal.setColor(QPalette::WindowText, m_activeFG); setPalette(pal); } void KexiRelationViewTableContainerHeader::unsetFocus() { QPalette pal(palette()); pal.setColor(QPalette::Window, m_inactiveBG); pal.setColor(QPalette::WindowText, m_inactiveFG); setPalette(pal); } bool KexiRelationViewTableContainerHeader::eventFilter(QObject *, QEvent *ev) { if (ev->type() == QEvent::MouseMove) { if (m_dragging) {// && static_cast(ev)->modifiers()==Qt::LeftButton) { int diffX = static_cast(ev)->globalPos().x() - m_grabX; int diffY = static_cast(ev)->globalPos().y() - m_grabY; if ((qAbs(diffX) > 2) || (qAbs(diffY) > 2)) { QPoint newPos = parentWidget()->pos() + QPoint(diffX, diffY); //correct the x position if (newPos.x() < 0) { m_offsetX += newPos.x(); newPos.setX(0); } else if (m_offsetX < 0) { m_offsetX += newPos.x(); if (m_offsetX > 0) { newPos.setX(m_offsetX); m_offsetX = 0; } else newPos.setX(0); } //correct the y position if (newPos.y() < 0) { m_offsetY += newPos.y(); newPos.setY(0); } else if (m_offsetY < 0) { m_offsetY += newPos.y(); if (m_offsetY > 0) { newPos.setY(m_offsetY); m_offsetY = 0; } else newPos.setY(0); } //move and update helpers parentWidget()->move(newPos); m_grabX = static_cast(ev)->globalPos().x(); m_grabY = static_cast(ev)->globalPos().y(); // qDebug()<<"HEADER:emitting moved"; emit moved(); } return true; } } return false; } void KexiRelationViewTableContainerHeader::mousePressEvent(QMouseEvent *ev) { //qDebug(); static_cast(parentWidget())->setFocus(); ev->accept(); if (ev->button() == Qt::LeftButton) { m_dragging = true; m_grabX = ev->globalPos().x(); m_grabY = ev->globalPos().y(); m_offsetX = 0; m_offsetY = 0; setCursor(Qt::SizeAllCursor); return; } if (ev->button() == Qt::RightButton) { emit static_cast(parentWidget()) ->contextMenuRequest(ev->globalPos()); } } void KexiRelationViewTableContainerHeader::mouseReleaseEvent(QMouseEvent *ev) { //qDebug(); if (m_dragging && ev->button() & Qt::LeftButton) { setCursor(Qt::ArrowCursor); m_dragging = false; emit endDrag(); } ev->accept(); } //===================================================================================== -KexiRelationsTableFieldList::KexiRelationsTableFieldList( - KDbTableOrQuerySchema* tableOrQuerySchema, - KexiRelationsScrollArea *scrollArea, QWidget *parent) - : KexiFieldListView(parent, ShowAsterisk) - , m_scrollArea(scrollArea) +KexiRelationsTableFieldList::KexiRelationsTableFieldList(KDbConnection *conn, + KDbTableOrQuerySchema *tableOrQuerySchema, + KexiRelationsScrollArea *scrollArea, + QWidget *parent) + : KexiFieldListView(parent, ShowAsterisk), m_scrollArea(scrollArea) { - setSchema(tableOrQuerySchema); + setSchema(conn, tableOrQuerySchema); setAcceptDrops(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotContentsMoving())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotContentsMoving())); horizontalScrollBar()->installEventFilter(this); verticalScrollBar()->installEventFilter(this); } KexiRelationsTableFieldList::~KexiRelationsTableFieldList() { } QSize KexiRelationsTableFieldList::sizeHint() const { //QFontMetrics fm(fontMetrics()); // qDebug() << schema()->name() << " cw=" << columnWidth(0) + fm.width("i") // << ", " << fm.width(schema()->name()+" "); //! @todo return KexiFieldListView::sizeHint(); } int KexiRelationsTableFieldList::globalY(const QString &item) { QAbstractItemModel *themodel = model(); QModelIndex idx; for (int i = 0; i < themodel->rowCount(); ++i) { idx = themodel->index(i, 0); QVariant data = themodel->data(idx); if (data.toString() == item) { break; } } if (idx.isValid()) { QRect r = this->rectForIndex(idx); int y = r.y() + r.height()/2; //Not sure what this line is supposed to do...is it to check if the item is visible? if (visualRect(idx).y() > viewport()->height()){ y = 0; } else if (y == 0) { y = height(); } return mapToGlobal(QPoint(0, y)).y(); } return -1; } void KexiRelationsTableFieldList::dragEnterEvent(QDragEnterEvent* event) { KexiFieldListView::dragEnterEvent(event); } void KexiRelationsTableFieldList::dragMoveEvent(QDragMoveEvent* event) { QModelIndex receiver = indexAt(event->pos()); if (!receiver.isValid() || !KexiFieldDrag::canDecode(event)) return; QString sourceMimeType; QString srcTable; QStringList srcFields; QString srcField; if (!KexiFieldDrag::decode(event, &sourceMimeType, &srcTable, &srcFields)) { event->ignore(); return; } if (sourceMimeType != "kexi/table" && sourceMimeType == "kexi/query"){ event->ignore(); return; } if (srcFields.count() != 1) { event->ignore(); return; } srcField = srcFields[0]; if (srcTable == schema()->name()) { event->ignore(); return; } QString f = model()->data(receiver, Qt::DisplayRole).toString(); //qDebug() << "Source:" << srcTable << "Dest:" << schema()->name(); if (!srcField.trimmed().startsWith('*') && !f.startsWith('*')) event->acceptProposedAction(); } void KexiRelationsTableFieldList::dropEvent(QDropEvent *event) { QModelIndex idx = indexAt(event->pos()); if (!idx.isValid() || !KexiFieldDrag::canDecode(event)) { event->ignore(); return; } QString sourceMimeType; QString srcTable; QStringList srcFields; QString srcField; if (!KexiFieldDrag::decode(event, &sourceMimeType, &srcTable, &srcFields)) { return; } if (sourceMimeType != "kexi/table" && sourceMimeType == "kexi/query") { return; } if (srcFields.count() != 1) { return; } srcField = srcFields[0]; // qDebug() << "srcfield:" << srcField; QString rcvField = model()->data(idx, Qt::DisplayRole).toString(); SourceConnection s; s.masterTable = srcTable; s.detailsTable = schema()->name(); s.masterField = srcField; s.detailsField = rcvField; m_scrollArea->addConnection(s); //qDebug() << srcTable << ":" << srcField << schema()->name() << ":" << rcvField; event->accept(); } void KexiRelationsTableFieldList::slotContentsMoving() { emit tableScrolling(); } void KexiRelationsTableFieldList::contentsMousePressEvent(QMouseEvent *ev) { Q_UNUSED(ev); // set focus before showing context menu because contents of the menu depend on focused table static_cast(parentWidget())->setFocus(); } bool KexiRelationsTableFieldList::eventFilter(QObject *o, QEvent *ev) { if (o == verticalScrollBar() || o == horizontalScrollBar()) { //qDebug() << ev->type(); } return KexiFieldListView::eventFilter(o, ev); } diff --git a/src/widget/relations/KexiRelationsTableContainer_p.h b/src/widget/relations/KexiRelationsTableContainer_p.h index ec7371533..f0c74829e 100644 --- a/src/widget/relations/KexiRelationsTableContainer_p.h +++ b/src/widget/relations/KexiRelationsTableContainer_p.h @@ -1,96 +1,96 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Lucijan Busch Copyright (C) 2003-2007 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 KexiRelationsTableContainer_P_H #define KexiRelationsTableContainer_P_H #include #include #include #include #include class KexiRelationsScrollArea; class KexiRelationViewTable; class KexiRelationViewTableContainerHeader; class KDbTableOrQuerySchema; //! @internal A field list widget used in table container to show fields class KexiRelationsTableFieldList : public KexiFieldListView { Q_OBJECT public: - KexiRelationsTableFieldList(KDbTableOrQuerySchema* tableOrQuerySchema, - KexiRelationsScrollArea *scrollArea, QWidget *parent = 0); + KexiRelationsTableFieldList(KDbConnection *conn, KDbTableOrQuerySchema* tableOrQuerySchema, + KexiRelationsScrollArea *scrollArea, QWidget *parent = nullptr); virtual ~KexiRelationsTableFieldList(); int globalY(const QString &item); virtual QSize sizeHint() const; Q_SIGNALS: void tableScrolling(); protected Q_SLOTS: void slotContentsMoving(); protected: virtual void dragEnterEvent(QDragEnterEvent* event); virtual void dragMoveEvent(QDragMoveEvent* e); virtual void dropEvent(QDropEvent *e); virtual void contentsMousePressEvent(QMouseEvent * e); virtual bool eventFilter(QObject *o, QEvent *ev); private: KexiRelationsScrollArea *m_scrollArea; }; //! @internal A header widget used in table container class KexiRelationViewTableContainerHeader : public QLabel { Q_OBJECT public: explicit KexiRelationViewTableContainerHeader(const QString& text, QWidget *parent = 0); virtual ~KexiRelationViewTableContainerHeader(); virtual void setFocus(); virtual void unsetFocus(); Q_SIGNALS: void moved(); void endDrag(); protected: bool eventFilter(QObject *obj, QEvent *ev); void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); bool m_dragging; int m_grabX; int m_grabY; int m_offsetX; int m_offsetY; QColor m_activeBG, m_activeFG, m_inactiveBG, m_inactiveFG; }; #endif diff --git a/src/widget/relations/KexiRelationsView.cpp b/src/widget/relations/KexiRelationsView.cpp index 75a5ff518..39479b70a 100644 --- a/src/widget/relations/KexiRelationsView.cpp +++ b/src/widget/relations/KexiRelationsView.cpp @@ -1,481 +1,482 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2007 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 "KexiRelationsView.h" #include #include #include #include #include "KexiRelationsScrollArea.h" #include "KexiRelationsConnection.h" #include #include #include #include #include #include #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KexiRelationsView::Private { public: Private() { } KComboBox *tableCombo; QPushButton *btnAdd; KexiRelationsScrollArea *scrollArea; KDbConnection *conn; QMenu *tableQueryPopup; //!< over table/query QMenu *connectionPopup; //!< over connection QMenu *areaPopup; //!< over outer area QAction *openSelectedTableAction, *designSelectedTableAction, *appendSelectedFieldAction, *appendSelectedFieldsAction, *hideTableAction; }; //--------------- KexiRelationsView::KexiRelationsView(QWidget *parent) : KexiView(parent) , d(new Private) { QWidget *mainWidget = new QWidget(this); QGridLayout *g = new QGridLayout(mainWidget); g->setContentsMargins(0, 0, 0, 0); g->setSpacing(KexiUtils::spacingHint()); QWidget *horWidget = new QWidget(mainWidget); QHBoxLayout *hlyr = new QHBoxLayout(horWidget); hlyr->setContentsMargins(0, 0, 0, 0); g->addWidget(horWidget, 0, 0); d->tableCombo = new KComboBox(horWidget); d->tableCombo->setObjectName("tables_combo"); d->tableCombo->setMinimumWidth(QFontMetrics(font()).width("w")*20); d->tableCombo->setInsertPolicy(QComboBox::NoInsert); d->tableCombo->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); QLabel *lbl = new QLabel(xi18n("Table:"), horWidget); lbl->setBuddy(d->tableCombo); lbl->setIndent(3); hlyr->addWidget(lbl); hlyr->addWidget(d->tableCombo); d->btnAdd = new QPushButton(xi18nc("Insert table/query into relations view", "&Insert"), horWidget); hlyr->addWidget(d->btnAdd); hlyr->addStretch(1); connect(d->btnAdd, SIGNAL(clicked()), this, SLOT(slotAddTable())); d->scrollArea = new KexiRelationsScrollArea(mainWidget); d->scrollArea->setObjectName("scroll_area"); setViewWidget(mainWidget, false/* no focus proxy */); setFocusProxy(d->scrollArea); g->addWidget(d->scrollArea, 1, 0); //actions d->tableQueryPopup = new QMenu(this); d->tableQueryPopup->setObjectName("tableQueryPopup"); connect(d->tableQueryPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu())); d->hideTableAction = plugSharedAction("edit_delete", xi18n("&Hide Table"), d->tableQueryPopup); if (d->hideTableAction) d->hideTableAction->setIcon(QIcon()); d->connectionPopup = new QMenu(this); d->connectionPopup->setObjectName("connectionPopup"); connect(d->connectionPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu())); //! @todo areaPopup d->areaPopup = new QMenu(this); d->areaPopup->setObjectName("areaPopup"); d->appendSelectedFieldAction = new QAction(KexiIcon("add-field"), xi18n("&Append Field"), this); d->appendSelectedFieldAction->setObjectName("relationsview_appendField"); connect(d->appendSelectedFieldAction, SIGNAL(triggered()), this, SLOT(appendSelectedFields())); d->appendSelectedFieldsAction = new QAction(KexiIcon("add-field"), xi18n("&Append Fields"), this); d->appendSelectedFieldsAction->setObjectName("relationsview_appendFields"); connect(d->appendSelectedFieldsAction, SIGNAL(triggered()), this, SLOT(appendSelectedFields())); d->openSelectedTableAction = new QAction(koIcon("document-open"), xi18n("&Open Table"), this); d->openSelectedTableAction->setObjectName("relationsview_openTable"); connect(d->openSelectedTableAction, SIGNAL(triggered()), this, SLOT(openSelectedTable())); d->designSelectedTableAction = new QAction(koIcon("document-properties"), xi18n("&Design Table"), this); connect(d->designSelectedTableAction, SIGNAL(triggered()), this, SLOT(designSelectedTable())); d->designSelectedTableAction->setObjectName("relationsview_designTable"); plugSharedAction("edit_delete", this, SLOT(removeSelectedObject())); connect(d->scrollArea, SIGNAL(tableViewGotFocus()), this, SLOT(tableViewGotFocus())); connect(d->scrollArea, SIGNAL(connectionViewGotFocus()), this, SLOT(connectionViewGotFocus())); connect(d->scrollArea, SIGNAL(emptyAreaGotFocus()), this, SLOT(emptyAreaGotFocus())); connect(d->scrollArea, SIGNAL(tableContextMenuRequest(QPoint)), this, SLOT(tableContextMenuRequest(QPoint))); connect(d->scrollArea, SIGNAL(connectionContextMenuRequest(QPoint)), this, SLOT(connectionContextMenuRequest(QPoint))); connect(d->scrollArea, SIGNAL(tableHidden(KDbTableSchema*)), this, SLOT(slotTableHidden(KDbTableSchema*))); connect(d->scrollArea, SIGNAL(tablePositionChanged(KexiRelationsTableContainer*)), this, SIGNAL(tablePositionChanged(KexiRelationsTableContainer*))); connect(d->scrollArea, SIGNAL(aboutConnectionRemove(KexiRelationsConnection*)), this, SIGNAL(aboutConnectionRemove(KexiRelationsConnection*))); //! @todo #if 0 if (!embedd) { /*todo setContextHelp(xi18n("Relations"), xi18n("To create a relationship simply drag the source field onto the target field. " "An arrowhead is used to show which table is the parent (master) and which table is the child (slave) in the relationship."));*/ } #endif #ifdef TESTING_KexiRelationWidget for (int i = 0;i < (int)d->db->tableNames().count();i++) QTimer::singleShot(100, this, SLOT(slotAddTable())); #endif invalidateActions(); } KexiRelationsView::~KexiRelationsView() { delete d; } TablesHash* KexiRelationsView::tables() const { return d->scrollArea->tables(); } KexiRelationsTableContainer* KexiRelationsView::table(const QString& name) const { return d->scrollArea->tables()->value(name); } const QSet* KexiRelationsView::relationsConnections() const { return d->scrollArea->relationsConnections(); } void KexiRelationsView::slotAddTable() { if (d->tableCombo->currentIndex() == -1) return; const QString tname = d->tableCombo->itemText(d->tableCombo->currentIndex()); KDbTableSchema *t = d->conn->tableSchema(tname); addTable(t); } void KexiRelationsView::addTable(KDbTableSchema *t, const QRect &rect) { if (!t) return; if (!d->scrollArea->tableContainer(t)) { KexiRelationsTableContainer *c = d->scrollArea->addTableContainer(t, rect); qDebug() << "adding table" << t->name(); if (!c) return; connect(c, SIGNAL(fieldsDoubleClicked(KDbTableOrQuerySchema&,QStringList)), this, SIGNAL(appendFields(KDbTableOrQuerySchema&,QStringList))); } const QString tname = t->name().toLower(); const int count = d->tableCombo->count(); int i = 0; for (; i < count; i++) { if (d->tableCombo->itemText(i).toLower() == tname) break; } if (i < count) { int oi = d->tableCombo->currentIndex(); qDebug() << "removing a table from the combo box"; d->tableCombo->removeItem(i); if (d->tableCombo->count() > 0) { if (oi >= d->tableCombo->count()) { oi = d->tableCombo->count() - 1; } d->tableCombo->setCurrentIndex(oi); } else { d->tableCombo->setEnabled(false); d->btnAdd->setEnabled(false); } } emit tableAdded(t); } void KexiRelationsView::addConnection(const SourceConnection& conn) { d->scrollArea->addConnection(conn); } void KexiRelationsView::addTable(const QString& t) { for (int i = 0; i < d->tableCombo->count(); i++) { if (d->tableCombo->itemText(i) == t) { d->tableCombo->setCurrentIndex(i); slotAddTable(); } } } void KexiRelationsView::tableViewGotFocus() { invalidateActions(); } void KexiRelationsView::connectionViewGotFocus() { invalidateActions(); } void KexiRelationsView::emptyAreaGotFocus() { invalidateActions(); } void KexiRelationsView::tableContextMenuRequest(const QPoint& pos) { invalidateActions(); executePopup(pos); } void KexiRelationsView::connectionContextMenuRequest(const QPoint& pos) { invalidateActions(); executePopup(pos); } void KexiRelationsView::emptyAreaContextMenuRequest(const QPoint& /*pos*/) { invalidateActions(); //! @todo } void KexiRelationsView::invalidateActions() { setAvailable("edit_delete", d->scrollArea->selectedConnection() || d->scrollArea->focusedTableContainer()); } void KexiRelationsView::executePopup(QPoint pos) { if (pos == QPoint(-1, -1)) { pos = mapToGlobal( d->scrollArea->focusedTableContainer() ? d->scrollArea->focusedTableContainer()->pos() + d->scrollArea->focusedTableContainer()->rect().center() : rect().center()); } if (d->scrollArea->focusedTableContainer()) d->tableQueryPopup->exec(pos); else if (d->scrollArea->selectedConnection()) d->connectionPopup->exec(pos); } void KexiRelationsView::removeSelectedObject() { d->scrollArea->removeSelectedObject(); } void KexiRelationsView::appendSelectedFields() { KexiRelationsTableContainer* currentTableContainer = d->scrollArea->focusedTableContainer(); if (!currentTableContainer) return; emit appendFields(*currentTableContainer->schema(), currentTableContainer->selectedFieldNames()); } void KexiRelationsView::openSelectedTable() { /*! @todo what about query? */ if (!d->scrollArea->focusedTableContainer() || !d->scrollArea->focusedTableContainer()->schema()->table()) return; bool openingCancelled; KexiMainWindowIface::global()->openObject( "kexi/table", d->scrollArea->focusedTableContainer()->schema()->name(), Kexi::DataViewMode, &openingCancelled); } void KexiRelationsView::designSelectedTable() { /*! @todo what about query? */ if (!d->scrollArea->focusedTableContainer() || !d->scrollArea->focusedTableContainer()->schema()->table()) return; bool openingCancelled; KexiMainWindowIface::global()->openObject( "kexi/table", d->scrollArea->focusedTableContainer()->schema()->name(), Kexi::DesignViewMode, &openingCancelled); } QSize KexiRelationsView::sizeHint() const { return d->scrollArea->sizeHint(); } void KexiRelationsView::slotTableHidden(KDbTableSchema* table) { const QString &t = table->name().toLower(); int i; for (i = 0; i < d->tableCombo->count() && t > d->tableCombo->itemText(i).toLower(); i++) { } d->tableCombo->insertItem(i, table->name()); if (!d->tableCombo->isEnabled()) { d->tableCombo->setCurrentIndex(0); d->tableCombo->setEnabled(true); d->btnAdd->setEnabled(true); } emit tableHidden(table); } void KexiRelationsView::aboutToShowPopupMenu() { KexiRelationsTableContainer* currentTableContainer = d->scrollArea->focusedTableContainer(); if (currentTableContainer /*&& currentTableContainer->schema()->table()*/) { /*! @todo what about query? */ d->tableQueryPopup->clear(); d->tableQueryPopup->addSection(KexiIcon("table"), QString(d->scrollArea->focusedTableContainer()->schema()->name()) + " : " + xi18n("Table")); QStringList selectedFieldNames(currentTableContainer->selectedFieldNames()); if (currentTableContainer && !selectedFieldNames.isEmpty()) { if (selectedFieldNames.count() > 1 || selectedFieldNames.first() == "*") //multiple d->tableQueryPopup->addAction(d->appendSelectedFieldsAction); else d->tableQueryPopup->addAction(d->appendSelectedFieldAction); d->tableQueryPopup->addSeparator(); } d->tableQueryPopup->addAction(d->openSelectedTableAction); d->tableQueryPopup->addAction(d->designSelectedTableAction); d->tableQueryPopup->addSeparator(); d->tableQueryPopup->addAction(d->hideTableAction); } else if (d->scrollArea->selectedConnection()) { unplugSharedAction("edit_delete", d->connectionPopup); d->connectionPopup->clear(); d->connectionPopup->addSection(QIcon(), d->scrollArea->selectedConnection()->toString() + " : " + xi18n("Relationship")); plugSharedAction("edit_delete", d->connectionPopup); } } bool KexiRelationsView::clear() { d->scrollArea->clear(); return setConnection(d->conn); } /*! Removes all coonections from the view. */ void KexiRelationsView::removeAllConnections() { d->scrollArea->removeAllConnections(); } bool KexiRelationsView::setConnection(KDbConnection *conn) { d->tableCombo->clear(); d->conn = conn; if (conn) { bool ok; QStringList result = d->conn->tableNames(false/*no system tables*/, &ok); if (!ok) { return false; } result.sort(); d->tableCombo->addItems(result); } + d->scrollArea->setConnection(conn); return true; } void KexiRelationsView::objectCreated(const QString &mime, const QString& name) { if (mime == "kexi/table" || mime == "kexi/query") { //! @todo query? const int count = d->tableCombo->count(); QString strName(name); int i = 0; for (; i < count && d->tableCombo->itemText(i) <= strName; i++) { } d->tableCombo->insertItem(i, name); } } void KexiRelationsView::objectDeleted(const QString &mime, const QString& name) { if (mime == "kexi/table" || mime == "kexi/query") { for (int i = 0; i < d->tableCombo->count(); i++) { //! @todo query? if (d->tableCombo->itemText(i) == name) { d->tableCombo->removeItem(i); if (d->tableCombo->currentIndex() == i) { if (i == (d->tableCombo->count() - 1)) d->tableCombo->setCurrentIndex(i - 1); else d->tableCombo->setCurrentIndex(i); } break; } } } } void KexiRelationsView::objectRenamed(const QString &mime, const QString& name, const QString& newName) { if (mime == "kexi/table" || mime == "kexi/query") { const int count = d->tableCombo->count(); for (int i = 0; i < count; i++) { //! @todo query? if (d->tableCombo->itemText(i) == name) { d->tableCombo->removeItem(i); int j = 0; for (; j < count && d->tableCombo->itemText(j) <= newName; j++) { } d->tableCombo->insertItem(j, newName); break; } } } } void KexiRelationsView::hideAllTablesExcept(QList* tables) { d->scrollArea->hideAllTablesExcept(tables); } diff --git a/src/widget/tableview/KexiTableScrollArea.cpp b/src/widget/tableview/KexiTableScrollArea.cpp index a7dd5c48b..cce1887f3 100644 --- a/src/widget/tableview/KexiTableScrollArea.cpp +++ b/src/widget/tableview/KexiTableScrollArea.cpp @@ -1,2506 +1,2506 @@ /* 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 //#define KEXITABLEVIEW_COMBO_DEBUG 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*/; QStyleOptionViewItem 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->recordHeight(); 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(); if (d->horizontalHeader->sizeHint().width() > 0) { d->horizontalHeader->setFixedWidth(d->horizontalHeader->sizeHint().width()); } if (d->horizontalHeader->sizeHint().height() > 0) { d->horizontalHeader->setFixedHeight(d->horizontalHeader->sizeHint().height()); } if (d->verticalHeader->sizeHint().width() > 0) { d->verticalHeader->setFixedWidth(d->verticalHeader->sizeHint().width()); } if (d->verticalHeader->sizeHint().height() > 0) { d->verticalHeader->setFixedHeight(d->verticalHeader->sizeHint().height()); } //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); 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(); } KDbOrderByColumn::SortOrder KexiTableScrollArea::currentLocalSortOrder() const { return KDbOrderByColumn::fromQt(d->horizontalHeader->sortIndicatorOrder()); } void KexiTableScrollArea::setLocalSortOrder(int column, KDbOrderByColumn::SortOrder order) { d->horizontalHeader->setSortIndicator(column, KDbOrderByColumn::toQt(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->recordHeight) == recordCount()"; #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 columnReadOnly = isReadOnly() || 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()); + editor->createInternalEditor(m_data->cursor()->connection(), *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() << recordHeight(); 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(), recordHeight() ); 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()); } QSize KexiTableScrollArea::tableSize() const { #ifdef KEXITABLEVIEW_COMBO_DEBUG if (objectName() == "KexiComboBoxPopup_tv") { qDebug() << "rowCount" << recordCount() << "\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->recordHeight */ // 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(recordCount()-1+(isInsertingEnabled()?1:0))" << recordPos(recordCount()-1+(isInsertingEnabled()?1:0)) << "\nd->recordHeight" << d->recordHeight << "\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->recordHeight; 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->recordHeight, columnWidth(col), d->recordHeight, QBrush(Qt::gray)); p.drawRect(width, topMargin - d->recordHeight, columnWidth(col), d->recordHeight); p.drawText(width, topMargin - d->recordHeight, columnWidth(col), d->recordHeight, 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->recordHeight), true); p.restoreWorldMatrix(); xOffset = xOffset + columnWidth(col); right = xOffset; } row++; yOffset = topMargin + row * d->recordHeight; } 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/tableview/kexicomboboxbase.cpp b/src/widget/tableview/kexicomboboxbase.cpp index 2d114baed..ea5a84f77 100644 --- a/src/widget/tableview/kexicomboboxbase.cpp +++ b/src/widget/tableview/kexicomboboxbase.cpp @@ -1,704 +1,704 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson 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. */ #include "kexicomboboxbase.h" #include #include #include "kexicomboboxpopup.h" #include "KexiTableScrollArea.h" #include "kexi.h" #include "KexiTableScrollAreaWidget.h" #include #include #include #include #include #include #include KexiComboBoxBase::KexiComboBoxBase() { m_internalEditorValueChanged = false; //user has text or other value inside editor m_slotInternalEditorValueChanged_enabled = true; m_mouseBtnPressedWhenPopupVisible = false; m_insideCreatePopup = false; m_setValueOrTextInInternalEditor_enabled = true; m_updatePopupSelectionOnShow = true; m_moveCursorToEndInInternalEditor_enabled = true; m_selectAllInInternalEditor_enabled = true; m_setValueInInternalEditor_enabled = true; m_setVisibleValueOnSetValueInternal = false; m_reinstantiatePopupOnShow = false; m_focusPopupBeforeShow = false; } KexiComboBoxBase::~KexiComboBoxBase() { } const KDbTableViewColumn *KexiComboBoxBase::column() const { return const_cast(this)->column(); } KDbLookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() { if (field() && field()->table()) { KDbLookupFieldSchema *lookupFieldSchema = field()->table()->lookupFieldSchema(*field()); if (lookupFieldSchema && !lookupFieldSchema->recordSource().name().isEmpty()) return lookupFieldSchema; } return nullptr; } const KDbLookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const { return const_cast(this)->lookupFieldSchema(); } int KexiComboBoxBase::recordToHighlightForLookupTable() const { if (!popup()) return -1;//err const KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (!lookupFieldSchema) return -1; if (lookupFieldSchema->boundColumn() == -1) return -1; //err bool ok; const int recordUid = origValue().toInt(); //! @todo for now we're assuming the id is INTEGER KDbTableViewData *tvData = popup()->tableView()->data(); const int boundColumn = boundColumnIndex(); if (boundColumn < 0) { return -1; } int record = -1; for (KDbTableViewDataIterator it(tvData->begin()); it != tvData->end(); ++it) { record++; KDbRecordData* data = *it; if (data->at(boundColumn).toInt(&ok) == recordUid && ok) return record; if (!ok) break; } //item not found: highlight 1st record, if available return -1; } void KexiComboBoxBase::setValueInternal(const QVariant& add_, bool removeOld) { Q_UNUSED(removeOld); m_mouseBtnPressedWhenPopupVisible = false; m_updatePopupSelectionOnShow = true; QString add(add_.toString()); if (add.isEmpty()) { const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; QVariant valueToSet; bool hasValueToSet = true; int recordToHighlight = -1; KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (lookupFieldSchema) { //use 'lookup field' model //! @todo support more RowSourceType's, not only table if (lookupFieldSchema->boundColumn() == -1) //! @todo errmsg return; if (m_setVisibleValueOnSetValueInternal) { //only for table views if (!popup()) createPopup(false/*!show*/); } if (popup()) { const int recordToHighlight = recordToHighlightForLookupTable(); popup()->tableView()->setHighlightedRecordNumber(recordToHighlight); const int visibleColumn = visibleColumnIndex(); if (m_setVisibleValueOnSetValueInternal && -1 != visibleColumn) { //only for table views KDbRecordData *data = popup()->tableView()->highlightedRecord(); if (data) valueToSet = data->at(visibleColumn); } else { hasValueToSet = false; } } } else if (relData) { //use 'related table data' model valueToSet = valueForString(origValue().toString(), &recordToHighlight, 0, 1); } else { //use 'enum hints' model const int record = origValue().toInt(); valueToSet = field()->enumHint(record).trimmed(); } if (hasValueToSet) setValueOrTextInInternalEditor(valueToSet); /*impl.*/moveCursorToEndInInternalEditor(); /*impl.*/selectAllInInternalEditor(); if (popup()) { if (origValue().isNull()) { popup()->tableView()->clearSelection(); popup()->tableView()->setHighlightedRecordNumber(0); } else { if (relData) { if (recordToHighlight != -1) popup()->tableView()->setHighlightedRecordNumber(recordToHighlight); } else if (!lookupFieldSchema) { //popup()->tableView()->selectRecord(origValue().toInt()); popup()->tableView()->setHighlightedRecordNumber(origValue().toInt()); } } } } else { //! @todo autocompl.? if (popup()) popup()->tableView()->clearSelection(); /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user! //setLineEditText( add ); /*impl.*/moveCursorToEndInInternalEditor(); } } KDbRecordData* KexiComboBoxBase::selectRecordForEnteredValueInLookupTable(const QVariant& v) { KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (!popup() || !lookupFieldSchema) return 0; //safety //-not effective for large sets: please cache it! //.trimmed() is not generic! const bool valueIsText = v.type() == QVariant::String || v.type() == QVariant::ByteArray; //most common case const QString txt(valueIsText ? v.toString().trimmed() : QString()); KDbTableViewData *lookupData = popup()->tableView()->data(); const int visibleColumn = visibleColumnIndex(); if (-1 == visibleColumn) return 0; KDbTableViewDataConstIterator it(lookupData->constBegin()); int record; for (record = 0;it != lookupData->constEnd();++it, record++) { if (valueIsText) { if ((*it)->at(visibleColumn).toString().trimmed().compare(txt, Qt::CaseInsensitive) == 0) break; } else { if ((*it)->at(visibleColumn) == v) break; } } m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value, // so do not change the internal editor's contents if (it != lookupData->constEnd()) popup()->tableView()->selectRecord(record); else popup()->tableView()->clearSelection(); m_setValueOrTextInInternalEditor_enabled = true; return it != lookupData->constEnd() ? *it : 0; } QString KexiComboBoxBase::valueForString(const QString& str, int* record, int lookInColumn, int returnFromColumn, bool allowNulls) { Q_UNUSED(returnFromColumn); const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; if (!relData) return QString(); //safety //use 'related table data' model //-not effective for large sets: please cache it! //.trimmed() is not generic! const QString txt(str.trimmed()); KDbTableViewDataConstIterator it(relData->constBegin()); for (*record = 0;it != relData->constEnd();++it, (*record)++) { const QString s((*it)->at(lookInColumn).toString()); if (s.trimmed().compare(txt, Qt::CaseInsensitive) == 0) return s; } *record = -1; if (column() && column()->isRelatedDataEditable()) return str; //new value entered and that's allowed qWarning() << "no related record found, ID will be painted!"; if (allowNulls) return QString(); return str; //for sanity but it's weird to show id to the user } int KexiComboBoxBase::boundColumnIndex() const { if (!lookupFieldSchema()) { return -1; } switch (lookupFieldSchema()->recordSource().type()) { case KDbLookupFieldSchemaRecordSource::Table: // When the record source is Table we have hardcoded columns: , return lookupFieldSchema()->visibleColumns().count(); default:; } // When the record source is Query we use the lookup field's bound column index //! @todo Implement for other types return lookupFieldSchema()->boundColumn(); } int KexiComboBoxBase::visibleColumnIndex() const { if (!lookupFieldSchema() || lookupFieldSchema()->visibleColumns().isEmpty()) { return -1; } switch (lookupFieldSchema()->recordSource().type()) { case KDbLookupFieldSchemaRecordSource::Table: // When the record source is Table we have hardcoded columns: , return lookupFieldSchema()->visibleColumn(0); default:; } // When the record source is Query we use the lookup field's visible column index //! @todo Implement for multiple visible columns //! @todo Implement for other types return lookupFieldSchema()->visibleColumns().first(); } QVariant KexiComboBoxBase::value() { const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; KDbLookupFieldSchema *lookupFieldSchema = 0; if (relData) { if (m_internalEditorValueChanged) { //we've user-entered text: look for id //! @todo make error if matching text not found? int recordToHighlight; return valueForString(m_userEnteredValue.toString(), &recordToHighlight, 1, 0, true/*allowNulls*/); } else { //use 'related table data' model KDbRecordData *data = popup() ? popup()->tableView()->selectedRecord() : 0; return data ? data->at(0) : origValue(); } } else if ((lookupFieldSchema = this->lookupFieldSchema())) { if (lookupFieldSchema->boundColumn() == -1) return origValue(); KDbRecordData *data = popup() ? popup()->tableView()->selectedRecord() : 0; if (/*!record &&*/ m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { // //try to select a record using the user-entered text if (!popup()) { QVariant prevUserEnteredValue = m_userEnteredValue; createPopup(false); m_userEnteredValue = prevUserEnteredValue; } data = selectRecordForEnteredValueInLookupTable(m_userEnteredValue); } const int boundColumn = boundColumnIndex(); return (data && boundColumn >= 0) ? data->at(boundColumn) : QVariant(); } else if (popup()) { //use 'enum hints' model const int record = popup()->tableView()->currentRecord(); if (record >= 0) return QVariant(record); } if (valueFromInternalEditor().toString().isEmpty()) return QVariant(); /*! \todo don't return just 1st record, but use autocompletion feature and: show message box if entered text does not match! */ return origValue(); //unchanged } QVariant KexiComboBoxBase::visibleValueForLookupField() { KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (!popup() || !lookupFieldSchema) return QVariant(); const int visibleColumn = visibleColumnIndex(); //qDebug() << "visibleColumn" << visibleColumn; if (-1 == visibleColumn) return QVariant(); KDbRecordData *data = popup()->tableView()->selectedRecord(); return data ? data->at(qMin(visibleColumn, data->count() - 1)/*sanity*/) : QVariant(); } QVariant KexiComboBoxBase::visibleValue() { return m_visibleValue; } void KexiComboBoxBase::clear() { if (popup()) popup()->hide(); slotInternalEditorValueChanged(QVariant()); } tristate KexiComboBoxBase::valueChangedInternal() { //avoid comparing values: const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (relData || lookupFieldSchema) { if (m_internalEditorValueChanged) return true; //use 'related table data' model KDbRecordData *data = popup() ? popup()->tableView()->selectedRecord() : 0; if (!data) return false; } else { //use 'enum hints' model const int record = popup() ? popup()->tableView()->currentRecord() : -1; if (record < 0 && !m_internalEditorValueChanged/*true if text box is cleared*/) return false; } return cancelled; } bool KexiComboBoxBase::valueIsNull() { // bool ok; QVariant v(value()); return v.isNull(); // return !ok || v.isNull(); } bool KexiComboBoxBase::valueIsEmpty() { return valueIsNull(); } void KexiComboBoxBase::showPopup() { //qDebug(); createPopup(true); } void KexiComboBoxBase::createPopup(bool show) { //qDebug() << show << field() << popup() << m_updatePopupSelectionOnShow; if (!field()) return; QWidget* thisWidget = dynamic_cast(this); if (!thisWidget) { return; } QScopedValueRollback insideCreatePopuRollback(m_insideCreatePopup, true); QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget; //qDebug() << "widgetToFocus:" << widgetToFocus; if (m_reinstantiatePopupOnShow) { QWidget *oldPopup = popup(); setPopup(0); delete oldPopup; } if (!popup()) { - setPopup(column() ? new KexiComboBoxPopup(thisWidget, column()) + setPopup(column() ? new KexiComboBoxPopup(thisWidget, connection(), column()) : new KexiComboBoxPopup(thisWidget, field())); QObject::connect(popup(), SIGNAL(recordAccepted(KDbRecordData*,int)), thisWidget, SLOT(slotRecordAccepted(KDbRecordData*,int))); QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KDbRecordData*)), thisWidget, SLOT(slotRecordSelected(KDbRecordData*))); popup()->setFocusProxy(widgetToFocus); popup()->tableView()->setFocusProxy(widgetToFocus); popup()->installEventFilter(thisWidget); if (origValue().isNull()) popup()->tableView()->clearSelection(); else { popup()->tableView()->selectRecord(0); popup()->tableView()->setHighlightedRecordNumber(0); } } if (show && internalEditor() && !internalEditor()->isVisible()) /*emit*/editRequested(); QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos()); if (posMappedToGlobal != QPoint(-1, -1)) { //! todo alter the position to fit the popup within screen boundaries QPoint pos = posMappedToGlobal + QPoint(0, thisWidget->height()); if (qobject_cast(thisWidget->parentWidget())) { KexiTableScrollArea* tableScroll = qobject_cast(thisWidget->parentWidget())->scrollArea; pos -= QPoint(tableScroll->horizontalScrollBar()->value(), tableScroll->verticalScrollBar()->value()); } popup()->hide(); popup()->move(pos); //qDebug() << "pos:" << posMappedToGlobal + QPoint(0, thisWidget->height()); //to avoid flickering: first resize to 0-height, then show and resize back to prev. height int w = popupWidthHint(); popup()->resize(w, 0); if (show) { popup()->show(); //qDebug() << "SHOW!!!"; } popup()->updateSize(w); // make sure the popup fits on the screen const QRect screen = QApplication::desktop()->availableGeometry(posMappedToGlobal); pos -= screen.topLeft(); // to simplify computation w = popup()->width(); int h = popup()->height(); if (screen.width() < w) { w = screen.width(); pos.setX(0); } else if (screen.width() < (pos.x() + w - 1)) { pos.setX(screen.width() - w + 1); } else if (pos.x() < 0) { pos.setX(0); } if (screen.height() < h) { h = screen.height(); pos.setY(0); } else if (screen.height() < (pos.y() + h - 1)) { const int topY = pos.y() - thisWidget->height() - h; if (topY >= 0 && (topY + h - 1 < screen.height())) { pos.setY(pos.y() - thisWidget->height() - h); } else { pos.setY(screen.height() - h + 1); } } else if (pos.y() < 0) { pos.setY(0); } popup()->move(pos + screen.topLeft()); popup()->resize(w, h); if (m_updatePopupSelectionOnShow) { int recordToHighlight = -1; const KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; if (lookupFieldSchema) { recordToHighlight = recordToHighlightForLookupTable(); } else if (relData) { (void)valueForString(origValue().toString(), &recordToHighlight, 0, 1); } else //enum hint recordToHighlight = origValue().toInt(); /*-->*/ m_moveCursorToEndInInternalEditor_enabled = show; m_selectAllInInternalEditor_enabled = show; m_setValueInInternalEditor_enabled = show; if (recordToHighlight == -1) { recordToHighlight = qMax(popup()->tableView()->highlightedRecordNumber(), 0); setValueInInternalEditor(QVariant()); } popup()->tableView()->selectRecord(recordToHighlight); popup()->tableView()->setHighlightedRecordNumber(recordToHighlight); popup()->tableView()->ensureCellVisible(-1, 0); // scroll to left as expected /*-->*/ m_moveCursorToEndInInternalEditor_enabled = true; m_selectAllInInternalEditor_enabled = true; m_setValueInInternalEditor_enabled = true; } } if (show) { moveCursorToEndInInternalEditor(); selectAllInInternalEditor(); if (m_focusPopupBeforeShow) { widgetToFocus->setFocus(); } popup()->show(); popup()->raise(); popup()->repaint(); if (!m_focusPopupBeforeShow) { widgetToFocus->setFocus(); } } } void KexiComboBoxBase::hide() { if (popup()) popup()->hide(); } void KexiComboBoxBase::slotRecordAccepted(KDbRecordData* data, int record) { Q_UNUSED(record); //update our value //..nothing to do? updateButton(); slotRecordSelected(data); /*emit*/acceptRequested(); } void KexiComboBoxBase::acceptPopupSelection() { if (!popup()) return; KDbRecordData *data = popup()->tableView()->highlightedRecord(); if (data) { popup()->tableView()->selectRecord(popup()->tableView()->highlightedRecordNumber()); slotRecordAccepted(data, -1); } popup()->hide(); } void KexiComboBoxBase::slotRecordSelected(KDbRecordData*) { //qDebug() << "m_visibleValue=" << m_visibleValue; QVariant valueToSet; const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant(); if (relData) { //use 'related table data' model KDbRecordData *data = popup()->tableView()->selectedRecord(); if (data) valueToSet = data->at(1); } else if (lookupFieldSchema) { KDbRecordData *data = popup()->tableView()->selectedRecord(); const int visibleColumn = visibleColumnIndex(); if (data && visibleColumn != -1 /* && (int)item->size() >= visibleColumn --already checked*/) { valueToSet = data->at(qMin(visibleColumn, data->count() - 1)/*sanity*/); popup()->tableView()->ensureCellVisible(popup()->tableView()->highlightedRecordNumber(), -1); } } else { //use 'enum hints' model valueToSet = field()->enumHint(popup()->tableView()->currentRecord()); if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) { clear(); QWidget* thisWidget = dynamic_cast(this); if (thisWidget) { thisWidget->parentWidget()->setFocus(); } return; } } setValueOrTextInInternalEditor(valueToSet); QWidget* thisWidget = dynamic_cast(this); thisWidget->setFocus(); if (m_setValueOrTextInInternalEditor_enabled) { moveCursorToEndInInternalEditor(); selectAllInInternalEditor(); } // a new (temp) popup table index is selected: do not update selection next time: m_updatePopupSelectionOnShow = false; } void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v) { if (!m_slotInternalEditorValueChanged_enabled) return; m_userEnteredValue = v; m_internalEditorValueChanged = true; if (v.toString().isEmpty()) { if (popup()) { popup()->tableView()->clearSelection(); } return; } } void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value) { if (!m_setValueOrTextInInternalEditor_enabled) return; setValueInInternalEditor(value); //this text is not entered by hand: m_userEnteredValue = QVariant(); m_internalEditorValueChanged = false; } bool KexiComboBoxBase::handleKeyPressForPopup(QKeyEvent *ke) { const int k = ke->key(); int highlightedOrSelectedRecord = popup() ? popup()->tableView()->highlightedRecordNumber() : -1; if (popup() && highlightedOrSelectedRecord < 0) highlightedOrSelectedRecord = popup()->tableView()->currentRecord(); const bool enterPressed = k == Qt::Key_Enter || k == Qt::Key_Return; // The editor may be active but the pull down menu not existent/visible, // e.g. when the user has pressed a normal button to activate the editor // Don't handle the event here in that case. if (!popup() || (!enterPressed && !popup()->isVisible())) { return false; } switch (k) { case Qt::Key_Up: popup()->tableView()->setHighlightedRecordNumber( qMax(highlightedOrSelectedRecord - 1, 0)); updateTextForHighlightedRecord(); return true; case Qt::Key_Down: popup()->tableView()->setHighlightedRecordNumber( qMin(highlightedOrSelectedRecord + 1, popup()->tableView()->recordCount() - 1)); updateTextForHighlightedRecord(); return true; case Qt::Key_PageUp: popup()->tableView()->setHighlightedRecordNumber( qMax(highlightedOrSelectedRecord - popup()->tableView()->recordsPerPage(), 0)); updateTextForHighlightedRecord(); return true; case Qt::Key_PageDown: popup()->tableView()->setHighlightedRecordNumber( qMin(highlightedOrSelectedRecord + popup()->tableView()->recordsPerPage(), popup()->tableView()->recordCount() - 1)); updateTextForHighlightedRecord(); return true; case Qt::Key_Home: popup()->tableView()->setHighlightedRecordNumber(0); updateTextForHighlightedRecord(); return true; case Qt::Key_End: popup()->tableView()->setHighlightedRecordNumber(popup()->tableView()->recordCount() - 1); updateTextForHighlightedRecord(); return true; case Qt::Key_Enter: case Qt::Key_Return: //accept //select record that is highlighted if (popup()->tableView()->highlightedRecordNumber() >= 0) { popup()->tableView()->selectRecord(popup()->tableView()->highlightedRecordNumber()); acceptPopupSelection(); return true; } default: ; } return false; } void KexiComboBoxBase::updateTextForHighlightedRecord() { KDbRecordData* data = popup() ? popup()->tableView()->highlightedRecord() : 0; if (data) slotRecordSelected(data); } void KexiComboBoxBase::undoChanges() { KDbLookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); if (lookupFieldSchema) { // qDebug() << "m_visibleValue BEFORE=" << m_visibleValue; if (popup()) popup()->tableView()->selectRecord(popup()->tableView()->highlightedRecordNumber()); m_visibleValue = visibleValueForLookupField(); // qDebug() << "m_visibleValue AFTER=" << m_visibleValue; setValueOrTextInInternalEditor(m_visibleValue); } } diff --git a/src/widget/tableview/kexicomboboxbase.h b/src/widget/tableview/kexicomboboxbase.h index d44b21cca..07891c73e 100644 --- a/src/widget/tableview/kexicomboboxbase.h +++ b/src/widget/tableview/kexicomboboxbase.h @@ -1,192 +1,194 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson 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. */ #ifndef _KEXICOMBOBOXBASE_H_ #define _KEXICOMBOBOXBASE_H_ #include "kexidatatable_export.h" #include "kexiinputtableedit.h" #include #include #include class KDbTableViewColumn; class KexiComboBoxPopup; /*! @short A base class for handling data-aware combo boxes. This class is used by KexiComboBoxTableEdit and KexiDBComboBox. */ class KEXIDATATABLE_EXPORT KexiComboBoxBase { public: KexiComboBoxBase(); virtual ~KexiComboBoxBase(); //! \return column related to this combo; for KexiComboBoxTableEdit 0 is returned here virtual KDbTableViewColumn *column() = 0; //! @overload const KDbTableViewColumn *column() const; //! \return database field related to this combo virtual KDbField *field() = 0; //! \return the original value virtual QVariant origValue() const = 0; //! Note: Generally in current implementation this is integer > 0; may be null if no value is set virtual QVariant value(); virtual QVariant visibleValue(); //! Reimplement this and call this impl.: used to clear internal editor virtual void clear(); virtual tristate valueChangedInternal(); virtual bool valueIsNull(); virtual bool valueIsEmpty(); -public: virtual void hide(); - void createPopup(bool show); - void showPopup(); //! Call this from slot virtual void slotRecordAccepted(KDbRecordData *data, int record); //! Call this from slot virtual void slotRecordSelected(KDbRecordData* data); //! Call this from slot void slotInternalEditorValueChanged(const QVariant &v); //! Implement this to return the internal editor virtual QWidget *internalEditor() const = 0; protected: + //! @return connection for this combo + virtual KDbConnection *connection() = 0; + + void createPopup(bool show); + virtual void setValueInternal(const QVariant& add, bool removeOld); //! Used to select record item for a user-entered value \a v. //! Only for "lookup table" mode. KDbRecordData* selectRecordForEnteredValueInLookupTable(const QVariant& v); /*! \return value from \a returnFromColumn related to \a str value from column \a lookInColumn. If \a allowNulls is true, NULL is returned if no matched column found, else: \a str is returned. Example: lookInColumn=0, returnFromColumn=1 --returns user-visible string for column #1 for id-column #0 */ QString valueForString(const QString& str, int* record, int lookInColumn, int returnFromColumn, bool allowNulls = false); //! sets \a value for the line edit without setting a flag (m_userEnteredValue) that indicates that //! the text has been entered by hand (by a user) void setValueOrTextInInternalEditor(const QVariant& value); //! \return lookup field schema for this combo box, if present and if is valid (i.e. has defined record source) KDbLookupFieldSchema* lookupFieldSchema(); //! @override const KDbLookupFieldSchema* lookupFieldSchema() const; int recordToHighlightForLookupTable() const; //! Implement this to perform "move cursor to end" in the internal editor virtual void moveCursorToEndInInternalEditor() = 0; //! Implement this to perform "select all" in the internal editor virtual void selectAllInInternalEditor() = 0; //! Implement this to perform "set value" in the internal editor virtual void setValueInInternalEditor(const QVariant& value) = 0; //! Implement this to return value from the internal editor virtual QVariant valueFromInternalEditor() = 0; //! Implement this as signal virtual void editRequested() = 0; //! Implement this as signal virtual void acceptRequested() = 0; //! Implement this to return a position \a pos mapped from parent (e.g. viewport) //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed. virtual QPoint mapFromParentToGlobal(const QPoint& pos) const = 0; //! Implement this to return a hint for popup width. virtual int popupWidthHint() const = 0; //! Implement this to update button state. Table view just updates on/off state //! for the button depending on visibility of the popup virtual void updateButton() {} virtual KexiComboBoxPopup *popup() const = 0; virtual void setPopup(KexiComboBoxPopup *popup) = 0; virtual QVariant visibleValueForLookupField(); void updateTextForHighlightedRecord(); bool handleKeyPressForPopup(QKeyEvent *ke); void acceptPopupSelection(); //! Used by KexiDBComboBox. void undoChanges(); //! \return index of bound column. int boundColumnIndex() const; //! \return index of (actually, first as this is the current limitation) visible column. int visibleColumnIndex() const; //! A hack for createPopup(), used by forms only. Avoid magical disappearing of the popup in forms after 2nd and subsequent use. //! fix creating popup for forms instead! bool m_reinstantiatePopupOnShow; QVariant m_visibleValue; QVariant m_userEnteredValue; //!< value (usually a text) entered by hand (by the user) bool m_internalEditorValueChanged; //!< true if user has text or other value inside editor bool m_slotInternalEditorValueChanged_enabled; //!< Used in slotInternalEditorValueChanged() bool m_setValueOrTextInInternalEditor_enabled; //!< Used in setValueOrTextInInternalEditor() and slotItemSelected() bool m_mouseBtnPressedWhenPopupVisible; //!< Used only by KexiComboBoxTableEdit bool m_insideCreatePopup; //!< true if we're inside createPopup(); used in slotItemSelected() //! Set to false as soon as the item corresponding with the current //! value is selected in the popup table. This avoids selecting item //! for origValue() and thus loosing the recent choice. bool m_updatePopupSelectionOnShow; bool m_moveCursorToEndInInternalEditor_enabled; bool m_selectAllInInternalEditor_enabled; bool m_setValueInInternalEditor_enabled; //! Used in setValueInternal() to control whether //! we want to set visible value on setValueInternal() //! - true for table view's combo box bool m_setVisibleValueOnSetValueInternal; //! Checked in createPopup(), true for form's combo box, so the popup is focused before showing; //! false for table view's combo box, so the popup is focused after showing. //! False by default. bool m_focusPopupBeforeShow; }; #endif diff --git a/src/widget/tableview/kexicomboboxpopup.cpp b/src/widget/tableview/kexicomboboxpopup.cpp index 22df78684..80f8a44ac 100644 --- a/src/widget/tableview/kexicomboboxpopup.cpp +++ b/src/widget/tableview/kexicomboboxpopup.cpp @@ -1,476 +1,477 @@ /* This file is part of the KDE project Copyright (C) 2004-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. */ #include "kexicomboboxpopup.h" #include "KexiDataTableScrollArea.h" #include "KexiTableScrollArea_p.h" #include "kexitableedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /*! @internal Helper for KexiComboBoxPopup. */ class KexiComboBoxPopup_KexiTableView : public KexiDataTableScrollArea { Q_OBJECT public: KexiComboBoxPopup_KexiTableView(QWidget* parent = 0) : KexiDataTableScrollArea(parent) { init(); } void init() { setObjectName("KexiComboBoxPopup_tv"); setReadOnly(true); setLineWidth(0); d->moveCursorOnMouseRelease = true; KexiTableScrollArea::Appearance a(appearance()); a.navigatorEnabled = false; //! @todo add option for backgroundAltering?? a.backgroundAltering = false; a.fullRecordSelection = true; a.recordHighlightingEnabled = true; a.recordMouseOverHighlightingEnabled = true; a.persistentSelections = false; a.recordMouseOverHighlightingColor = palette().highlight().color(); a.recordMouseOverHighlightingTextColor = palette().highlightedText().color(); a.recordHighlightingTextColor = a.recordMouseOverHighlightingTextColor; a.horizontalGridEnabled = false; a.verticalGridEnabled = false; setAppearance(a); setInsertingEnabled(false); setSortingEnabled(false); setVerticalHeaderVisible(false); setHorizontalHeaderVisible(false); setContextMenuEnabled(false); setScrollbarToolTipsEnabled(false); installEventFilter(this); setBottomMarginInternal(0); } virtual void setData(KDbTableViewData *data, bool owner = true) { KexiTableScrollArea::setData(data, owner); } bool setData(KDbCursor *cursor) { return KexiDataTableScrollArea::setData(cursor); } }; //======================================== //! @internal class KexiComboBoxPopupPrivate { public: KexiComboBoxPopupPrivate() : int_f(0) , privateQuery(0) { maxRecordCount = KexiComboBoxPopup::defaultMaxRecordCount; } ~KexiComboBoxPopupPrivate() { delete int_f; delete privateQuery; } KexiComboBoxPopup_KexiTableView *tv; KDbField *int_f; //!< @todo remove this -temporary KDbQuerySchema* privateQuery; int maxRecordCount; //! Columns that should be kept visible; the others should be hidden. //! Used when query is used as the record source type (KDbLookupFieldSchemaRecordSource::Query). //! We're doing this in this case because it's hard to alter the query to remove columns. QList visibleColumnsToShow; }; //======================================== const int KexiComboBoxPopup::defaultMaxRecordCount = 8; -KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KDbTableViewColumn *column) +KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KDbConnection *conn, KDbTableViewColumn *column) : QFrame(parent, Qt::Popup) , d( new KexiComboBoxPopupPrivate ) { init(); //setup tv data - setData(column, 0); + setData(conn, column, 0); } KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KDbField *field) : QFrame(parent, Qt::Popup) , d( new KexiComboBoxPopupPrivate ) { init(); //setup tv data - setData(0, field); + setData(nullptr, nullptr, field); } KexiComboBoxPopup::~KexiComboBoxPopup() { delete d; } void KexiComboBoxPopup::init() { setObjectName("KexiComboBoxPopup"); setAttribute(Qt::WA_WindowPropagation); setAttribute(Qt::WA_X11NetWmWindowTypeCombo); QPalette pal(palette()); pal.setBrush(backgroundRole(), pal.brush(QPalette::Base)); setPalette(pal); setLineWidth(1); setFrameStyle(Box | Plain); d->tv = new KexiComboBoxPopup_KexiTableView(this); d->tv->setFrameShape(QFrame::NoFrame); d->tv->setLineWidth(0); installEventFilter(this); connect(d->tv, SIGNAL(itemReturnPressed(KDbRecordData*,int,int)), this, SLOT(slotTVItemAccepted(KDbRecordData*,int,int))); connect(d->tv, SIGNAL(itemMouseReleased(KDbRecordData*,int,int)), this, SLOT(slotTVItemAccepted(KDbRecordData*,int,int))); connect(d->tv, SIGNAL(itemDblClicked(KDbRecordData*,int,int)), this, SLOT(slotTVItemAccepted(KDbRecordData*,int,int))); } -void KexiComboBoxPopup::setData(KDbTableViewColumn *column, KDbField *aField) +void KexiComboBoxPopup::setData(KDbConnection *conn, KDbTableViewColumn *column, KDbField *aField) { d->visibleColumnsToShow.clear(); const KDbField *field = aField; if (column && !field) { field = column->field(); } if (!field) { qWarning() << "!field"; return; } // case 1: simple related data if (column && column->relatedData()) { d->tv->setColumnsResizeEnabled(true); //only needed when using single column setDataInternal(column->relatedData(), false /*!owner*/); return; } // case 2: lookup field const KDbLookupFieldSchema *lookupFieldSchema = nullptr; if (field->table()) lookupFieldSchema = field->table()->lookupFieldSchema(*field); delete d->privateQuery; d->privateQuery = 0; const QList visibleColumns(lookupFieldSchema ? lookupFieldSchema->visibleColumns() : QList()); if (!visibleColumns.isEmpty() && lookupFieldSchema && lookupFieldSchema->boundColumn() >= 0) { const bool multipleLookupColumnJoined = visibleColumns.count() > 1; //! @todo support more RowSourceType's, not only table and query KDbCursor *cursor = 0; switch (lookupFieldSchema->recordSource().type()) { case KDbLookupFieldSchemaRecordSource::Table: { - KDbTableSchema *lookupTable - = field->table()->connection()->tableSchema(lookupFieldSchema->recordSource().name()); + KDbTableSchema *lookupTable = conn->tableSchema(lookupFieldSchema->recordSource().name()); if (!lookupTable) //! @todo errmsg return; if (multipleLookupColumnJoined) { /*qDebug() << "--- Orig query: "; qDebug() << *lookupTable->query(); qDebug() << field->table()->connection()->selectStatement(*lookupTable->query());*/ - d->privateQuery = new KDbQuerySchema(*lookupTable->query()); + d->privateQuery = new KDbQuerySchema(*lookupTable->query(), conn); } else { // Create a simple SELECT query that contains only needed columns, // that is visible and bound ones. The bound columns are placed on the end. // Don't do this if one or more visible or bound columns cannot be found. - const KDbQueryColumnInfo::Vector fieldsExpanded(lookupTable->query()->fieldsExpanded()); + const KDbQueryColumnInfo::Vector fieldsExpanded( + lookupTable->query()->fieldsExpanded(conn)); d->privateQuery = new KDbQuerySchema; bool columnsFound = true; QList visibleAndBoundColumns = visibleColumns; visibleAndBoundColumns.append(lookupFieldSchema->boundColumn()); qDebug() << visibleAndBoundColumns; foreach (int index, visibleAndBoundColumns) { KDbQueryColumnInfo *columnInfo = fieldsExpanded.value(index); if (!columnInfo || !columnInfo->field() || !d->privateQuery->addField(columnInfo->field())) { columnsFound = false; break; } } if (columnsFound) { // proper data source: bound + visible columns - cursor = field->table()->connection()->prepareQuery(d->privateQuery); + cursor = conn->prepareQuery(d->privateQuery); /*qDebug() << "--- Composed query:"; qDebug() << *d->privateQuery; qDebug() << field->table()->connection()->selectStatement(*d->privateQuery);*/ } else { // for sanity delete d->privateQuery; d->privateQuery = 0; - cursor = field->table()->connection()->prepareQuery(lookupTable); + cursor = conn->prepareQuery(lookupTable); } } break; } case KDbLookupFieldSchemaRecordSource::Query: { KDbQuerySchema *lookupQuery - = field->table()->connection()->querySchema(lookupFieldSchema->recordSource().name()); + = conn->querySchema(lookupFieldSchema->recordSource().name()); if (!lookupQuery) //! @todo errmsg return; if (multipleLookupColumnJoined) { /*qDebug() << "--- Orig query: "; qDebug() << *lookupQuery; qDebug() << field->table()->connection()->selectStatement(*lookupQuery);*/ - d->privateQuery = new KDbQuerySchema(*lookupQuery); + d->privateQuery = new KDbQuerySchema(*lookupQuery, conn); } else { d->visibleColumnsToShow = visibleColumns; qSort(d->visibleColumnsToShow); // because we will depend on a sorted list - cursor = field->table()->connection()->prepareQuery(lookupQuery); + cursor = conn->prepareQuery(lookupQuery); } break; } default:; } if (multipleLookupColumnJoined && d->privateQuery) { // append a column computed using multiple columns - const KDbQueryColumnInfo::Vector fieldsExpanded(d->privateQuery->fieldsExpanded()); + const KDbQueryColumnInfo::Vector fieldsExpanded(d->privateQuery->fieldsExpanded(conn)); int fieldsExpandedSize(fieldsExpanded.size()); KDbExpression expr; QList::ConstIterator it(visibleColumns.constBegin()); for (it += visibleColumns.count() - 1; it != visibleColumns.constEnd(); --it) { KDbQueryColumnInfo *ci = ((*it) < fieldsExpandedSize) ? fieldsExpanded.at(*it) : 0; if (!ci) { qWarning() << *it << ">= fieldsExpandedSize"; continue; } KDbVariableExpression fieldExpr(ci->field()->table()->name() + "." + ci->field()->name()); //! @todo KEXI3 check this we're calling KDbQuerySchema::validate() instead of this: fieldExpr.field = ci->field; //! @todo KEXI3 check this we're calling KDbQuerySchema::validate() instead of this: fieldExpr.tablePositionForField = d->privateQuery->tableBoundToColumn(*it); if (expr.isValid()) { //! @todo " " separator hardcoded... //! @todo use SQL sub-parser here... KDbConstExpression constExpr(KDbToken::CHARACTER_STRING_LITERAL, " "); expr = KDbBinaryExpression(constExpr, KDbToken::CONCATENATION, expr); expr = KDbBinaryExpression(fieldExpr, KDbToken::CONCATENATION, expr); } else { expr = fieldExpr; } } qDebug() << expr; KDbField *f = new KDbField(); f->setExpression(expr); if (!d->privateQuery->addField(f)) { qWarning() << "d->privateQuery->addField(f)"; delete f; return; } QString errorMessage, errorDescription; //! @todo KEXI3 check d->privateQuery->validate() if (!d->privateQuery->validate(&errorMessage, &errorDescription)) { qWarning() << "error in query:" << d->privateQuery << "\n" << "errorMessage:" << errorMessage << "\nerrorDescription:" << errorDescription; return; } #if 0 //does not work yet // //! @todo temp: improved display by hiding all columns except the computed one const int numColumntoHide = d->privateQuery->fieldsExpanded().count() - 1; for (int i = 0; i < numColumntoHide; i++) d->privateQuery->setColumnVisible(i, false); // #endif //! @todo ... - qDebug() << "--- Private query:" << *d->privateQuery; - cursor = field->table()->connection()->prepareQuery(d->privateQuery); + qDebug() << "--- Private query:" << KDbConnectionAndQuerySchema(conn, *d->privateQuery); + cursor = conn->prepareQuery(d->privateQuery); } if (!cursor) //! @todo errmsg return; if (d->tv->data()) d->tv->data()->disconnect(this); d->tv->setData(cursor); connect(d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); updateSize(); return; } qWarning() << "no column relatedData \n - moving to setData(KDbField &)"; // case 3: enum hints d->tv->setColumnsResizeEnabled(true); //only needed when using single column //! @todo THIS IS PRIMITIVE: we'd need to employ KDbReference here! d->int_f = new KDbField(field->name(), KDbField::Text); KDbTableViewData *data = new KDbTableViewData(); data->addColumn(new KDbTableViewColumn(d->int_f)); const QVector hints(field->enumHints()); for (int i = 0; i < hints.size(); i++) { KDbRecordData *newData = data->createItem(); (*newData)[0] = QVariant(hints[i]); qDebug() << "added: '" << hints[i] << "'"; data->append(newData); } setDataInternal(data, true); updateSize(); } void KexiComboBoxPopup::setDataInternal(KDbTableViewData *data, bool owner) { if (d->tv->data()) d->tv->data()->disconnect(this); d->tv->setData(data, owner); connect(d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); updateSize(); } void KexiComboBoxPopup::updateSize(int minWidth) { const int records = qMin(d->maxRecordCount, d->tv->recordCount()); KexiTableEdit *te = dynamic_cast(parentWidget()); int width = qMax(d->tv->tableSize().width(), (te ? te->totalSize().width() : (parentWidget() ? parentWidget()->width() : 0/*sanity*/))); //qDebug() << "size=" << size(); const QRect screen = QApplication::desktop()->availableGeometry(this); resize(qMin(screen.width(), qMax(minWidth, width)), d->tv->recordHeight() * records + 3); //qDebug() << "size after=" << size() << d->tv->verticalScrollBar()->isVisible() << d->tv->horizontalScrollBar()->isVisible(); if (d->visibleColumnsToShow.isEmpty()) { // record source type is not Query d->tv->setColumnResizeEnabled(0, true); d->tv->setColumnResizeEnabled(d->tv->columnCount() - 1, false); d->tv->setColumnWidth(1, 0); //!< @todo A temp. hack to hide the bound column if (d->tv->verticalScrollBar()->isVisible()) { d->tv->setColumnWidth(0, d->tv->width() - 1 - d->tv->verticalScrollBar()->width()); } else { d->tv->setColumnWidth(0, d->tv->width() - 1); } d->tv->triggerUpdate(); if (d->tv->recordNumberAt(0) == 0 && records == d->tv->recordCount()) { d->tv->setColumnWidth(0, d->tv->width() - 1); } } else { // record source type is Query // Set width to 0 and disable resizing of columns that shouldn't be visible - const KDbQueryColumnInfo::Vector fieldsExpanded(d->tv->cursor()->query()->fieldsExpanded()); + const KDbQueryColumnInfo::Vector fieldsExpanded( + d->tv->cursor()->query()->fieldsExpanded(d->tv->cursor()->connection())); QList::ConstIterator visibleColumnsToShowIt = d->visibleColumnsToShow.constBegin(); for (int i = 0; i < fieldsExpanded.count(); ++i) { bool show = visibleColumnsToShowIt != d->visibleColumnsToShow.constEnd() && i == *visibleColumnsToShowIt; d->tv->setColumnResizeEnabled(i, show); if (show) { if (d->visibleColumnsToShow.count() == 1) { d->tv->setColumnWidth(i, d->tv->width() - 1); } ++visibleColumnsToShowIt; } else { d->tv->setColumnWidth(i, 0); } //qDebug() << i << show; } } } KexiTableScrollArea* KexiComboBoxPopup::tableView() { return d->tv; } void KexiComboBoxPopup::resize(int w, int h) { //d->tv->horizontalScrollBar()->hide(); //d->tv->verticalScrollBar()->hide(); d->tv->move(0, 0); d->tv->resize(w + 1, h - 1); QFrame::resize(d->tv->size() + QSize(1, 1)); update(); updateGeometry(); } void KexiComboBoxPopup::setMaxRecordCount(int r) { d->maxRecordCount = r; } int KexiComboBoxPopup::maxRecordCount() const { return d->maxRecordCount; } void KexiComboBoxPopup::slotTVItemAccepted(KDbRecordData *data, int record, int) { hide(); emit recordAccepted(data, record); } bool KexiComboBoxPopup::eventFilter(QObject *o, QEvent *e) { #if 0 if (e->type() == QEvent::Resize) { qDebug() << "QResizeEvent" << dynamic_cast(e)->size() << "old=" << dynamic_cast(e)->oldSize() << o << qobject_cast(o)->geometry() << "visible=" << qobject_cast(o)->isVisible(); } #endif if (o == this && (e->type() == QEvent::Hide || e->type() == QEvent::FocusOut)) { qDebug() << "HIDE!!!"; emit hidden(); } else if (e->type() == QEvent::MouseButtonPress) { qDebug() << "QEvent::MousePress"; } else if (o == d->tv) { qDebug() << "QEvent::KeyPress TV"; if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); const int k = ke->key(); if ((ke->modifiers() == Qt::NoModifier && (k == Qt::Key_Escape || k == Qt::Key_F4)) || (ke->modifiers() == Qt::AltModifier && k == Qt::Key_Up)) { hide(); emit cancelled(); emit hidden(); return true; } } } return QFrame::eventFilter(o, e); } void KexiComboBoxPopup::slotDataReloadRequested() { updateSize(); } #include "kexicomboboxpopup.moc" diff --git a/src/widget/tableview/kexicomboboxpopup.h b/src/widget/tableview/kexicomboboxpopup.h index 1d591daf4..fcab41ffb 100644 --- a/src/widget/tableview/kexicomboboxpopup.h +++ b/src/widget/tableview/kexicomboboxpopup.h @@ -1,91 +1,92 @@ /* This file is part of the KDE project Copyright (C) 2004-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. */ #ifndef KEXICOMBOBOXPOPUP_H #define KEXICOMBOBOXPOPUP_H #include +class KDbConnection; class KDbField; class KDbRecordData; class KDbTableViewColumn; class KDbTableViewData; class KexiComboBoxPopupPrivate; class KexiTableScrollArea; //! Internal class for displaying popup table view class KexiComboBoxPopup : public QFrame { Q_OBJECT public: //! @todo js: more ctors! /*! Constructor for creating a popup using definition from \a column. - If the column is lookup column, it's definition is used to display + If the column is lookup column, its definition is used to display one or more column within the popup. Otherwise column.field() is used to display single-column data. */ - KexiComboBoxPopup(QWidget* parent, KDbTableViewColumn *column); + KexiComboBoxPopup(QWidget* parent, KDbConnection *conn, KDbTableViewColumn *column); /*! Alternative constructor supporting lookup fields and enum hints. */ KexiComboBoxPopup(QWidget* parent, KDbField *field); virtual ~KexiComboBoxPopup(); KexiTableScrollArea* tableView(); /*! Sets maximum number of records for this popup. */ void setMaxRecordCount(int count); /*! \return maximum number of records for this popup. */ int maxRecordCount() const; /*! Default maximum number of records for KexiComboBoxPopup objects. */ static const int defaultMaxRecordCount; Q_SIGNALS: void recordAccepted(KDbRecordData *data, int record); void cancelled(); void hidden(); public Q_SLOTS: virtual void resize(int w, int h); void updateSize(int minWidth = 0); protected Q_SLOTS: void slotTVItemAccepted(KDbRecordData *data, int record, int column); void slotDataReloadRequested(); protected: void init(); virtual bool eventFilter(QObject *o, QEvent *e); //! The main function for setting data; data can be set either by passing \a column or \a field. //! The second case is used for lookup - void setData(KDbTableViewColumn *column, KDbField *field); + void setData(KDbConnection *conn, KDbTableViewColumn *column, KDbField *field); //! used by setData() void setDataInternal(KDbTableViewData *data, bool owner = true); //!< helper KexiComboBoxPopupPrivate * const d; friend class KexiComboBoxTableEdit; }; #endif diff --git a/src/widget/tableview/kexicomboboxtableedit.cpp b/src/widget/tableview/kexicomboboxtableedit.cpp index ff438dec0..5aef72341 100644 --- a/src/widget/tableview/kexicomboboxtableedit.cpp +++ b/src/widget/tableview/kexicomboboxtableedit.cpp @@ -1,430 +1,439 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson Copyright (C) 2003-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 "kexicomboboxtableedit.h" #include #include #include "kexicomboboxpopup.h" #include "KexiTableScrollArea.h" #include "KexiTableScrollAreaWidget.h" #include "kexi.h" #include #include #include #include #include #include #include #include // the right margin is too large when the editor is show, reduce it const int RIGHT_MARGIN_DELTA = 6; //! @internal class Q_DECL_HIDDEN KexiComboBoxTableEdit::Private { public: Private() : popup(0) , currentEditorWidth(0) , visibleTableViewColumn(0) , internalEditor(0) { } ~Private() { delete internalEditor; delete visibleTableViewColumn; } KexiComboBoxDropDownButton *button; KexiComboBoxPopup *popup; int currentEditorWidth; QSize totalSize; KDbTableViewColumn* visibleTableViewColumn; KexiTableEdit* internalEditor; int arrowWidth; + KDbConnection *connection = nullptr; }; //====================================================== KexiComboBoxTableEdit::KexiComboBoxTableEdit(KDbTableViewColumn *column, QWidget *parent) : KexiComboBoxBase() , KexiInputTableEdit(column, parent) , d(new Private()) { m_setVisibleValueOnSetValueInternal = true; m_reinstantiatePopupOnShow = true; // needed because re-opening of the popup fails for unknown reason d->button = new KexiComboBoxDropDownButton(parentWidget() /*usually a viewport*/); d->button->hide(); d->button->setFocusPolicy(Qt::NoFocus); connect(d->button, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); connect(m_lineedit, SIGNAL(textChanged(QString)), this, SLOT(slotLineEditTextChanged(QString))); - m_rightMarginWhenFocused = this->column()->isReadOnly() ? 0 : d->button->width(); + m_rightMarginWhenFocused = (isReadOnly() || this->column()->isReadOnly()) ? 0 : d->button->width(); m_rightMarginWhenFocused -= RIGHT_MARGIN_DELTA; updateLineEditStyleSheet(); m_rightMarginWhenFocused += RIGHT_MARGIN_DELTA; //! @todo update when style changes d->arrowWidth = KexiUtils::comboBoxArrowSize(style()).width(); } KexiComboBoxTableEdit::~KexiComboBoxTableEdit() { delete d; } -void KexiComboBoxTableEdit::createInternalEditor(KDbQuerySchema& schema) +KDbConnection *KexiComboBoxTableEdit::connection() { + return d->connection; +} + +void KexiComboBoxTableEdit::createInternalEditor(KDbConnection *conn, const KDbQuerySchema& schema) +{ + d->connection = conn; if (!m_column->visibleLookupColumnInfo() || d->visibleTableViewColumn/*sanity*/) return; const KDbField::Type t = m_column->visibleLookupColumnInfo()->field()->type(); //! @todo subtype? KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(t); if (!item || item->className() == "KexiInputTableEdit") return; //unsupported type or there is no need to use subeditor for KexiInputTableEdit //special cases: BLOB, Bool datatypes //! @todo //find real type to display KDbQueryColumnInfo *ci = m_column->visibleLookupColumnInfo(); KDbQueryColumnInfo *visibleLookupColumnInfo = 0; if (ci->indexForVisibleLookupValue() != -1) { //Lookup field is defined - visibleLookupColumnInfo = schema.expandedOrInternalField(ci->indexForVisibleLookupValue()); + visibleLookupColumnInfo = schema.expandedOrInternalField(conn, ci->indexForVisibleLookupValue()); } d->visibleTableViewColumn = new KDbTableViewColumn(schema, ci, visibleLookupColumnInfo); //! todo set d->internalEditor visible and use it to enable data entering by hand d->internalEditor = KexiCellEditorFactory::createEditor(d->visibleTableViewColumn, nullptr); m_lineedit->hide(); } KexiComboBoxPopup *KexiComboBoxTableEdit::popup() const { return d->popup; } void KexiComboBoxTableEdit::setPopup(KexiComboBoxPopup *popup) { d->popup = popup; } void KexiComboBoxTableEdit::showFocus(const QRect& r, bool readOnly) { updateFocus(r); d->button->setEnabled(!readOnly); d->button->setVisible(!readOnly); } void KexiComboBoxTableEdit::resize(int w, int h) { d->totalSize = QSize(w, h); - if (!column()->isReadOnly()) { + if (isReadOnly() || column()->isReadOnly()) { + m_rightMarginWhenFocused = 0; + } else { + m_rightMarginWhenFocused = d->button->width(); d->button->resize(h, h); QWidget::resize(w, h); } - m_rightMarginWhenFocused = column()->isReadOnly() ? 0 : d->button->width(); m_rightMarginWhenFocused -= RIGHT_MARGIN_DELTA; updateLineEditStyleSheet(); m_rightMarginWhenFocused += RIGHT_MARGIN_DELTA; QRect r(pos().x(), pos().y(), w + 1, h + 1); if (qobject_cast(parentWidget())) { r.translate( qobject_cast(parentWidget())->scrollArea->horizontalScrollBar()->value(), qobject_cast(parentWidget())->scrollArea->verticalScrollBar()->value()); } updateFocus(r); if (popup()) { popup()->updateSize(); } } // internal void KexiComboBoxTableEdit::updateFocus(const QRect& r) { - if (!column()->isReadOnly()) { + if (!isReadOnly() && !column()->isReadOnly()) { if (d->button->width() > r.width()) moveChild(d->button, r.right() + 1, r.top()); else moveChild(d->button, r.right() - d->button->width(), r.top()); } } void KexiComboBoxTableEdit::hideFocus() { d->button->hide(); } QVariant KexiComboBoxTableEdit::visibleValue() { return KexiComboBoxBase::visibleValue(); } void KexiComboBoxTableEdit::clear() { m_lineedit->clear(); KexiComboBoxBase::clear(); } bool KexiComboBoxTableEdit::valueChanged() { const tristate res = valueChangedInternal(); if (~res) //no result: just compare values return KexiInputTableEdit::valueChanged(); return res == true; } void KexiComboBoxTableEdit::paintFocusBorders(QPainter *p, QVariant &, int x, int y, int w, int h) { p->drawRect(x, y, w, h); } void KexiComboBoxTableEdit::setupContents(QPainter *p, bool focused, const QVariant& val, QString &txt, int &align, int &x, int &y_offset, int &w, int &h) { if (d->internalEditor) { d->internalEditor->setupContents(p, focused, val, txt, align, x, y_offset, w, h); } else { KexiInputTableEdit::setupContents(p, focused, val, txt, align, x, y_offset, w, h); } if (!val.isNull()) { const KDbTableViewData *relData = column()->relatedData(); if (relData) { int recordToHighlight; txt = valueForString(val.toString(), &recordToHighlight, 0, 1); } else if (lookupFieldSchema()) { } else { //use 'enum hints' model txt = field()->enumHint(val.toInt()); } } } void KexiComboBoxTableEdit::slotButtonClicked() { // this method is sometimes called by hand: // do not allow to simulate clicks when the button is disabled - if (column()->isReadOnly() || !d->button->isEnabled()) + if (isReadOnly() || column()->isReadOnly() || !d->button->isEnabled()) return; if (m_mouseBtnPressedWhenPopupVisible) { m_mouseBtnPressedWhenPopupVisible = false; return; } if (!popup() || !popup()->isVisible()) { qDebug() << "SHOW POPUP"; showPopup(); } } void KexiComboBoxTableEdit::slotPopupHidden() { } void KexiComboBoxTableEdit::updateButton() { } void KexiComboBoxTableEdit::hide() { KexiInputTableEdit::hide(); KexiComboBoxBase::hide(); } void KexiComboBoxTableEdit::show() { KexiInputTableEdit::show(); - if (!column()->isReadOnly()) { + if (!isReadOnly() && !column()->isReadOnly()) { d->button->show(); } } bool KexiComboBoxTableEdit::handleKeyPress(QKeyEvent *ke, bool editorActive) { //qDebug() << ke; const int k = ke->key(); if ((ke->modifiers() == Qt::NoModifier && k == Qt::Key_F4) || (ke->modifiers() == Qt::AltModifier && k == Qt::Key_Down)) { //show popup slotButtonClicked(); return true; } else if (editorActive) { const bool enterPressed = k == Qt::Key_Enter || k == Qt::Key_Return; if (enterPressed && m_internalEditorValueChanged) { createPopup(false); selectRecordForEnteredValueInLookupTable(m_userEnteredValue); return true; } return handleKeyPressForPopup(ke); } return false; } void KexiComboBoxTableEdit::slotLineEditTextChanged(const QString& s) { slotInternalEditorValueChanged(s); } int KexiComboBoxTableEdit::widthForValue(const QVariant &val, const QFontMetrics &fm) { const KDbTableViewData *relData = column() ? column()->relatedData() : nullptr; if (lookupFieldSchema() || relData) { // in 'lookupFieldSchema' or or 'related table data' model // we're assuming val is already the text, not the index //! @todo ok? return qMax(KEXITV_MINIMUM_COLUMN_WIDTH, fm.width(val.toString()) + d->arrowWidth); } //use 'enum hints' model QVector hints = field()->enumHints(); bool ok; int idx = val.toInt(&ok); if (!ok || idx < 0 || idx > int(hints.size() - 1)) return KEXITV_MINIMUM_COLUMN_WIDTH; QString txt = hints.value(idx); if (txt.isEmpty()) return KEXITV_MINIMUM_COLUMN_WIDTH; return fm.width(txt) + d->arrowWidth; } bool KexiComboBoxTableEdit::eventFilter(QObject *o, QEvent *e) { #if 0 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; qDebug() << "FOCUS WIDGET:" << focusWidget(); } #endif KexiTableScrollArea *tv = qobject_cast(parentWidget())->scrollArea; if (tv && e->type() == QEvent::KeyPress) { if (tv->eventFilter(o, e)) { return true; } } - if (!column()->isReadOnly() && e->type() == QEvent::MouseButtonPress + if (!isReadOnly() && !column()->isReadOnly() && e->type() == QEvent::MouseButtonPress && qobject_cast(parentWidget())) { QPoint gp = static_cast(e)->globalPos() + d->button->pos(); QRect r(d->button->mapToGlobal(d->button->geometry().topLeft()), d->button->mapToGlobal(d->button->geometry().bottomRight())); if (o == popup() && popup()->isVisible() && r.contains(gp)) { m_mouseBtnPressedWhenPopupVisible = true; } } return false; } QSize KexiComboBoxTableEdit::totalSize() const { return d->totalSize; } QWidget *KexiComboBoxTableEdit::internalEditor() const { return m_lineedit; } void KexiComboBoxTableEdit::moveCursorToEndInInternalEditor() { moveCursorToEnd(); } void KexiComboBoxTableEdit::selectAllInInternalEditor() { selectAll(); } void KexiComboBoxTableEdit::moveCursorToEnd() { m_lineedit->end(false/*!mark*/); } void KexiComboBoxTableEdit::moveCursorToStart() { m_lineedit->home(false/*!mark*/); } void KexiComboBoxTableEdit::selectAll() { m_lineedit->selectAll(); } void KexiComboBoxTableEdit::setValueInInternalEditor(const QVariant& value) { KexiUtils::BoolBlocker guard(&m_slotInternalEditorValueChanged_enabled, false); m_lineedit->setText(value.toString()); } QVariant KexiComboBoxTableEdit::valueFromInternalEditor() { return m_lineedit->text(); } QPoint KexiComboBoxTableEdit::mapFromParentToGlobal(const QPoint& pos) const { KexiTableScrollArea *tv = qobject_cast(parentWidget())->scrollArea; if (!tv) return QPoint(-1, -1); return tv->viewport()->mapToGlobal(pos); } int KexiComboBoxTableEdit::popupWidthHint() const { return m_lineedit->width(); } void KexiComboBoxTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) { Q_UNUSED(value); //! @todo does not work with BLOBs! qApp->clipboard()->setText(visibleValue.toString()); } void KexiComboBoxTableEdit::handleAction(const QString& actionName) { const bool alreadyVisible = m_lineedit->isVisible(); if (actionName == "edit_paste") { if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode emit editRequested(); m_lineedit->clear(); } //! @todo does not work with BLOBs! setValueInInternalEditor(qApp->clipboard()->text()); } else KexiInputTableEdit::handleAction(actionName); } QVariant KexiComboBoxTableEdit::origValue() const { return KexiDataItemInterface::originalValue(); } KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiComboBoxEditorFactoryItem, KexiComboBoxTableEdit) diff --git a/src/widget/tableview/kexicomboboxtableedit.h b/src/widget/tableview/kexicomboboxtableedit.h index 53d79b963..b5001cf04 100644 --- a/src/widget/tableview/kexicomboboxtableedit.h +++ b/src/widget/tableview/kexicomboboxtableedit.h @@ -1,183 +1,186 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson Copyright (C) 2003-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 _KEXICOMBOBOXTABLEEDIT_H_ #define _KEXICOMBOBOXTABLEEDIT_H_ #include "kexiinputtableedit.h" #include "kexicomboboxbase.h" #include #include #include #include #include class KDbTableViewColumn; class KexiComboBoxPopup; /*! @short Drop-down cell editor. */ class KexiComboBoxTableEdit : public KexiInputTableEdit, virtual public KexiComboBoxBase { Q_OBJECT public: explicit KexiComboBoxTableEdit(KDbTableViewColumn *column, QWidget *parent = 0); virtual ~KexiComboBoxTableEdit(); //! Implemented for KexiComboBoxBase KDbTableViewColumn *column() override { return m_column; } //! Implemented for KexiComboBoxBase KDbField *field() override { return m_column->field(); } //! Implemented for KexiComboBoxBase virtual QVariant origValue() const; virtual void setValueInternal(const QVariant& add, bool removeOld) { KexiComboBoxBase::setValueInternal(add, removeOld); } virtual QVariant value() { return KexiComboBoxBase::value(); } virtual void clear(); virtual bool valueChanged(); virtual QVariant visibleValue(); /*! Reimplemented: resizes a view(). */ virtual void resize(int w, int h); virtual void showFocus(const QRect& r, bool readOnly); virtual void hideFocus(); virtual void paintFocusBorders(QPainter *p, QVariant &cal, int x, int y, int w, int h); /*! Setups contents of the cell. As a special case, if there is lookup field schema defined, \a val already contains the visible value (usually the text) set by \ref KexiTableView::paintcell(), so there is noo need to lookup the value in the combo box's popup. */ virtual void setupContents(QPainter *p, bool focused, const QVariant& val, QString &txt, int &align, int &x, int &y_offset, int &w, int &h); /*! Used to handle key press events for the item. */ virtual bool handleKeyPress(QKeyEvent *ke, bool editorActive); virtual int widthForValue(const QVariant &val, const QFontMetrics &fm); virtual void hide(); virtual void show(); /*! \return total size of this editor, including popup button. */ virtual QSize totalSize() const; - virtual void createInternalEditor(KDbQuerySchema& schema); + virtual void createInternalEditor(KDbConnection *conn, const KDbQuerySchema& schema); /*! Reimplemented after KexiInputTableEdit. */ virtual void handleAction(const QString& actionName); /*! Reimplemented after KexiInputTableEdit. For a special case (combo box), \a visibleValue can be provided, so it can be copied to the clipboard instead of unreadable \a value. */ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); public Q_SLOTS: //! Implemented for KexiDataItemInterface virtual void moveCursorToEnd(); //! Implemented for KexiDataItemInterface virtual void moveCursorToStart(); //! Implemented for KexiDataItemInterface virtual void selectAll(); protected Q_SLOTS: void slotButtonClicked(); void slotRecordAccepted(KDbRecordData *data, int record) { KexiComboBoxBase::slotRecordAccepted(data, record); } void slotRecordSelected(KDbRecordData* data) { KexiComboBoxBase::slotRecordSelected(data); } void slotInternalEditorValueChanged(const QVariant& v) { KexiComboBoxBase::slotInternalEditorValueChanged(v); } void slotLineEditTextChanged(const QString& s); void slotPopupHidden(); protected: + //! Implemented for KexiComboBoxBase + KDbConnection *connection() override; + //! internal void updateFocus(const QRect& r); virtual bool eventFilter(QObject *o, QEvent *e); //! Implemented for KexiComboBoxBase virtual QWidget *internalEditor() const; //! Implemented for KexiComboBoxBase virtual void moveCursorToEndInInternalEditor(); //! Implemented for KexiComboBoxBase virtual void selectAllInInternalEditor(); //! Implemented for KexiComboBoxBase virtual void setValueInInternalEditor(const QVariant& value); //! Implemented for KexiComboBoxBase virtual QVariant valueFromInternalEditor(); //! Implemented for KexiComboBoxBase virtual void editRequested() { KexiInputTableEdit::editRequested(); } //! Implemented for KexiComboBoxBase virtual void acceptRequested() { KexiInputTableEdit::acceptRequested(); } //! Implemented for KexiComboBoxBase virtual QPoint mapFromParentToGlobal(const QPoint& pos) const; //! Implemented for KexiComboBoxBase virtual int popupWidthHint() const; //! Implemented this to update button state. virtual void updateButton(); virtual KexiComboBoxPopup *popup() const; virtual void setPopup(KexiComboBoxPopup *popup); class Private; Private * const d; }; KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiComboBoxEditorFactoryItem) #endif diff --git a/src/widget/tableview/kexitableedit.cpp b/src/widget/tableview/kexitableedit.cpp index 8d3b04a57..bf50e9dd6 100644 --- a/src/widget/tableview/kexitableedit.cpp +++ b/src/widget/tableview/kexitableedit.cpp @@ -1,290 +1,291 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson 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. */ #include "kexitableedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KEXI_MOBILE KexiTableEdit::KexiTableEdit(KDbTableViewColumn *column, QWidget* parent) : QWidget(parent) , m_column(column) , m_usesSelectedTextColor(true) , m_view(0) #else KexiTableEdit::KexiTableEdit(KDbTableViewColumn *column, QWidget* parent) : QWidget(parent) , m_column(column) , m_textFormatter(nullptr) , m_usesSelectedTextColor(true) , m_view(0) #endif { Q_ASSERT(column); QPalette pal(palette()); pal.setBrush(backgroundRole(), pal.brush(QPalette::Base)); setPalette(pal); //margins const KDbField::Type type = displayedField()->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isFPNumericType(type)) { #ifdef Q_OS_WIN m_leftMargin = 0; #else m_leftMargin = 0; #endif m_rightMargin = 6; } else if (KDbField::isIntegerType(type)) { #ifdef Q_OS_WIN m_leftMargin = 1; #else m_leftMargin = 0; #endif m_rightMargin = 6; } else {//default #ifdef Q_OS_WIN m_leftMargin = 5; #else m_leftMargin = 5; #endif m_rightMargin = 0; } m_rightMarginWhenFocused = m_rightMargin; } KexiTableEdit::~KexiTableEdit() { delete m_textFormatter; } KDbField *KexiTableEdit::field() { return m_column->field(); } KDbQueryColumnInfo *KexiTableEdit::columnInfo() { return m_column->columnInfo(); } -void KexiTableEdit::setColumnInfo(KDbQueryColumnInfo *) +void KexiTableEdit::setColumnInfo(KDbConnection *, KDbQueryColumnInfo *) { } const KDbTableViewColumn *KexiTableEdit::column() const { return m_column; } QWidget* KexiTableEdit::widget() { return m_view; } void KexiTableEdit::hideWidget() { hide(); } void KexiTableEdit::showWidget() { show(); } bool KexiTableEdit::usesSelectedTextColor() const { return m_usesSelectedTextColor; } int KexiTableEdit::leftMargin() const { return m_leftMargin; } bool KexiTableEdit::handleKeyPress(QKeyEvent* ke, bool editorActive) { Q_UNUSED(ke); Q_UNUSED(editorActive); return false; } bool KexiTableEdit::handleDoubleClick() { return false; } QSize KexiTableEdit::totalSize() const { return QWidget::size(); } -void KexiTableEdit::createInternalEditor(KDbQuerySchema& schema) +void KexiTableEdit::createInternalEditor(KDbConnection *conn, const KDbQuerySchema& schema) { - Q_UNUSED(schema); + Q_UNUSED(conn) + Q_UNUSED(schema) } const KDbField *KexiTableEdit::displayedField() { if (m_column->visibleLookupColumnInfo()) return m_column->visibleLookupColumnInfo()->field(); //mainly for lookup field in KexiComboBoxTableEdit: return m_column->field(); //typical case } void KexiTableEdit::setViewWidget(QWidget *v) { m_view = v; m_view->move(0, 0); setFocusProxy(m_view); } void KexiTableEdit::moveChild(QWidget * child, int x, int y) { #ifndef KEXI_MOBILE child->move(x, y); #endif } void KexiTableEdit::resize(int w, int h) { QWidget::resize(w, h); if (m_view) { if (!layout()) { //if there is layout (eg. KexiInputTableEdit), resize is automatic m_view->move(0, 0); m_view->resize(w, h); } } } #if 0 bool KexiTableEdit::eventFilter(QObject* watched, QEvent* e) { if (watched == this) { if (e->type() == QEvent::KeyPress) { QKeyEvent* ev = static_cast(e); if (ev->key() == Qt::Key_Escape) { return false; } } else { return false; } } return false; } #endif void KexiTableEdit::paintFocusBorders(QPainter *p, QVariant &, int x, int y, int w, int h) { p->drawRect(x, y, w, h); } void KexiTableEdit::setupContents(QPainter *p, bool focused, const QVariant& val, QString &txt, int &align, int &/*x*/, int &y_offset, int &w, int &h) { Q_UNUSED(p); Q_UNUSED(h); const KDbField *realField = displayedField(); #ifdef Q_OS_WIN y_offset = -1; #else y_offset = 0; #endif if (!m_textFormatter) { // delayed init m_textFormatter = new KexiTextFormatter; m_textFormatter->setField(realField); } //! @todo ADD OPTION to displaying NULL VALUES as e.g. "(null)" txt = m_textFormatter->toString(val, QString(), nullptr); const KDbField::Type type = realField->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isNumericType(type)) { align |= Qt::AlignRight; } else {//default: align |= Qt::AlignLeft; } w -= rightMargin(focused); } void KexiTableEdit::paintSelectionBackground(QPainter *p, bool /*focused*/, const QString& txt, int align, int x, int y_offset, int w, int h, const QColor& fillColor, const QFontMetrics &fm, bool readOnly, bool fullRecordSelection) { if (!readOnly && !fullRecordSelection && !txt.isEmpty()) { QRect bound = fm.boundingRect(x, y_offset, w - (x + x), h, align, txt); bound.setY(0); bound.setWidth(qMin(bound.width() + 2, w - (x + x) + 1)); if (align & Qt::AlignLeft) { bound.setX(bound.x() - 1); } else if (align & Qt::AlignRight) { bound.moveLeft(w - bound.width()); //move to left, if too wide } //! @todo align center bound.setHeight(h - 1); p->fillRect(bound, fillColor); } else if (fullRecordSelection) { p->fillRect(0, 0, w, h, fillColor); } } int KexiTableEdit::widthForValue(const QVariant &val, const QFontMetrics &fm) { return fm.width(val.toString()); } void KexiTableEdit::repaintRelatedCell() { #ifndef KEXI_MOBILE if (KexiDataAwareObjectInterface *iface = dynamic_cast(parentWidget())) { iface->updateCurrentCell(); } #endif } bool KexiTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, bool focused) { Q_UNUSED(value); Q_UNUSED(rect); Q_UNUSED(fm); Q_UNUSED(focused); return false; } int KexiTableEdit::rightMargin(bool focused) const { return focused ? m_rightMarginWhenFocused : m_rightMargin; } diff --git a/src/widget/tableview/kexitableedit.h b/src/widget/tableview/kexitableedit.h index a0ce67d7c..d5c4b7e84 100644 --- a/src/widget/tableview/kexitableedit.h +++ b/src/widget/tableview/kexitableedit.h @@ -1,232 +1,232 @@ /* This file is part of the KDE project Copyright (C) 2002 Peter Simonsson 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. */ #ifndef _KEXITABLEEDIT_H_ #define _KEXITABLEEDIT_H_ #include "kexidatatable_export.h" #include #include #include #include class KDbField; class KDbQueryColumnInfo; class KexiTextFormatter; /*! @short Abstract class for a cell editor. Handles cell painting and displaying the editor widget. */ class KEXIDATATABLE_EXPORT KexiTableEdit : public QWidget, public KexiDataItemInterface { Q_OBJECT public: explicit KexiTableEdit(KDbTableViewColumn *column, QWidget* parent = 0); virtual ~KexiTableEdit(); //! Implemented for KexiDataItemInterface. //! \return field information for this item KDbField *field() override; /*! A rich field information for db-aware data. For not-db-aware data it is always 0 (use field() instead. */ KDbQueryColumnInfo *columnInfo() override; //! Implemented for KexiDataItemInterface. //! Does nothing because instead KDbTableViewColumn is used to get field's schema. - virtual void setColumnInfo(KDbQueryColumnInfo *); + void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo *cinfo) override; //! \return column information for this item //! (extended information, comparing to field()). const KDbTableViewColumn *column() const; /*! \return displayed field. This is equal to field() in typical case but can return a different field definition if the column contains a lookup field. This distiction is especially used for displaying data dependent on the type and specifics of the field definition (e.g. text type versus integer type). Note that to compute the editor's value we still use field(). */ const KDbField *displayedField(); /*! Reimplemented: resizes a view(). */ virtual void resize(int w, int h); /*! \return the view widget of this editor, e.g. line edit widget. */ virtual QWidget* widget(); /*! Hides item's widget, if available. */ virtual void hideWidget(); /*! Shows item's widget, if available. */ virtual void showWidget(); /*! Paints a border for the cell described by \a x, \a y, \a w, \a h on \a p painter. The cell's value is \a val (may be useful if you want to reimplement this method). */ virtual void paintFocusBorders(QPainter *p, QVariant &cal, int x, int y, int w, int h); /*! For reimplementation. Sets up and paints cell's contents using context of \a val value. \a focused is true if the cell is focused. \a align is set using Qt::AlignmentFlags. Some additional things may be painted using \a p, but it is not needed to paint the text (this is done automatically outside of this method). Before calling, \a x, \a y_offset, \a w, \a h parameters are initialized, but you can tune these values depending on the context. You should set \a txt to a text representation of \a val, otherwise no text will be painted. \a p can be 0 - in this case no painting should be performed, becasue caller only expects that \a x, \a y_offset, \a w, \a h, \a txt parameters are tuned, if needed. \a p painter's pen is set to foreground color (usually black) that should be used to paint foreground information, if needed. For example boolean editor widget paints a rectangle using this color. */ virtual void setupContents(QPainter *p, bool focused, const QVariant& val, QString &txt, int &align, int &x, int &y_offset, int &w, int &h); /*! \return true if "selected text" color should be used to paint contents of the editor. True by default. It's false e.g. in boolean editor, where no selection is painted using paintSelectionBackground(). This flag is set in editor's constructor and checked in KexiTableView::paintCell(). Depending on it, appropriate ("text" or "selected text" color is set for painter) before setupContents() is called. */ bool usesSelectedTextColor() const; /*! For reimplementation. Paints selection's background using \a p. Most parameters are similar to these from setupContents(). */ virtual void paintSelectionBackground(QPainter *p, bool focused, const QString& txt, int align, int x, int y_offset, int w, int h, const QColor& fillColor, const QFontMetrics &fm, bool readOnly, bool fullRecordSelection); /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains dropdown button at the right side. \return left margin's size; 0 by default. For reimplementation. */ int leftMargin() const; /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains dropdown button at the right side. THe dropdown button's width is counted only if \a focused is true. \return right margin's size; 0 by default. For reimplementation. */ int rightMargin(bool focused) const; /*! Handles \a ke key event that came over the column that is bound to this editor. For implementation: true should be returned if \a ke should be accepted. If \a editorActive is true, this editor is currently active, i.e. the table view is in edit mode. By default false is returned. */ virtual bool handleKeyPress(QKeyEvent* ke, bool editorActive); /*! Handles double click request coming from the table view. \return true if it has been consumed. Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */ virtual bool handleDoubleClick(); /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate for the editor's impementation, e.g. for image cell it can be a pixmap. For a special case (combo box), \a visibleValue can be provided, so it can be copied to the clipboard instead of unreadable \a value. For reimplementation. */ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue) = 0; /*! \return width of \a value. For the default implementation \a val is converted to a string and width of this string is returned. */ virtual int widthForValue(const QVariant &val, const QFontMetrics &fm); /*! \return total size of this editor, including any buttons, etc. (if present). Reimplement this if you want to return more appropriate size. This impelmentation just returns QWidget::size(). */ virtual QSize totalSize() const; /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect for a given font metrics \a fm. \return true a normal tooltip should be displayed (using QToolTip,) and false if no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported). Default implementation does nothing and returns false. If the cell is currentl focused (selected), \a focused is true. */ virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, bool focused); /*! Created internal editor for this editor is needed. This method is only implemented in KexiComboBoxTableEdit since it's visible value differs from internal value, so a different KexiTableEdit object is used to displaying the data. */ - virtual void createInternalEditor(KDbQuerySchema& schema); + virtual void createInternalEditor(KDbConnection *conn, const KDbQuerySchema& schema); Q_SIGNALS: void editRequested(); void cancelRequested(); void acceptRequested(); protected: /*! Sets \a v as view widget for this editor. The view will be assigned as focus proxy for the editor, its events will be filtered, it will be resized when neede, and so on. */ void setViewWidget(QWidget *v); /*! Moves child widget within the viewport if the parent is scrollview (otherwise does nothing). Use this for child widgets that are outside of this editor widget, instead of calling QWidget::move(). */ void moveChild(QWidget * child, int x, int y); /*! Allows to force redrawing the related cell by the editor itself. Usable when the editor is not displayed by a QWidget but rather by table view cell itself, for example KexiBlobTableEdit. */ void repaintRelatedCell(); KDbTableViewColumn * const m_column; KexiTextFormatter* m_textFormatter; int m_leftMargin; int m_rightMargin, m_rightMarginWhenFocused; bool m_usesSelectedTextColor; //!< set in ctor, @see usesSelectedTextColor() private: //! @see widget() QWidget* m_view; }; //! Declaration of cell editor factory #define KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(factoryclassname) \ class factoryclassname : public KexiCellEditorFactoryItem \ { \ public: \ factoryclassname(); \ virtual ~factoryclassname(); \ \ protected: \ virtual KexiTableEdit* createEditor(KDbTableViewColumn *column, QWidget* parent = 0); \ }; //! Implementation of cell editor factory #define KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(factoryclassname, itemclassname) \ factoryclassname::factoryclassname() \ : KexiCellEditorFactoryItem() \ { \ m_className = "" #itemclassname ""; \ } \ \ factoryclassname::~factoryclassname() \ {} \ \ KexiTableEdit* factoryclassname::createEditor( \ KDbTableViewColumn *column, QWidget* parent) \ { \ return new itemclassname(column, parent); \ } #endif