using QString as argmuent in SLOT macro - qt

Is it possible to be used as an argument QString in SLOT macro?
PS. I mean a simple solution .. Not as in QMetaObject::connectSlotsByName().

No you cannot pass QString to SLOT macro. But you can use QString for connect. Also connect cannot take QString, so you have to convert it to const char *. Simple example is:
QString slotName = SLOT(clicked());
connect(ui->pushButton, SIGNAL(clicked()), qPrintable(slotName));
SLOT just stringifies passed parameter and concatenate it with 1:
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
If you don't want to use SLOT it's possible to write code like this:
QString slotName = QString::number(QSLOT_CODE) + "clicked()";

This is raw code from my current project. It parses chat commands like /me customtext and calls cmd_me( const QString& params ); slot.
To introduce new command it's enough to create private slot with void cmd_*( const QString& ); signature.
Here is code:
void ConsoleController::onCommand( const QString& cmd )
{
if ( cmd.length() < 1 )
return ;
if ( cmd[0] != '/' )
return ;
const QMetaObject *meta = metaObject();
int posParam = cmd.indexOf( ' ' );
if ( posParam == -1 )
posParam = cmd.length();
const QString command = cmd.mid( 1, posParam - 1 ).toLower();
const QString params = cmd.mid( posParam + 1 );
const QString slotName = QString( "cmd_%1( const QString& )" ).arg( command );
const QString normalizedSlotName = QMetaObject::normalizedSignature( slotName.toStdString().c_str() );
for ( int i = 0; i < meta->methodCount(); i++ )
{
QMetaMethod method = meta->method( i );
if ( method.methodType() != QMetaMethod::Slot )
continue;
const QString signature = method.signature();
if ( normalizedSlotName == signature )
{
method.invoke( this, Q_ARG( QString, params ) );
return ;
}
}
log( QString( "Command \"%1\" not recognized, type /help to list all available commands" ).arg( command ) );
}
You can take an idea and adapt it for your needs.

Related

How to refresh a QFileSystemModel in a QTreeView after files change through another process?

I have a QTreeView with a QFileSystemModel as the model. Files and directories load correctly.
In my application workflow, a different process copies and overwrites files on the file system.
However, my QTreeView does not update the item/row for the overwritten file (eg: the size and lastModified values for the file do not update to the new values).
Using the file path, I'm able to get a FileInfo that DOES have the updated lastModified value. However, using that same path to grab the QModelIndex of the row's lastModified value results in it returning the old value.
I've tried a few things (see below) to no avail.
PLEASE let me know if you are aware of how to resolve this. Many Thanks! :)
// ... at this point the file system has been updated and the file
// has new values for things like last modified date
QFileInfo *updatedFile = new QFileInfo( filePath );
QModelIndex indexOfFileName = myFileTreeView->myFileSystemModel->index( filePath );
QModelIndex indexOfLastModifiedDate = myFileTreeView->myFileSystemModel->index( filePath, lastModifiedColumnIndex );
// attempts to kick the QFileSystemModel and QTreeView to update the model values
// a collection from the internet :)
emit myFileTreeView->dataChanged( indexOfFileName, indexOfLastModifiedDate );
emit myFileTreeView->myFileSystemModel->dataChanged( indexOfFileName, indexOfLastModifiedDate );
myTreeView->repaint();
myTreeView->update( indexOfLastModifiedDate );
myTreeView->viewport()->update();
// looking to see if values changed
QModelIndex newIndexOfLastModifiedDate = myTreeView->myFileSystemModel->index( filePath, lastModifiedColumnIndex );
// this shows the correct row and column for the index, so the index looks correct
qDebug() << "newIndexOfLastModifiedDate: " << newIndexOfLastModifiedDate;
// does not have new value
qDebug() << "newIndexOfLastModifiedDate.data(): " << newIndexOfLastModifiedDate->data();
// has new value
qDebug() << "fileInfo.lastModified(): " << updatedFile->lastModified();
[EDIT - adding steps to simulate application workflow]
The following I believe to be the steps that can mimic the issue.
Steps to Reproduce:
Setup a simple QTreeView that uses a QFileSystemModel as its model.
Set Root Path to a directory called Root
Create a text file, Test.txt inside of the Root dir
Load the application and observe in it the Test.txt file's Last Modified Date in the Root dir.
Keep this application window open.
Copy Test.txt to a different directory, say Temp
Modify the file and save in Temp
Copy and Replace Test.txt to overwrite the file in Root dir
Observe the Last Modified Date in the application window
Result: The Last Modified Date is not updated
ADDED SAPMLE:
#include <QApplication>
#include <QFileSystemModel>
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QDebug>
#include <QTreeView>
#include <QDateTime>
// Globals
QFileSystemModel *model = NULL;
const QString name = "test.txt";
const int delayOffset = 1000;
// Interface
void onDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles );
void clean();
void doCreateFile();
void doUpdateFile();
void doTest();
// Implementation
int main( int argc, char *argv[] )
{
QApplication a( argc, argv );
int delay = 0;
// Clean
clean();
// Init model
const QString rootPath = QCoreApplication::applicationDirPath();
model = new QFileSystemModel( &a );
model->setRootPath( rootPath );
QObject::connect( model, &QFileSystemModel::dataChanged, &onDataChanged );
// Init file actions
delay += delayOffset * 2;
QTimer tCreate;
tCreate.setSingleShot( true );
tCreate.setInterval( delay );
QObject::connect( &tCreate, &QTimer::timeout, &doCreateFile );
delay += delayOffset * 4;
QTimer tUpdate;
tUpdate.setSingleShot( true );
tUpdate.setInterval( delay );
QObject::connect( &tUpdate, &QTimer::timeout, &doUpdateFile );
// GUI
QTreeView w;
w.setModel( model );
w.setRootIndex( model->index( rootPath ) );
w.show();
w.expandAll();
qDebug() << "Start";
tCreate.start();
tUpdate.start();
return a.exec();
}
void onDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles )
{
qDebug() << "Model changed";
}
void clean()
{
const QString path = QString( "%1/%2" ).arg( QCoreApplication::applicationDirPath() ).arg( name );
QFile f( path );
if ( f.exists() )
f.remove();
}
void doCreateFile()
{
const QString path = QString( "%1/%2" ).arg( QCoreApplication::applicationDirPath() ).arg( name );
QFile f( path );
if ( !f.open( QIODevice::WriteOnly ) )
{
qDebug() << "Could not create file";
return ;
}
f.write( "Preved" );
f.close();
qDebug() << "File created";
doTest();
}
void doUpdateFile()
{
const QString path = QString( "%1/%2" ).arg( QCoreApplication::applicationDirPath() ).arg( name );
QFile f( path );
if ( !f.open( QIODevice::Append ) )
{
qDebug() << "Could not open file for modification";
return ;
}
f.write( " medved" );
f.close();
qDebug() << "File updated";
doTest();
}
void doTest()
{
const QString path = QString( "%1/%2" ).arg( QCoreApplication::applicationDirPath() ).arg( name );
QFileInfo info( path );
qDebug() << "Last modified (real): " << info.lastModified().toString( "hh:mm:ss" );
qDebug() << "Last modified (model): " << model->lastModified( model->index( path ) ).toString( "hh:mm:ss" );
}
You should use QFileSystemWatcher to track events for each file, when it necessary to update your model.
QFileSystemWatcher does not track an events because of performance issues. This is known issue. So you may create your own model and/or use proposed workaround:
model.setRootPath("");
model.setRootPath("the_folder_you_want_to_update");

QTableView::edit( const QModelIndex &index ) failed

I am trying to prevent user from entering the same data into my model, which is subclassed from QAbstractTableModel.
bool MyModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
bool result = false;
...
// Test if my model already has the same data
result = findItem( value.toString() ) != -1;
...
if ( result )
emit( dataChanged( index, index );
else
emit ( dataInvalid( index ) );
return result;
}
Now I should catch the signal and turn my table view (which type is QTableView) back to editing state:
void MyWindow::dataInvalid( const QModelIndex &index )
{
myTableView->edit( index );
}
But when I run my application I got message in the console and QTableView does not turn to editing state:
edit: edit failed
What am I doing wrong?
Thank you very much in advance.
When calling
myTableView->edit( index )
my view is still in QAbstractItemView::EditState and that is the reason of failure.
Solution is to add Qt::QueuedConnection when connecting to signal:
MyWindow::MyWindow()
{
...
connect( myModel, SIGNAL( dataInvalid( QModelIndex ) ),
this, SLOT( dataInvalid( QModelIndex ) ), Qt::QueuedConnection );
...
}
Now everything works fine.

Populate QTreeWidget with hierarchic structure from absolute filepaths

I have a file which has absolute filepaths listed, 1 per line. The listed files are in order, so all files in e.g. the /Documents/ dir will be listed after eachother in the file.
What I want to do is to place all these files in a QTreeWidget in a nice hierarchic structure, just like a normal filesystem. How would I do that from my file of absolute paths I have?
This is how far I've gotten with my coding on this:
QFile file(FILENAME_ENCRYPTED);
QString line;
QDir dir;
QTreeWidgetItem *item;
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream( &file );
do {
line = stream.readLine();
if (!line.isNull()) {
dir = QDir(line);
item = new QTreeWidgetItem();
item->setText(0, dir.dirName());
this->ui->treeWidget->addTopLevelItem(item);
}
} while (!line.isNull());
}
file.close();
This works fine, but it only lists all the filenames after eachother. I guess I have to do some recursive function but recursion is not my best friend, I prefer iteration! Could someone give me a push in the right direction? :)
No recursion should be necessary. You can use QString::split() to split the file path into separate QStrings in a QStringList based on a separator (i.e., "/"), then iterate through each QString to determine the file structure.
EDIT: Here is an example:
#include <QtGui>
const QString s1 = "Docs/Testing/textFile1.txt";
const QString s2 = "Docs/Testing/textFile2.txt";
const QString s3 = "Docs/Testing/textFile3.txt";
const QString s4 = "Docs/Testing/AnotherFolder/textFile4.txt";
const QString s5 = "ThisIsGonnaBeCrazy/WholeNewFolder/AndAnother/file.pdf";
const QString s6 = "ThisIsGonnaBeCrazy/file.doc";
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
QTreeWidget *treeWidget = new QTreeWidget;
QStringList fileNames;
fileNames << s1 << s2 << s3 << s4 << s5 << s6;
QTreeWidgetItem *topLevelItem = NULL;
foreach (const QString &fileName, fileNames)
{
QStringList splitFileName = fileName.split("/");
// add root folder as top level item if treeWidget doesn't already have it
if (treeWidget->findItems(splitFileName[0], Qt::MatchFixedString).isEmpty())
{
topLevelItem = new QTreeWidgetItem;
topLevelItem->setText(0, splitFileName[0]);
treeWidget->addTopLevelItem(topLevelItem);
}
QTreeWidgetItem *parentItem = topLevelItem;
// iterate through non-root directories (file name comes after)
for (int i = 1; i < splitFileName.size() - 1; ++i)
{
// iterate through children of parentItem to see if this directory exists
bool thisDirectoryExists = false;
for (int j = 0; j < parentItem->childCount(); ++j)
{
if (splitFileName[i] == parentItem->child(j)->text(0))
{
thisDirectoryExists = true;
parentItem = parentItem->child(j);
break;
}
}
if (!thisDirectoryExists)
{
parentItem = new QTreeWidgetItem(parentItem);
parentItem->setText(0, splitFileName[i]);
}
}
QTreeWidgetItem *childItem = new QTreeWidgetItem(parentItem);
childItem->setText(0, splitFileName.last());
}
setCentralWidget(treeWidget);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Note that you can use QTreeWidgetItem::setData() to set the file name for each file if you like. My example does not do that, though.
This works for me:
QStringList tokens = path.split ( '/' );
QTreeWidgetItem* treeWidgetItem = NULL;
for ( int32_t j = 0 ; j < packageTreeWidget->topLevelItemCount(); ++j )
{
if ( packageTreeWidget->topLevelItem ( j )->text ( 0 ) == tokens.at ( 0 ) )
{
treeWidgetItem = packageTreeWidget->topLevelItem ( j );
break;
}
}
if ( treeWidgetItem == NULL )
{
treeWidgetItem = new QTreeWidgetItem;
treeWidgetItem->setText ( 0, tokens.at ( 0 ) );
packageTreeWidget->addTopLevelItem ( treeWidgetItem );
}
for ( int32_t j = 1; j < tokens.size(); ++j )
{
int32_t k;
for ( k = 0 ; k < treeWidgetItem->childCount(); ++k )
{
if ( treeWidgetItem->child ( k )->text ( 0 ) == tokens.at ( j ) )
{
treeWidgetItem = treeWidgetItem->child ( k );
break;
}
}
if ( k == treeWidgetItem->childCount() )
{
QTreeWidgetItem* newTreeWidgetItem = new QTreeWidgetItem;
newTreeWidgetItem->setText ( 0, tokens.at ( j ) );
treeWidgetItem->addChild ( newTreeWidgetItem );
treeWidgetItem = newTreeWidgetItem;
}
}
The index 'i' is missing from the loops because is the one I used to iterate the file paths, replace 'packageTreeWidget' with the name of your Tree Widget, I am using this to display the contents of a zip-like package file.

Need example about nodes in QAbstractItemModel for QTreeView?

Problem: I'm looking example about creating model ( based on QAbstractItemModel ) to QTreeView, but can't find sane codes. Qt examples are based on QStandardModel, which is not very useful and complex, Internet examples are based on python?! codes... Other information can't gave me the right directions to go. So, here is what I have:
std::map
typedef std::map< CompanyData, std::vector< ContractorData >, LessData< CompanyData > > Companies;
Here it's data example (CompanyName + ContractorsNames):
[Microsoft]*
[Bill Gates]
[Steve Balmer]
[...]
[Apple]*
[Steve Jobs - R.I.P.]
[Wozniak]
[OtherStuff]*
...
where * means - expandable item (parent)
And all that I need it to create QTreeView with this data above!
Can any one help?
Many thanks!
So, as no posts was done here, I post here my own solution (also used text filtering):
void ContractorsFilter::onCustomFilterChanged( const QString& text )
{
try
{
struct MatchFilter
{
// data
QString filter_;
Companies& filtered_recipients_;
// methods
MatchFilter( const QString& _filter, Companies& _recipients )
: filter_( _filter )
, filtered_recipients_( _recipients )
{
filtered_recipients_.clear();
}
void operator()( const Companies::value_type& val ) const
{
bool isFound = false;
std::vector< ContractorData >::const_iterator con_i( val.second.begin() ), con_e( val.second.end() );
for( ; con_i != con_e; ++con_i )
{
const QString contractorName = (*con_i).name;
if( contractorName.contains( filter_, Qt::CaseInsensitive ) )
{
filtered_recipients_[ val.first ].push_back( (*con_i) );
isFound = true;
}
}
const QString companyName = val.first.name;
if( companyName.contains( filter_, Qt::CaseInsensitive ) )
{
filtered_recipients_[ val.first ].push_back( ContractorData() );
}
}
};
struct FillView
{
// data
QFont boldFont;
QBrush whiteBrush;
QStandardItemModel* model_;
// methods
FillView( QStandardItemModel* model )
: model_( model )
{
model_->clear();
}
void operator ()( const Companies::value_type& val ) const
{
struct AppendContractors
{
// data
QStandardItem* parent_;
// methods
AppendContractors( QStandardItem* _parent = 0 )
: parent_( _parent )
{}
bool isEmpty( const ContractorData& contractor ) const
{
return contractor.id.isEmpty();
}
void operator()( const std::vector< ContractorData >::value_type& contractor ) const
{
if( !isEmpty( contractor ) )
{
QStandardItem *item = 0;
QList< QStandardItem* > line;
line << ( item = new QStandardItem( QIcon( ACCOUNT_ITEM_ICON ), contractor.name ) );
item->setSizeHint( QSize( 0, 25 ) );
parent_->appendRow( line );
}
}
};
QStandardItem *parentItem = model_->invisibleRootItem();
// добавляем новую компанию + контрагента
QList< QStandardItem* > line;
line << ( parentItem = new QStandardItem( QIcon( COMPANY_ITEM_ICON ), val.first.name ) );
parentItem->setSizeHint( QSize( 0, 25 ) );
model_->appendRow( line );
std::for_each( val.second.begin(), val.second.end(), AppendContractors( parentItem ) );
}
};
// удаляем символ(ы), которые не фильтруются
// формируется новая таблица, которая и будет использоваться моделью для отображения
std::for_each( data_.begin(), data_.end(),
MatchFilter( text, filter_data_ ) );
// вывод отфильтрованных контрагентов
std::for_each( filter_data_.begin(), filter_data_.end(),
FillView( model_ ) );
ui_.treeView->expandAll();
}
catch( const std::exception& e )
{
Core::errLog( "ContractorsFilter::onCustomFilterChanged", e.what() );
throw;
}
}
PS: type Companies is a
typedef std::map< CompanyData, ContractorsData, LessData< CompanyData > > Companies;
where CompanyData, ContractorsData are simple structures...
Have a nice day!

SQLite and UTF8

I have a strange problem. Cant add function to sqlite/ Write on c++/Qt.
Function should do upper to Utf-8 simbols;
this->db = QSqlDatabase::addDatabase("QSQLITE");
this->db.setDatabaseName(db);
this->db.open();
QVariant v = this->db.driver()->handle();
sqlite3 *handler = *static_cast<sqlite3 **>(v.data());
sqlite3_create_function( handler, "myUpper", 1, SQLITE_ANY, NULL, myFunc, NULL, NULL )
and the function is:
void myFunc( sqlite3_context * ctx, int argc, sqlite3_value ** argv )
{
if( argc != 1 ) return;
QString str;
switch(sqlite3_value_type(argv[0]))
{
case SQLITE_NULL:
{
sqlite3_result_text( ctx, "NULL", 4, SQLITE_STATIC );
break;
}
case SQLITE_TEXT:
{
QString wstr((char*)sqlite3_value_text16(argv[0]));
wstr = wstr.toUpper();
sqlite3_result_text16( ctx, wstr.toStdString().c_str(), wstr.size() , SQLITE_TRANSIENT );
break;
}
default:
sqlite3_result_text( ctx, "NULL", 4, SQLITE_STATIC );
break;
}
}
You didn't specify the problem, but I suppose you should pass a pointer to the function:
sqlite3_create_function(handler, "myUpper", 1, SQLITE_ANY, NULL, &myFunc, NULL, NULL)
EDIT:
I you're passing to the function wstr.toStdString().c_str(). This is not allowed because the pointer returned is only temporary and will go out of scope. What I would do is:
...
case SQLITE_TEXT: {
QString wstr((char*)sqlite3_value_text(argv[0]));
wstr = wstr.toUpper();
QByteArray array = wstr.toLocal8Bit();
const char* cstr = array.data();
sqlite3_result_text(ctx, cstr, wstr.size() , SQLITE_TRANSIENT);
break;
}
...
I didn't try it so check this.

Resources