Is it possible to get primary key of selected text from QCompleter - qt

I am using a QCompleter on line edit to get some text. The completer functionality is as such working fine.
The QCompleter is fetching data from Sql Table.
completer = new QCompleter(this);
model = new QSqlRelationalTableModel(this, db);
model->setTable("product");
model->select();
completer->setModel(model);
completer->setCompletionColumn(1); // points to "name" in product table
ui->line_edit->setCompleter(completer);
now on line_edit_returnPressed(), I am able to get the selected text. Is it further possible to get the primary key / row index in Sql Table for the currect selection made from "QCompleter" ?
I see that ui->line_edit->completer()->currentRow(); always return 0.
I am just trying to save one SQL query thats all.

I have to acknowledge #Pavel Strakhov comments, thanks. Had it been put up as answer, I would have accepted it.
The whole time I was using QCompleter::currentIndex with the sql table model I had set with QCompleter::setModel(). I dont know how QCompleter works but I believe it internally derives a list model from the input table model.
From documentation -
QAbstractItemModel* QCompleter::completionModel()
Returns the completion model. The completion model is a read-only list model that contains all the possible matches for the current completion prefix. The completion model is auto-updated to reflect the current completions.
So now my SLOT looks like this -
void MainWindow::on_line_edit_returnPressed()
{
QModelIndex index = ui->le_filter->completer()->currentIndex();
if (index.isValid()) {
int row = index.row();
int key = completer->completionModel()->index(row, 0).data().toInt();
qDebug() << key;
}
}

Related

Datasource Paging Issue (Revised Again)

See Datasource Paging Issue (Revised)
for the original question.
Markus, you were kind enough to help with out with the issue of incorporating a record count into a query using a calculated datasource. I have a search form with 15 widgets - a mix of date ranges, dropdowns, text values and ._contains, ._equals, ._greaterThanOrEquals, ._lessThanOrEquals, etc.
I have tested this extensively against mySQL SQL code and it works fine.
I have now added a 16th parameter PropertyNames, which is a list with binding #datasource.query.filters.Property.PropertyName._in and Options blank. The widget on the form is hidden because it is only used for additional filtering.
Logic such as the following is used, such that a particular logged-in user can only view their own properties. So if they perform a search and the Property is not specified we do:-
if (params.param_Property === null && canViewAllRecords === false) {
console.log(params.param_PropertyNames); // correct output
ds.filters.Property.PropertyName._in = params.param_PropertyNames;
}
The record count (records.length) is correct, and if I for loop through the array of records the record set is correct.
However, on the results page the table displays a larger resultset which omits the PropertyNames filter. So if I was to search on Status 'Open' (mySQL results 50) and then I add a single value ['Property Name London SW45'] for params.param_PropertyNames the record count is 6, the records array is 6 but the datasource display is 50. So the datasource is not filtering on the property array.
Initially I tried without adding the additional parameter and form widget and just using code such as
if (params.param_Property === null && canViewAllRecords === false) {
console.log(params.param_PropertyNames); // correct output
ds.filters.Property.PropertyName._in = properties; // an array of
properties to filter out
}
But this didn't work, hence the idea of adding a form widget and an additional parameter to the calculated recordcount datasource.
If I inspect at query.parameters then I see:-
"param_Status": "Open",
"param_PropertyNames": ["Property Name London SW45"],
If I inspect query.filters:-
name=param_Status, value=Open
name=param_PropertyNames, value=[]}]}
It looks as though the filter isn't set. Even hard coding
ds.filters.Property.PropertyName._in = ['Property Name London SW45'],
I get the same reuslt.
Have you got any idea what would be causing this issue and what I can do for a workaround ?
Using a server side solution I would suggest editing both your SQL datasource query script (server side) that is supposed to filter by this property list and including the same code in your server side script for your calculated Count datasource. The code would look something like this, not knowing your exact details:
var subquery = app.models.Directory.newQuery();
subquery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
subquery.prefetch.Property._add();
var results = subquery.run();
if(!results[0].CanViewAllRecords) {
query.filters.Property.PropertyName._in = results[0].Property.map(function(i) {return i.PropertyName;});
}
By adding this code you are filtering your directory by your current user and prefetching the Property relation table, then you set the filter only if your user canviewallRecords is false and use JS map function to create an array of the PropertyName field in the Property table. As I stated, your code may not be exactly the same depending on how you have to retrieve your user canviewallrecords property and then of course I don't know your relation between user and Property table either, is it one-to-many or other. But this should give you an idea how to implement this on server side.

Qt - Move a table column to beginning first column index on the table

I am using Qt 5.2.1, I have a tableview in which I have enabled sorting of columns by the user, i.e. they can drag and drop columns by the headers to rearrange them. I want to add a right-click option "Move to front", where the user can right click a column header and when the option is clicked, that column will be moved to the leftmost spot on the table (first column index). I have it set up where it gets to my function properly, but I cannot figure out how to make it move the column once it gets to that point. The rearranging works fine with the drag and drop, but I'm not sure how to have it go automatically to a given index.
void SomeClass::moveColumnToFront(int column, QTableView *table) {
// I assume that you are using QStandardItemModel
QStandardItemModel *model = qobject_cast<QStandardItemModel *>(table->model());
if (model) {
QList<QStandardItem *> columndData = model->takeColumn(column);
model->insertColumn(0, columndData);
}
}
You should learn to search documentation! Yes it is possible to do it using QAbstractItemModel API. But remember that model have to support this feature (QStandardItemModel does)! If you are you using some custom model this may not work.
void SomeClass::moveColumnToFront(int column, QTableView *table) {
bool success = table->model()->moveColumn(QModelIndex(), column,
QModelIndex(), 0);
}

qt multiple QSqlTableModels edited together in one transaction

I have a window in a Qt application using PostgreSQL 9.3 database. The window is a form used do display, edit and insert new data. t looks like that:
I have data from 3 sql tables in that view. the tables are related with foreign keys:
contractors (main table) - mapped to "personal data" section
contacts (has foreign key to contractors.ID)
addresses (has foreign key to contractors.ID)
So - in my window's class I have 3 main models (+ 2 proxy models to transpose tables in "personal data" an "address data" sections). I use QSqlTableModel for theese sesctions, and a QSqlRelationalTableModel for contactData section. when opening that window "normally" (to view some contractor), i simply pass contractor's ID to the constructor and store it in proper variable. Also, I call the QSqlTableModel::​setFilter(const QString & filter) method for each of the models, and set the proper filtering. When opening that window in "add new" mode i simply pass a "-1" or "0" value to the ID variable, so no data gets loaded to the model.
All 3 models have QSqlTableModel::OnManualSubmit editStrategy. When saving the data (triggered by clicking a proper button), I start a transaction. And then I submit models one-by-one. personalData model gets submitted first, as I need to obtain it's PK after insert (to set in the FK fields in other models).
When submitting of the model fails, I show a messageBox with the QSqlError content, rollback the transaction and return from the method.
When I have an error on the first model being processed - no problem, as nothing was inserted. But when the first model is saved, but the second or third fails - there is a little problem. So I rollback the transacion as before, and return from the function. But after correcting the data and submitting it again - the first model is not trying to submit - as it doesn't know that there was a rollback, and the data needs to be inserted again. What would be a good way to notice such a model, that it needs to be submited once again?
At the moment I ended up with something like that:
void kontrahenciSubWin::on_btnContractorAdd_clicked() {
//QStringList errorList; // when error occurs in one model - whole transacion gets broken, so no need for a list
QString error;
QSqlDatabase db = QSqlDatabase::database();
//backup the data - in case something fails and we have to rollback the transaction
QSqlRecord personalDataModelrec = personalDataModel->record(0); // always one row. will get erased by SubmitAll, as no filter is set, because I don't have its ID.
QList<QSqlRecord> contactDataModelRecList;
for (int i = 0 ; i< contactDataModel->rowCount(); i++) {
contactDataModelRecList.append( contactDataModel->record(i) );
}
QList<QSqlRecord> addressDataModelRecList;
for (int i = 0 ; i< addressDataModel->rowCount(); i++) {
addressDataModelRecList.append( addressDataModel->record(i) );
}
db.transaction();
if ( personalDataModel->isDirty() && error.isEmpty() ) {
if (!personalDataModel->submitAll()) //submitAll calls select() on the model, which destroys the data as the filter is invalid ("where ID = -1")
//errorList.append( personalDataModel->lastError().databaseText() );
error = personalDataModel->lastError().databaseText();
else {
kontrahentid = personalDataModel->query().lastInsertId().toInt(); //only here can I fetch ID
setFilter(ALL); //and pass it to the models
}
}
if ( contactDataModel->isDirty() && error.isEmpty() )
if (!contactDataModel->submitAll()) //slot on_contactDataModel_beforeInsert() sets FK field
//errorList.append( contactDataModel->lastError().databaseText() );
error = contactDataModel->lastError().databaseText();
if ( addressDataModel->isDirty() && error.isEmpty() )
if (!addressDataModel->submitAll()) //slot on_addressDataModel_beforeInsert() sets FK field
//errorList.append( addressDataModel->lastError().databaseText() );
error = addressDataModel->lastError().databaseText();
//if (!errorList.isEmpty()) {
// QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + errorList.join("\n"));
if (!error.isEmpty()) {
QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + error);
db.rollback();
personalDataModel->clear();
contactDataModel->clear();
addressDataModel->clear();
initModel(ALL); //re-init models: set table and so on.
//re-add data to the models - backup comes handy
personalDataModel->insertRecord(-1, personalDataModelrec);
for (QList<QSqlRecord>::iterator it = contactDataModelRecList.begin(); it != contactDataModelRecList.end(); it++) {
contactDataModel->insertRecord(-1, *it);
}
for (QList<QSqlRecord>::iterator it = addressDataModelRecList.begin(); it != addressDataModelRecList.end(); it++) {
addressDataModel->insertRecord(-1, *it);
}
return;
}
db.commit();
isInEditMode = false;
handleGUIOnEditModeChange();
}
Does anyone have a better idea? I doubt if it's possible to ommit backing-up the records before trying to insert them. But maybe there is a better way to "re-add" them to the model? I tried to use "setRecord", and "remoweRows" & "insertRecord" combo, but no luck. Resetting the whole model seems easiest (I only need to re-init it, as it loses table, filter, sorting and everything else when cleared)
I suggest you to use a function written in the language PLPGSQL. It has one transaction between BEGIN and END. If it goes wrong at a certain point of the code then will it rollback all data flawlessly.
What you are doing now is not a good design, because you handle the control over a certain functionality (rollback) to an external system with regard to the rollback (it is happening in the database). The external system is not designed to do that, while the database on the contrairy is created and designed for dealing with rollbacks and transactions. It is very good at it. Rebuilding and reinventing this functionality, which is quite complex, outside the database is asking for a lot of trouble. You will never get the same flawless rollback handling as you will have using functions within the database.
Let each system do what it can do best.
I have met your problem before and had the same line of thought to work this problem out using Hibernate in my case. Until I stepped back from my efforts and re-evaluated the situation.
There are three teams working on the rollback mechanism of a database:
1. the men and women who are writing the source code of the database itself,
2. the men and women who are writing the Hibernate code, and
3. me.
The first team is dedicated to the creation of a good rollback mechanism. If they fail, they have a bad product. They succeeded. The second team is dedicated to the creation of a good rollback mechanism. Their product is not failing when it is not working in very complex situations.
The last team, me, is not dedicated to this problem. Who am I to write a better solution then the people of team 2 or team 1 based on the work of team 2 who were not able to get it to the level of team 1?
That is when I decided to use database functions instead.

Checking then Adding items to QCompleter model

I am currently working on a code editor written in Qt,
I have managed to implement most of the features which I desire, i.e. auto completion and syntax highlighting but there is one problem which I can't figure out.
I have created a model for which the QCompleter uses, which is fine for things like html tags and c++ keywords such as if else etc.
But I would like to add variables to the completer as they are entered by the user.
So I created an event on the QTextEdit which will get the word (I know I need to check to make sure that it is a variable etc but I just want to get it working for now).
void TextEdit::checkWord()
{
//going to get the previous word and try to do something with it
QTextCursor tc = textCursor();
tc.movePosition(QTextCursor::PreviousWord);
tc.select(QTextCursor::WordUnderCursor);
QString word = tc.selectedText();
//check to see it is in the model
}
But now I want to work out how to check to see if that word is already in the QCompleters model and if it isn't how do I add it?
I have tried the following:
QAbstractItemModel *m = completer->model();
//dont know what to do with it now :(
You can check if word is in your QCompleter really by using
QAbstractItemModel *m = completer->model();
as you can see, method model() returns const pointer.
That is good for checking procedure, you can check like this:
bool matched = false;
QString etalon("second");
QStringListModel *strModel = qobject_cast<QStringListModel*>(completer.model());
if (strModel!=NULL)
foreach (QString str, strModel->stringList()) {
if (str == etalon)
{
matched = true;
break;
}
}
qDebug()<<matched;
But for your purposes, I recommend you to declare QStringListModel, and connect it to your completer, and then, all of operations you'll must do thru your model, according to Qt's principles of MVC programming (http://doc.qt.digia.com/qt/model-view-programming.html).
Your code can be like this:
// declaration
QCompleter completer;
QStringListModel completerModel;
// initialization
completer.setModel(&completerModel);
QStringList stringListForCompleter;
stringListForCompleter << "first" << "second" << "third";
completerModel.setStringList(stringListForCompleter);
// adding new word to your completer list
completerModel.setStringList(completerModel.stringList() << "New Word");
Good luck!

Qt - How to associate data with QTableWidgetItem?

I want to associate additional data with each QTableWidgetItem inserted into the table, in order to use that data in future, when it is being clicked on a table item. But that data should not be visible. How can I do that?
You can use QTableWidgetItem::setData() like so:
setData(Qt::UserRole, myData); // set
Where myData is a supported QVariant type. You can use QTableWidgetItem::data() to retrieve the value that you store.
If you need more than one you can use Qt::UserRole + 1, + 2, and so on (Qt::UserRole is "The first role that can be used for application-specific purposes.", you can read more about the other types of roles here).
If you're storing a custom type that isn't natively supported by QVariant you will need to register your type with the Qt meta-object system. Look at QMetaType for more details on that.
If you wanted to store an integer, for example:
QTableWidgetItem* widgetItem = tableWidget->item(row, col); // get the item at row, col
int myInteger = 42;
widgetItem->setData(Qt::UserRole, myInteger);
// ...
myInteger = widgetItem->data(Qt::UserRole);
You could derive from QTableItem and provide your own data member, or you could use the QTableView with your own model.

Resources