I am new in Qt and I need to do a program who export html page to PDF
So, the main idea is use QWebPage for interpret html and export itself to pdf with QPrinter.
I have two class webview who use QWebPage and Print who use QPrinter.
In main.cpp I have connect LoadFinished to PrintPDF slot:
Print *pdf = new Print(args);
webview *nav = new webview();
nav->setPrinter(pdf->getPrinter());
if(nav->load(args) == false) {
qDebug() << "can't load page";
return 0;
}
//when the page page is ready, we will print it
QObject::connect(nav->getFrame(), SIGNAL(loadFinished(bool)), nav, SLOT(printPDF(bool)));
My webview.cpp class:
#include "webview.h"
webview::webview()
{
page = new QWebPage;
printer = 0;
}
webview::~webview()
{
delete page;
}
bool webview::load(Arguments *args)
{
QRegularExpression url("^(file|http)://");
QRegularExpression fullPath("^/");
QRegularExpressionMatch pathMatch = fullPath.match(args->getSource());
QRegularExpressionMatch urlMatch = url.match(args->getSource());
// see http://qt-project.org/doc/qt-4.8/qwebsettings.html#WebAttribute-enum for more informations
page->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
page->settings()->setAttribute(QWebSettings::AutoLoadImages, true);
page->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
page->settings()->setAttribute(QWebSettings::PrintElementBackgrounds, true);
page->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
if(pathMatch.hasMatch()) {
page->mainFrame()->load(QUrl::fromLocalFile(args->getSource()));
} else {
if (urlMatch.hasMatch()) {
page->mainFrame()->load(QUrl(args->getSource()));
} else {
fprintf(stderr, "%s\n", qPrintable(QApplication::translate("main", "Error: Invalide source file")));
return false;
}
}
return true;
}
void webview::printPDF(bool ok)
{
if(ok == true) {
qDebug() << "okay";
} else
qDebug() << "non okay";
if(printer != 0)
page->mainFrame()->print(printer);
}
This is what my console display:
non okay
QPainter::begin: A paint device can only be painted by one painter at a time.
I have no idea where the error might be due. The whole project is here
The arguments are:
./htmltopdf http://stackoverflow.com destinationFolder
(destinationFolder is not yet implemented, you must directly modify the source code)
You have more than one Painter in your Code. Search for it.
I thing the Problem is in your "Printer Class". With a Local Printer in the printPDF Method it works. Try it:
void webview::printPDF(bool ok)
{
QPrinter printer(QPrinter::HighResolution);
QString fileName = QFileDialog::getSaveFileName(this, "Export PDF",
QString(), "*.pdf");
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(fileName);
page()->mainFrame()->print(&printer);
}
UPDATE: Here a small working Example:
testview.cpp
#include "testview.h"
#include <QWebFrame>
#include "webview.h"
#include <QWebPage>
#include <QGridLayout>
testview::testview(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QGridLayout* Layout = new QGridLayout(this);
webview* w = new webview(this);
w->setMinimumSize(500, 500);
centralWidget()->setLayout(Layout);
bool a = connect(w->page()->mainFrame(), SIGNAL(loadFinished(bool)), w, SLOT(printPDF(bool)));
w->page()->mainFrame()->load(QUrl("http://stackoverflow.com"));
}
testview::~testview()
{
}
webview.cpp
#include "webview.h"
#include <QPrinter>
#include <QWebFrame>
#include <QFileDialog>
webview::webview(QWidget *parent)
: QWebView(parent)
{
}
webview::~webview()
{
}
void webview::printPDF(bool ok)
{
QPrinter printer(QPrinter::HighResolution);
QString fileName = QFileDialog::getSaveFileName(this, "Export PDF",
QString(), "*.pdf");
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(fileName);
page()->mainFrame()->print(&printer);
}
The problem was elsewhere :
In my main.cpp I had this :
QObject::connect(nav->getFrame(), SIGNAL(loadFinished(bool)), nav, SLOT(printPDF(bool)));
[...]
delete args;
delete pdf;
delete nav;
return app.exec();
app.exec() is a kind of infinit loop who thaht handles Qt events and thus allows the program does not close.
If I proceed to delete before calling this function,then I will have freshly deallocated pointers that will be used
If I Do :
bool result = app.exec();
delete args;
delete pdf;
delete nav;
return result;
It's work fine !
Related
I've been trying to learn c++ and qt framework by doing a point of sale software project.my main window has couple of tabs and with in one tab i have a qpushbutton which open a newpurchase dialog which takes input from user to record a new purchase from supplier.this newpurchase dialog has a lineedit which, when recieves product barcode from a barcode reader ,triggers another quanity dialog which, user can use to enter the quantity of the product. but when i click ok on the button box of the quantity dialog instead of closing just the quantity dialog ,it closes the newpurchase dialog as well.i debugged the qt application and it shows that a qpushbutton (which saves and closes the newpurchase dialog)on the newpuchase dialog gets triggered automatically.additionally this behaviour is only spotted when quantity dialog is triggered through the barcode lineedit,i have another lineedit which accepts description of the product and triggers the same quantity dialog ,but clicking ok button when quantity dialog is triggered through description lineedit does not closes the newpurchase dialog or trigger the qpushbutton which saves and closes the newpurchase dialog.im attaching the screen shots of the dialog and code for the dialogs
on mainwindow.cpp
void MainWindow::on_pushButton_clicked()
{
newpurchase mypurchase;
connect(&mypurchase,SIGNAL(purchase_added()),this,SLOT(update_view()));
mypurchase.exec();
}
newpurchase dialog
newpurchase.cpp
#include "newpurchase.h"
#include "ui_newpurchase.h"
#include <QtDebug>
#include <qtablewidget.h>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlRecord>
#include <QCompleter>
#include <QObject>
#include <QWidget>
#include <QDate>
newpurchase::newpurchase(QWidget *parent) :
QDialog(parent),
ui(new Ui::newpurchase)
{
ui->setupUi(this);
QSqlQuery qry;
qry.prepare("select description from product");
if(!qry.exec())
{
qDebug() << "error getting description from product";
}
QStringList items;
while(qry.next())
{
items <<qry.value(0).toString();
}
QCompleter *completer =new QCompleter(items,this);
completer->setCaseSensitivity(Qt::CaseInsensitive);
ui->lineEdit_descripion->setCompleter(completer);
connect(completer,static_cast<void (QCompleter::*)(const QString&)>(&QCompleter::activated),
[&](const QString &text)->void
{
newpurchase::clear(text);
});
QSqlQuery supqry;
supqry.prepare("select supplier_name from supplier");
if(!supqry.exec())
{
qDebug() << "error getting suppliername from supplier";
}
QStringList supitems;
while(supqry.next())
{
supitems <<supqry.value(0).toString();
}
QCompleter *supcompleter =new QCompleter(supitems,this);
supcompleter->setCaseSensitivity(Qt::CaseInsensitive);
ui->lineEdit_sup->setCompleter(supcompleter);
}
newpurchase::~newpurchase()
{
delete ui;
}
void newpurchase::on_lineEdit_barcode_returnPressed()
{
if(!ui->lineEdit_sale->text().isEmpty() && !ui->lineEdit_sup->text().isEmpty() && !ui->lineEdit_inv->text().isEmpty())
{
quantity_dialog quandialog;
connect(&quandialog,SIGNAL(purdetails(QString)),this,SLOT(code(QString)));
quandialog.exec();
}
else
{
Errdialog myerror("please provide supplier and invoice" );
myerror.exec();
}
}
void newpurchase::saveDataIntoTable(QString item,double price,int quantity,QString code,QString date)
{
double rupee =price*quantity*1.00;
//qDebug() <<date;
QString mrp= QString::number(rupee,'f',2);
QString qty= QString::number(quantity);
QString prc= QString::number(price);
// QString kod= QString::number(code);
if (!ui->tableWidget)
return;
const int currentRow = ui->tableWidget->rowCount();
ui->tableWidget->setRowCount(currentRow + 1);
ui->tableWidget->setItem(currentRow, 0, new QTableWidgetItem(item));
ui->tableWidget->setItem(currentRow, 1, new QTableWidgetItem(prc));
ui->tableWidget->setItem(currentRow, 2, new QTableWidgetItem(qty));
ui->tableWidget->setItem(currentRow, 3, new QTableWidgetItem(mrp));
ui->tableWidget->setItem(currentRow, 4, new QTableWidgetItem(code));
ui->tableWidget->setItem(currentRow, 5, new QTableWidgetItem(date));
double total=ui->label_3->text().toDouble();
total = total+rupee;
ui->label_3->setText(QString::number(total,'f',2));
}
void newpurchase::clear(QString item)
{
if(!item.isEmpty())
{
if(!ui->lineEdit_des->text().isEmpty() && !ui->lineEdit_sup->text().isEmpty() && !ui->lineEdit_inv->text().isEmpty())
{
quantity_dialog quandialog;
connect(&quandialog,SIGNAL(purdetails(QString)),this,SLOT(descr(QString)));
quandialog.exec();
}
else
{
Errdialog myerror("please provide supplier and invoice" );
myerror.exec();
}
}
}
void newpurchase::descr(QString q)
{
QStringList elements = q.split(':');
QSqlQuery qry;
qry.prepare("select * from product where description='"+ui->lineEdit_des->text()+"'");
if(!qry.exec())
{
qDebug() << "error getting table product";
}
if (qry.next())
{
QString result=qry.value(1).toString();
double cost= elements[1].toDouble();
QString itemco=qry.value(2).toString();
int quan=elements[0].toInt();
QString mfd=elements[2];
saveDataIntoTable(result,cost,quan,itemco,mfd);
ui->lineEdit_des->clear();
}
}
void newpurchase::on_pushButton_clicked()
{
QItemSelectionModel *select =ui->tableWidget->selectionModel();
int row = select->selectedRows().takeFirst().row();
double rupee=ui->tableWidget->item(row,3)->text().toDouble();
ui->tableWidget->removeRow(row);
double total=ui->label_3->text().toDouble();
total = total-rupee;
ui->label_3->setText(QString::number(total,'f',2));
}
void newpurchase::on_lineEdit_des_returnPressed()
{
}
void newpurchase::code(QString q)
{
QStringList elements = q.split(':');
QSqlQuery qry;
qry.prepare("select * from product where code="+ui->lineEdit_sale->text());
if(!qry.exec())
{
qDebug() << "error getting table product";
}
if (qry.next())
{
QString result=qry.value(1).toString();
double cost= elements[2].toDouble();
QString itemco=qry.value(2).toString();
int quan=elements[0].toInt();
QString mfd=elements[1];
saveDataIntoTable(result,cost,quan,itemco,mfd);
}
ui->lineEdit_sale->clear();
}
void newpurchase::on_pushButton_2_clicked()
{
QString datetime= QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QString supplier=ui->lineEdit_sup->text();
QString invoice=ui->lineEdit_inv->text();
int numrow=ui->tableWidget->rowCount();
// qDebug() << numrow;
for (int i = 0; i < numrow; i++)
{
QSqlQuery query;
query.prepare("SELECT MAX(Id) FROM stock_transaction");
if(!query.exec())
{
qDebug() << "error getting id";
}
int id=0 ;
if (query.next())
{
id=query.value(0).toInt()+1;
}
QString coder=ui->tableWidget->item(i,4)->text();
QString qua=ui->tableWidget->item(i,2)->text();
double rate=ui->tableWidget->item(i,1)->text().toDouble();
QString date= ui->tableWidget->item(i,5)->text();
QString d=QString::number(id);
QString batch=supplier+"_"+invoice+"_"+d;
QSqlQuery querysale;
querysale.prepare("INSERT INTO stock_transaction(id,code,stock,mfd,supplier,invoice,cost,date_time) VALUES(:id,:code,:stock,:mfd,:supplier,:invoice,:cost,:date_time)");
querysale.bindValue(":id", id);
querysale.bindValue(":code",coder);
querysale.bindValue(":stock", qua.toInt());
querysale.bindValue(":mfd",date);
querysale.bindValue(":supplier", supplier);
querysale.bindValue(":invoice", invoice);
querysale.bindValue(":cost", rate*1.00);
querysale.bindValue(":date_time", datetime);
if(!querysale.exec())
{
qDebug() << "error recording sale";
}
}
emit this->purchase_added();
close();
}
quantity dialog
quantity.cpp
#include "quantity_dialog.h"
#include "ui_quantity_dialog.h"
quantity_dialog::quantity_dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::quantity_dialog)
{
ui->setupUi(this);
ui->lineEdit_quan->setFocus();
ui->buttonBox->setEnabled(false);
connect(ui->lineEdit_quan, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits()));
connect(ui->dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(checkLineEdits()));
connect(ui->lineEdit_3, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits()));
}
quantity_dialog::~quantity_dialog()
{
delete ui;
}
void quantity_dialog::on_buttonBox_accepted()
{
emit purdetails(ui->lineEdit_quan->text()+":"+ui->dateEdit->text()+":"+ui->lineEdit_3->text());
ui->lineEdit_quan->clear();
}
Please let me know if i can provide any other details
the auto default property of the autoclicking qpushbutton was set.changing this property solved my issue
I have a program that needs to check at startup if a path to project is set. For that I have subclassed QMessageBox, added some custom stuff (like spacer to make dialog wider) and I am calling QFileDialog::getExistingDirectory to get directory.
Thing is, user can click Cancel in QFileDialog. But I want user to be returned to QMessageBox to have another chance to set path or quit program altogether. To achieve that I wrote method loop():
CustomMessageBox::loop()
{
while (true) {
this->exec();
if (this->clickedButton() == setPathButton) {
path = QFileDialog::getExistingDirectory(...);
if (!path.isEmpty()) { break; }
} else if (this->clickedButton() == quitButton) {
break;
}
}
}
Then, I have a method getPath():
CustomMessageBox::getPath()
{
loop();
return path;
}
which I call in main.cpp:
CustomMessageBox box;
QString path = box.getPath();
if (!path.isEmpty()) {
// save path, bla, bla
} else {
exit(EXIT_FAILURE)
}
This works, but is this a good practice? I am specifically asking regarding this while inside which resides method exec().
IMO disadvantages of your decision:
Non-standard usage of dialog class. Put strange loop inside some GUI method and call standard exec() method inside it, when the method can easily be called in a standard way.
Hide exec() call from "user" (another programmer)
Dual, or even triple, purpose of the one getPath() method:
show dialog
flag that the dialog was accepted/rejected via empty - non-empty string
return directory path string
I suggest inherit QDialog (why message box?):
In Degister I put on form standard Push Button setPathButton and Dialog Button Box buttonBox. Then I removed "Ok" button from the box:
#include <QtWidgets/QDialog>
#include "ui_CustomDialog.h"
class CustomDialog : public QDialog
{
Q_OBJECT
public:
CustomDialog(QWidget *parent = nullptr);
QString path() const { return m_path; };
private slots:
void on_setPathButton_clicked();
void on_buttonBox_rejected(); // You can use any pushButton instead: on_closeButton_clicked()
private:
Ui::CustomDialogClass ui;
QString m_path;
};
...
#include "CustomDialog.h"
#include <QFileDialog>
CustomDialog::CustomDialog(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
}
void CustomDialog::on_setPathButton_clicked()
{
m_path.clear();
QString dir = QFileDialog::getExistingDirectory();
if (!dir.isEmpty())
{
m_path = dir;
done(QDialog::Accepted);
}
}
// You can use any pushButton instead: void on_closeButton_clicked()
void CustomDialog::on_buttonBox_rejected()
{
reject();
}
main.cpp
#include "CustomDialog.h"
#include <QtWidgets/QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomDialog box;
int code = box.exec();
if (code == QDialog::Accepted)
{
qDebug() << box.path();
// save path, bla, bla
}
else
{
exit(EXIT_FAILURE);
}
return a.exec();
}
I am trying to get the serial data and display it on the GUI, that i made in the QT for that when I try to get the serial data and display it in the output, it shows me this garbage type value.
"?\u0004?#\u0004??\u0004?#\u001C??"
"?\u0004?#\u0004??\u0004?#\u001C???\u0004?#\u001C"
this whole code is below, but the culprit lines are "readserial" function in the last.
#include "dialog.h"
#include "ui_dialog.h"
#include <QSerialPort>
#include<string>
#include<QDebug>
#include<QMessageBox>
#include <QSerialPortInfo>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
ui->label->setText("0");
arduino = new QSerialPort(this);
serialBuffer = "";
bool arduino_is_availabe = false;
QString arduino_uno_port_name;
foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts())
{
if(serialPortInfo.hasProductIdentifier() && serialPortInfo.hasVendorIdentifier())
{
if((serialPortInfo.productIdentifier() == arduino_uno_product_id) && (serialPortInfo.vendorIdentifier() == arduino_uno_vendor_id))
{
arduino_is_availabe = true;
arduino_uno_port_name = serialPortInfo.portName();
}
}
}
if(arduino_is_availabe)
{
qDebug() <<"Found the arduino port...\n";
arduino->setPortName(arduino_uno_port_name);
arduino->open(QSerialPort::ReadOnly);
arduino->setBaudRate(QSerialPort::Baud9600);
arduino->setBaudRate(QSerialPort::Data8);
arduino->setFlowControl(QSerialPort::NoFlowControl);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
QObject::connect(arduino, SIGNAL(readyRead()), this, SLOT(readSerial()));
}else
{
qDebug() <<"Couldn't find the correct port for the arduino.\n";
QMessageBox::information(this, "serial port error", "couldn't open the serial port to arduino ");
}
}
Dialog::~Dialog()
{
if(arduino->isOpen())
{
arduino->close(); // Close the serial port if it's open.
}
delete ui;
}
void Dialog::readSerial()
{
QByteArray serialData;
serialData=arduino->readAll();
serialBuffer+=QString::fromStdString(serialData.toStdString());
qDebug()<<serialBuffer;
}
void Dialog::on_label_linkActivated(const QString &link)
{
ui->label->setText(serialBuffer);
}
I think your problem is right here:
arduino->setBaudRate(QSerialPort::Baud9600);
arduino->setBaudRate(QSerialPort::Data8);
You are setting the baud rate twice in a row, which makes no sense. In the second line you are setting a value of QSerialPort::Data8, so perhaps you are trying to define the setDataBits method. It would look like this:
arduino->setBaudRate(QSerialPort::Baud9600);
arduino->setDataBits(QSerialPort::Data8);
I'm trying to create a simple app which allows user to enter a word and the app will output the definition. I have compiled words and definitions by making mysql database file. I thought using MySQL would be a faster way make my app. My database looks like this:
rowed | Column1(word) | Column2(definition)
1 DNA A double-stranded,helical...
The pseudocode for this program definitely should be like this:
if(lineEdit == wordInput)
{
ui->label->setText("Display definition)
}
I just can't figure out how to do it. So far I have tried following code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QPalette>
#define Path_to_DB "/Users/makkhay/Desktop/nep_eng-2.sqlite"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Set background picture
QPixmap bkgnd("/Users/makkhay/Desktop/background.jpg");
bkgnd = bkgnd.scaled(this->size(), Qt::IgnoreAspectRatio);
QPalette palette;
palette.setBrush(QPalette::Background, bkgnd);
this->setPalette(palette);
myDB = QSqlDatabase::addDatabase("QSQLITE");
myDB.setDatabaseName(Path_to_DB);
QFileInfo checkFile(Path_to_DB);
if(checkFile.isFile())
{
if(myDB.open())
{
ui->label->setText("The database is connected");
}
}else{
ui->label->setText("No file found!");
}
}
MainWindow::~MainWindow()
{
delete ui;
myDB.close();
}
void MainWindow::on_pushButton_clicked()
{
// QMessageBox::StandardButton reply= QMessageBox::question(this,
// "My Title", " Word not found, quit app?",
// QMessageBox::Yes | QMessageBox::No);
// if(reply == QMessageBox::Yes){
// QApplication::quit();
//}
if(!myDB.isOpen()){
qDebug() << " No connection to db";
return;
}
QString wordInput, definition;
wordInput = ui->lineEdit->text();
QSqlQuery qry;
qry.prepare("SELECT Column1,Column2 FROM Nepali WHERE Column1 = :input");
qry.bindValue(":input",wordInput);
// int fieldNo = query.record().indexof("Column1");
if(qry.exec())
{
ui->debug->setText(" Checking output!"); // output is visible
while (qry.next())
{
ui->output->setText("Checing output!"); // output is not visible
QString inputWord = qry.value(0).toString();
QString wordDefinition = qry.value(1).toString();
ui->output->setText(wordDefinition);
}
}
}
OK, the following code works for me. Try commenting out your existing code and pasting this code in. This code is basically the exact same code you posted, I just commented out some of the code to set the background and removed some excess lines.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QPalette>
#include <QSqlQuery>
#include <QFileInfo>
#define Path_to_DB "/Users/makkhay/Desktop/nep_eng-2.sqlite"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Set background picture
//QPixmap bkgnd("/Users/makkhay/Desktop/background.jpg");
//bkgnd = bkgnd.scaled(this->size(), Qt::IgnoreAspectRatio);
//QPalette palette;
//palette.setBrush(QPalette::Background, bkgnd);
//this->setPalette(palette);
myDB = QSqlDatabase::addDatabase("QSQLITE");
myDB.setDatabaseName(Path_to_DB);
QFileInfo checkFile(Path_to_DB);
if(checkFile.isFile())
{
if(myDB.open())
{
ui->label->setText("The database is connected");
}
}else{
ui->label->setText("No file found!");
}
}
MainWindow::~MainWindow()
{
delete ui;
myDB.close();
}
void MainWindow::on_pushButton_clicked()
{
if(!myDB.isOpen()){
qDebug() << " No connection to db";
return;
}
QString wordInput;
wordInput = ui->lineEdit->text();
QSqlQuery qry;
qry.prepare("SELECT Column1,Column2 FROM Nepali WHERE Column1 = :input");
qry.bindValue(":input",wordInput);
if(qry.exec())
{
ui->debug->setText(" Checking output!");
while (qry.next())
{
// Retrieve Values from select statement
QString inputWord = qry.value(0).toString();
QString wordDefinition = qry.value(1).toString();
// Display values
ui->output->setText(wordDefinition);
}
} else {
qDebug() << "query failed to execute";
}
}
After declaring the qry variable the code should look like this:
qry.prepare("SELECT Column1,Column2 FROM tableName WHERE Column1 = :input");
qry.bindValue(":input",wordInput);
if(qry.exec())
{
if (qry.next())
{
// Extract the results from the SELECT statement
QString inputWord = qry.value(0).toString();
QString wordDefinition = qry.value(1).toString();
// Then use the values for whatever you wish.
}
}
You don't need comma before WHERE. Also, I guess that you are comparing Column1 with wordInput, not Column2.
if(qry.exec("SELECT Column1,Column2 FROM some_table WHERE Column1='"+ wordInput+"'"))
Edit: you don't mention a table you are selecting from.
Is it possible to show a QFileDialog where the user can select a file or a directory, either one?
QFileDialog::getOpenFileName() accepts only files, while QFileDialog::getExistingDirectory() is directories-only, but I need to show a file dialog that can accept both a file or a directory (it makes sense for my program). QFileDialog::Options didn't have anything promising.
QFileDialog currently does not support this. I think the main problem for you here is that the FileMode is not a Q_FLAGS and the values are not power of 2, either, and so, you cannot write this to solve this issue.
setFileMode(QFileDialog::Directory|QFileDialog::ExistingFiles)
To solve this, you would need quite a bit of fiddling, e.g.:
Override the open button click operation.
Get the "treeview" indices properly for both files and directories.
My attempt below demonstrates the former, but I did not really go as far as solving the second because that seems to involve some more fiddling with the selection model.
main.cpp
#include <QFileDialog>
#include <QApplication>
#include <QWidget>
#include <QTreeWidget>
#include <QPushButton>
#include <QStringList>
#include <QModelIndex>
#include <QDir>
#include <QDebug>
class FileDialog : public QFileDialog
{
Q_OBJECT
public:
explicit FileDialog(QWidget *parent = Q_NULLPTR)
: QFileDialog(parent)
{
setOption(QFileDialog::DontUseNativeDialog);
setFileMode(QFileDialog::Directory);
// setFileMode(QFileDialog::ExistingFiles);
for (auto *pushButton : findChildren<QPushButton*>()) {
qDebug() << pushButton->text();
if (pushButton->text() == "&Open" || pushButton->text() == "&Choose") {
openButton = pushButton;
break;
}
}
disconnect(openButton, SIGNAL(clicked(bool)));
connect(openButton, &QPushButton::clicked, this, &FileDialog::openClicked);
treeView = findChild<QTreeView*>();
}
QStringList selected() const
{
return selectedFilePaths;
}
public slots:
void openClicked()
{
selectedFilePaths.clear();
qDebug() << treeView->selectionModel()->selection();
for (const auto& modelIndex : treeView->selectionModel()->selectedIndexes()) {
qDebug() << modelIndex.column();
if (modelIndex.column() == 0)
selectedFilePaths.append(directory().absolutePath() + modelIndex.data().toString());
}
emit filesSelected(selectedFilePaths);
hide();
qDebug() << selectedFilePaths;
}
private:
QTreeView *treeView;
QPushButton *openButton;
QStringList selectedFilePaths;
};
#include "main.moc"
int main(int argc, char **argv)
{
QApplication application(argc, argv);
FileDialog fileDialog;
fileDialog.show();
return application.exec();
}
main.pro
TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp
Build and Run
qmake && make && ./main
Quite old question but I think I have a simpler solution than most for the lazy ones.
You can connect the signal currentChanged of QFileDialog with a function that dynamically changes the fileMode.
//header
class my_main_win:public QMainWindow
{
private:
QFileDialog file_dialog;
}
//constructor
my_main_win(QWidget * parent):QMainWindow(parent)
{
connect(&file_dialog,QFileDialog::currentChanged,this,[&](const QString & str)
{
QFileInfo info(str);
if(info.isFile())
file_dialog.setFileMode(QFileDialog::ExistingFile);
else if(info.isDir())
file_dialog.setFileMode(QFileDialog::Directory);
});
}
And then simply call file_dialog as you would.
if(file_dialog.exec()){
QStringList dir_names=file_dialog.selectedFiles():
}
What worked for me was to use:
file_dialog.setProxyModel(nullptr);
as suggested here, or
class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
return (fileModel!=NULL && fileModel->isDir(sourceModel()->index(sourceRow, 0, sourceParent))) || QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
};
...
FileFilterProxyModel* proxyModel = new FileFilterProxyModel;
file_dialog.setProxyModel(proxyModel);
as suggested here, or
class FileDialog : public QFileDialog
{
Q_OBJECT
public:
explicit FileDialog(QWidget *parent = Q_NULLPTR)
: QFileDialog(parent)
{
m_btnOpen = NULL;
m_listView = NULL;
m_treeView = NULL;
m_selectedFiles.clear();
this->setOption(QFileDialog::DontUseNativeDialog, true);
this->setFileMode(QFileDialog::Directory);
QList<QPushButton*> btns = this->findChildren<QPushButton*>();
for (int i = 0; i < btns.size(); ++i) {
QString text = btns[i]->text();
if (text.toLower().contains("open") || text.toLower().contains("choose"))
{
m_btnOpen = btns[i];
break;
}
}
if (!m_btnOpen) return;
m_btnOpen->installEventFilter(this);
//connect(m_btnOpen, SIGNAL(changed()), this, SLOT(btnChanged()))
m_btnOpen->disconnect(SIGNAL(clicked()));
connect(m_btnOpen, SIGNAL(clicked()), this, SLOT(chooseClicked()));
m_listView = findChild<QListView*>("listView");
if (m_listView)
m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_treeView = findChild<QTreeView*>();
if (m_treeView)
m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
}
QStringList selectedFiles()
{
return m_selectedFiles;
}
bool eventFilter( QObject* watched, QEvent* event )
{
QPushButton *btn = qobject_cast<QPushButton*>(watched);
if (btn)
{
if(event->type()==QEvent::EnabledChange) {
if (!btn->isEnabled())
btn->setEnabled(true);
}
}
return QWidget::eventFilter(watched, event);
}
public slots:
void chooseClicked()
{
QModelIndexList indexList = m_listView->selectionModel()->selectedIndexes();
foreach (QModelIndex index, indexList)
{
if (index.column()== 0)
m_selectedFiles.append(this->directory().absolutePath() + "/" + index.data().toString());
}
QDialog::accept();
}
private:
QListView *m_listView;
QTreeView *m_treeView;
QPushButton *m_btnOpen;
QStringList m_selectedFiles;
};
as suggested here. Credits for the original authors and me.
Connect to the currentChanged signal and then set the file mode to the currently selected item (directory or file). This is a Python3 implementation:
class GroupFileObjectDialog(QFileDialog):
def __init__(self, parent):
super().__init__(parent)
self.setOption(QFileDialog.DontUseNativeDialog)
self.setFileMode(QFileDialog.Directory)
self.currentChanged.connect(self._selected)
def _selected(self,name):
if os.path.isdir(name):
self.setFileMode(QFileDialog.Directory)
else:
self.setFileMode(QFileDialog.ExistingFile)
Tested on PyQt 5.14 running on linux / Ubuntu18.04.