Public Member Functions | Protected Slots | Protected Member Functions

MappedBaseEditor Class Reference

Specific implementation of BaseEditor for use with widgets. More...

#include <baseeditor.h>

Inheritance diagram for MappedBaseEditor:
Inheritance graph
[legend]
Collaboration diagram for MappedBaseEditor:
Collaboration graph
[legend]

List of all members.

Public Member Functions

 MappedBaseEditor (QWidget *parent=0)
const QSqlRecordcurrentPrimaryKey ()
const int currentMapperRow ()
void setTable (const QString &table, const QString &fieldName=QString(), Qt::SortOrder order=Qt::AscendingOrder, const QString &filter=QString())
void select ()
virtual bool isDirty () const
SqlTableModelmodel ()
DataWidgetMappermapper ()
void setCurrent (const QString &fieldIdName, const QVariant &fieldId)

Protected Slots

virtual void onCurrentRecordChange (int row)
virtual void onQueryChange ()
virtual void onNewButtonPress ()
virtual void onDeleteButtonPress ()
void onUndoButtonPress ()
virtual void onSubmitButtonPress ()
virtual void updateButtons ()

Protected Member Functions

void addWidgetMapping (QWidget *widget, const QString &mapFieldName, const QByteArray &propertyName="")
void addWidget (QWidget *widget, int row, int column, int rowSpan, int columnSpan)
void addWidget (QWidget *widget, int row, int column, int rowSpan, int columnSpan, const QString &mapFieldName, const QByteArray &propertyName="")
void addWidget (QComboBox *widget, int row, int column, int rowSpan, int columnSpan, const QSqlRelation &relation, const QString &fkColumnName=QString(), const QByteArray &propertyName="")
WidgetLayoutwidgetLayout ()
bool isCurrentRecordNew () const

Detailed Description

Specific implementation of BaseEditor for use with widgets.


Constructor & Destructor Documentation

MappedBaseEditor::MappedBaseEditor ( QWidget parent = 0  ) 

                                                            : BaseEditor(parent), m_currentRecordIsNew(false)
{
    // member initialization
    m_widgetLayout = new WidgetLayout();
    m_mapper = new DataWidgetMapper(this);
    m_model = new SqlTableModel(this, Database::staticCon());

    // model setup
    m_model->setEditStrategy(QSqlTableModel::OnRowChange);

    // button layout setup
    QVBoxLayout *buttonLayout = new QVBoxLayout();
    buttonLayout->addWidget(button(BaseEditor::ButtonNext));
    buttonLayout->addWidget(button(BaseEditor::ButtonPrevious));
    buttonLayout->addStretch();
    buttonLayout->addWidget(button(BaseEditor::ButtonNew));
    buttonLayout->addWidget(button(BaseEditor::ButtonDelete));
    buttonLayout->addStretch();
    buttonLayout->addWidget(button(BaseEditor::ButtonUndo));
    buttonLayout->addWidget(button(BaseEditor::ButtonSubmit));
    buttonLayout->addStretch(1);
    buttonLayout->addWidget(button(BaseEditor::ButtonClose));

    // setup widget groupBox
    QGroupBox *wBox = new QGroupBox("Record", this);
    wBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    wBox->setLayout(m_widgetLayout);

    // setup seeker
    Seeker *seeker = new Seeker(this);
    seeker->setModel(m_model);
    seeker->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);

    // main layout setup
    mainLayout()->addWidget(seeker, 0, 0, 1, 2);
    mainLayout()->addWidget(wBox, 1, 0, 1, 1);
    mainLayout()->addLayout(buttonLayout, 1, 1, 2, 1);

    // connection setup
    connect(button(BaseEditor::ButtonPrevious), SIGNAL(clicked()),
            m_mapper, SLOT(toPrevious()));
    connect(button(BaseEditor::ButtonNext), SIGNAL(clicked()),
            m_mapper, SLOT(toNext()));
    connect(button(BaseEditor::ButtonDelete), SIGNAL(clicked()),
            this, SLOT(onDeleteButtonPress()));
    connect(m_mapper, SIGNAL(currentIndexChanged(int)),
            this, SLOT(onCurrentRecordChange(int)));
    connect(m_model, SIGNAL(queryChanged()),
            this, SLOT(onQueryChange()));
    connect(seeker, SIGNAL(found(QModelIndex)),
            m_mapper, SLOT(setCurrentModelIndex(QModelIndex)));

    // mapper setup
    m_mapper->setModel(m_model);
    m_mapper->setSubmitPolicy(DataWidgetMapper::ManualSubmit);
    m_mapper->setOrientation(Qt::Horizontal);
    m_mapper->setItemDelegate(new MapperDelegate(this));
}


Member Function Documentation

void MappedBaseEditor::addWidget ( QWidget widget,
int  row,
int  column,
int  rowSpan,
int  columnSpan 
) [protected]
void MappedBaseEditor::addWidget ( QWidget widget,
int  row,
int  column,
int  rowSpan,
int  columnSpan,
const QString mapFieldName,
const QByteArray propertyName = "" 
) [protected]

{
    addWidget(widget, row, column, rowSpan, columnSpan);
    addWidgetMapping(widget, mapFieldName, propertyName);
}

void MappedBaseEditor::addWidget ( QComboBox widget,
int  row,
int  column,
int  rowSpan,
int  columnSpan,
const QSqlRelation relation,
const QString fkColumnName = QString(),
const QByteArray propertyName = "" 
) [protected]

{
    Q_ASSERT(m_mapper);

    QString fkColumnN = fkColumnName.isNull() ? relation.indexColumn() : fkColumnName;
    int fkColumn = m_model->fieldIndex(fkColumnN);
    Q_ASSERT(fkColumn != -1 || !Database::staticCon().isOpen()); //Relations indexColumn should exist in model als fk!
    m_model->setRelation(fkColumn, relation); //Hereafter, the query()-string changes to reflect the relation at fkIndex, so select() must be executed to update the query().

    QSqlTableModel *widgetModel = m_model->relationModel(fkColumn);
    Q_ASSERT(widgetModel);
    widget->setModel(widgetModel);
    widget->setModelColumn(widgetModel->fieldIndex(relation.displayColumn()));

    addWidget(widget, row, column, rowSpan, columnSpan, fkColumnN, propertyName);
}

void MappedBaseEditor::addWidgetMapping ( QWidget widget,
const QString mapFieldName,
const QByteArray propertyName = "" 
) [protected]

Referenced by addWidget(), and UserEditor::UserEditor().

{
    Q_ASSERT(m_mapper);

    // setup vars
    int mapFieldIndex = m_model->fieldIndex(mapFieldName);

    // setup mapper
    m_mapper->addMapping(widget, mapFieldIndex, propertyName);
    m_mapper->revert(); //mapper doesn't populate the widget with the data: revert() or toFirst() must be called. revert() only calls d->populate() so it's the prefered way;

    // setup completer
    if (QLineEdit *edit = qobject_cast<QLineEdit*>(widget)){
        QCompleter *nameCompleter = new QCompleter(m_model, edit);
        nameCompleter->setCompletionColumn(mapFieldIndex);
        nameCompleter->setCaseSensitivity(Qt::CaseInsensitive); //cs or ci? depends on feedback.
        nameCompleter->setCompletionMode(QCompleter::InlineCompletion); //should be the default, but the problem is that it also shows duplicate items
        edit->setCompleter(nameCompleter);
    }
}

const int MappedBaseEditor::currentMapperRow (  ) 

{
    m_mapper->currentIndex();
}

const QSqlRecord & MappedBaseEditor::currentPrimaryKey (  ) 

Referenced by onSubmitButtonPress().

{
    return m_currentPrimaryKey;
}

bool MappedBaseEditor::isCurrentRecordNew (  )  const [protected]

Referenced by SensorEditor::updateButtons(), SensorChannelEditor::updateButtons(), and updateButtons().

{
    return m_currentRecordIsNew;
}

bool MappedBaseEditor::isDirty (  )  const [virtual]

Implements BaseEditor.

Referenced by updateButtons().

{
    return m_mapper->isDirty();
}

DataWidgetMapper * MappedBaseEditor::mapper (  ) 

{
    return m_mapper;
}

SqlTableModel * MappedBaseEditor::model (  ) 
void MappedBaseEditor::onCurrentRecordChange ( int  row  )  [protected, virtual, slot]

Reimplemented in UserEditor, ProjectEditor, SensorChannelEditor, and SensorEditor.

Referenced by MappedBaseEditor().

{
    updateCurrentPrimaryKey();
    updateButtons();
}

void MappedBaseEditor::onDeleteButtonPress (  )  [protected, virtual, slot]

Referenced by MappedBaseEditor().

{
    if (m_currentRecordIsNew){
        m_model->revert();
        m_mapper->setCurrentIndex(m_model->rowCount()-1);
        m_currentRecordIsNew = false;
    } else {
        int button = QMessageBox::warning(this, "Delete", "Are you sure you wish to delete the current record?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
        if (button == QMessageBox::No)
            return;

        int idAboutToBeDeleted = m_mapper->currentIndex();
        if (!m_model->removeRow(idAboutToBeDeleted)) { //calls onQueryChange() implicitely!
            qWarning() << "Record could not be deleted! \n " << m_model->lastError().text();
            return;
        } else {
            m_mapper->setCurrentIndex(m_model->rowCount() == idAboutToBeDeleted ? idAboutToBeDeleted - 1 : idAboutToBeDeleted);
            Database::notifyTableChange(m_model->tableName());
        }
    }
    updateButtons();
}

void MappedBaseEditor::onNewButtonPress (  )  [protected, virtual, slot]

Implements BaseEditor.

Reimplemented in SensorChannelCalibrationEditor, and SensorChannelEditor.

{
    // Notes on model->insertRecord(-1, QSqlRecord());:
    // - Geeft QSqlError(-1, "No Fields to update", "") bij strategy: QSqlTableModel::OnRowChange
    // - De nieuwe QModelIndexes worden ook niet gemarkeerd als dirty
    // - Doorgegeven QSqlRecord() wordt aangepast (i.e. krijgt extra fields)
    //   en dus geldt niet: model->record(model->rowCount() - 1) == QSqlRecord(),
    //   Verder geeft QSqlRecord::isNull "true" voor elke field. (Dit kan als toets gebruikt worden.)
    // - Er wordt maar 1 record achteraan gecreëerd met PK == 0,
    //   ongeacht hoeveel keer men model->insertRecord(-1, QSqlRecord()); uitvoert
    // - Because this makes an empty record in the model itself, it can not be
    //   reverted by mapper.
    // - Return value is always false (so its not written to the DB), but is still cached
    //   so it can be undone with model->revert() or deleted.
    m_model->insertRecord(-1, QSqlRecord());
    Q_ASSERT(m_model->rowCount() - 1 > -1);
    m_currentRecordIsNew = true;
    m_mapper->setCurrentIndex(m_model->rowCount()-1);
    updateButtons();
}

void MappedBaseEditor::onQueryChange (  )  [protected, virtual, slot]

This function is primarily due to a bug in Qt where the mappers indexes become invalid after Model::select() is called, which also emits queryChanged(). cf. http://bugreports.qt.nokia.com/browse/QTBUG-1086 One consequence of this is that mapper->currentIndex() == -1 so it can not be used as currentIndex.

Todo:
Check whether MappedBaseEditor::onQueryChange() is still required. If not, delete.

Reimplemented in SensorEditor.

Referenced by MappedBaseEditor().

{
    // This is no longer required, as submit calls it's own updateButtons()
//    // An issue arises when one calls submit() and it is successfull, and this function is
//    // executed sooner than m_currentRecordIsNew is set to false. This gives a wrong effect
//    // because it's used in updateButtons(). I think it is safe to set
//    // it to false when it's called from the model. An other way to do this is to "slow down" the
//    // emitting process of the model, but Qt::QueuedConnection didn't seem to work.
//    if (sender() == model) m_currentRecordIsNew = false;

    //qDebug() << "Executing onQueryChange()";
    bool modelIsEmpty = m_model->rowCount() == 0;
    int newIndex;

    if (modelIsEmpty) m_mapper->revert(); //because the mapper will otherwise not empty the widgets

    if (m_currentPrimaryKey.isEmpty()){ //model was empty || it's a view without pk set!
        Q_ASSERT(m_mapper->currentIndex() == -1);
        if (m_model->rowCount() != 0) newIndex = 0;
    } else {
        newIndex = seekRow(m_model->query(), m_currentPrimaryKey);
        if (newIndex == -1) //an item has been deleted
            newIndex = 0;
    }

    Q_ASSERT(newIndex > -1);
    m_mapper->setCurrentIndex(newIndex);
    updateButtons();
}

void MappedBaseEditor::onSubmitButtonPress (  )  [protected, virtual, slot]

Implements BaseEditor.

{
    bool isUpdate = !m_currentRecordIsNew;

    // Confirm update
    if (isUpdate){
        // Hier zou eventueel ook kunnen geschreven worden welke veranderingen er
        // aangebracht gaan worden, maar is niet op één twee drie te implementeren.
        // DataWidgetMapper zou extended moeten worden voor het geven van QVariant van
        // elke widget waarvoor em moet bemiddelen, of iets dergelijk.
        int button = QMessageBox::warning(this, "Update", "Are you sure you wish to update the current record?", QMessageBox::Yes | QMessageBox::No | QMessageBox::Discard, QMessageBox::No);
        if (button == QMessageBox::No)
            return;
        if (button == QMessageBox::Discard){
            onUndoButtonPress();
            return;
        }
    }

    // Submit data
    // - Note that submit() submits all the data from the mapped widgets. This
    //   corresponds to submitting only one row!
    // - Note also that submit() internaly calls model()::submit() which only works
    //   with OnRowChange or OnFieldChange, but does nothing for the OnManualSubmit
    //   strategy. So make sure the model's strategy is set on OnRowChange.
    // - Note also that submitting executes model()'s queryChanged() and thus onQueryChanged()
    if (!m_mapper->submit()) { //is false only when the QModelIndex is invalid;
        qWarning() << "Record could not be submitted: " << m_model->lastError();

        // Model caches the changes, and model::revert() could be called here.
        // But then the new record is ereased and the currentIndex points to an
        // invalid index: QModelIndex of the current row is invalid. It only can be
        // used on update.
        if (isUpdate) m_model->revert();

        updateButtons();
        return;
    }

    Database::notifyTableChange(m_model->tableName());

    // Revert to the just submitted record.
    // There are two options here: either it was a submit, or it was an update.
    int row = -1;
    if (isUpdate)
        row = seekRow(m_model->query(), currentPrimaryKey());
    else
        row = seekRow(m_model->query(), Database::lastInserted(m_model->tableName()));
    if (row == -1){
        qDebug() << "Last inserted record not found for table " << m_model->tableName();
        row = 0; // vroeger stond hier een Q_ASSERT: probleem is dat indien lastInserted niets oplevert, zoals bij tbl_users, proggie blokt
    }

    m_mapper->setCurrentIndex(row);
    m_currentRecordIsNew = false;
    updateButtons();
}

void MappedBaseEditor::onUndoButtonPress (  )  [protected, virtual, slot]

Implements BaseEditor.

Referenced by onSubmitButtonPress().

{
    m_mapper->revert();
    updateButtons();
}

void MappedBaseEditor::select (  ) 
void MappedBaseEditor::setCurrent ( const QString fieldIdName,
const QVariant fieldId 
)

Referenced by SensorChannelCalibrationEditor::SensorChannelCalibrationEditor(), and SensorChannelEditor::SensorChannelEditor().

{
    // seek specific record
    QSqlRecord record;
    QSqlField field(fieldIdName, fieldId.type());
    record.append(field);
    record.setValue(field.name(), fieldId);
    int rowNumber = seekRow(model()->query(), record);
    Q_ASSERT(rowNumber != -1);
    m_mapper->setCurrentIndex(rowNumber);
}

void MappedBaseEditor::setTable ( const QString table,
const QString fieldName = QString(),
Qt::SortOrder  order = Qt::AscendingOrder,
const QString filter = QString() 
)

Referenced by InstallationEditor::InstallationEditor(), LocationEditor::LocationEditor(), ProjectEditor::ProjectEditor(), SensorCategoryEditor::SensorCategoryEditor(), SensorChannelCalibrationEditor::SensorChannelCalibrationEditor(), SensorChannelEditor::SensorChannelEditor(), SensorEditor::SensorEditor(), UnitEditor::UnitEditor(), and UserEditor::UserEditor().

{
    m_model->setTable(table); //resets order, filter, etc...
    m_model->setSort(m_model->fieldIndex(fieldName), order);
    m_model->setFilter(filter);
    if (!Database::execStaticR(QString("SELECT fnc_has_edit_privileges('").append(table).append("'::regclass)")).value(0).toBool())
        qWarning() << "You do not have full privileges for editing this table. Please contact the administrator if you want this restriction changed.";
}

void MappedBaseEditor::updateButtons (  )  [protected, virtual, slot]

Implements BaseEditor.

Reimplemented in SensorChannelEditor, and SensorEditor.

Referenced by InstallationEditor::InstallationEditor(), LocationEditor::LocationEditor(), onCurrentRecordChange(), onDeleteButtonPress(), onNewButtonPress(), onQueryChange(), onSubmitButtonPress(), onUndoButtonPress(), ProjectEditor::ProjectEditor(), SensorCategoryEditor::SensorCategoryEditor(), SensorChannelCalibrationEditor::SensorChannelCalibrationEditor(), UnitEditor::UnitEditor(), and UserEditor::UserEditor().

{
    //qDebug() << "Button update requested by: " << sender();
    Q_ASSERT(m_model);
    Q_ASSERT(m_mapper);

    bool newRecord = isCurrentRecordNew();
    bool modelNotEmpty = m_model->rowCount() > 0;
    bool editorDirty = isDirty();

    button(BaseEditor::ButtonSubmit)->setEnabled(editorDirty || newRecord);
    button(BaseEditor::ButtonUndo)->setEnabled(editorDirty);
    button(BaseEditor::ButtonPrevious)->setEnabled(m_mapper->currentIndex() > 0);
    button(BaseEditor::ButtonNext)->setEnabled(m_mapper->currentIndex() < m_model->rowCount() - 1);
    button(BaseEditor::ButtonNew)->setEnabled(!newRecord || !modelNotEmpty);
    button(BaseEditor::ButtonDelete)->setEnabled(modelNotEmpty);

    // special cases, to do: make conditionals per button. (= possible?)
    if (newRecord){
        button(BaseEditor::ButtonNext)->setEnabled(!newRecord);
        button(BaseEditor::ButtonPrevious)->setEnabled(!newRecord);
    }

    // Update submit button name
    newRecord ? button(BaseEditor::ButtonSubmit)->setText("Submit") : button(BaseEditor::ButtonSubmit)->setText("Update");

    m_widgetLayout->setWidgetsEnabled(modelNotEmpty);
}

WidgetLayout * MappedBaseEditor::widgetLayout (  )  [protected]

{
    return m_widgetLayout;
}


The documentation for this class was generated from the following files: