I have a Qt application for Windows which contains a list of files in a widget. I want to be able to drag the files from this widget to another application. I am using Qt-base drag and drop mechanism and it works well in most cases. One case which does not work is to drag and drop it into MS Edge browser, for example when I open MS Edge and start writing an email with mail.google.com and I want to add the files as attachment by drag and drop from my Qt application. It is strange that this does work in Chrome, Firefox and also in ancient Internet Explorer. But not in Edge. Here is my minimalistic example. Try to drag files from the widget to a web page which accepts drag and drop of a file. On my computer this works for all browsers except Edge.
#include <QApplication>
#include <QDrag>
#include <QLabel>
#include <QMimeData>
#include <QMouseEvent>
class Window : public QLabel
{
public:
void mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
QList<QUrl> urls;
urls.append(QUrl::fromLocalFile("C:/test/test1"));
urls.append(QUrl::fromLocalFile("C:/test/test2"));
QMimeData* mimeData = new QMimeData();
mimeData->setUrls(urls);
QDrag* drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->exec(Qt::CopyAction);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.setText("Drag from here...");
w.show();
return a.exec();
}
Note that I am passing data as text/uri-list MIME type. I was observing how other applications behave as drag source, and they seem to work fine - e.g. when dragging from File Explorer, Total Commander etc to MS Edge. When I look at the MIME types that they send, I can see also application/x-qt-windows-mime;value="Shell IDList Array" type, which I am missing in my example. So this is what I assume is the problem, Edge probably expects this MS-centric data type. I would also like to wrap data in this format but I do not know how to do it.
I assume I will have to use Win32 API. So I browsed around and got some snippets which should be able to create some useful data structures...
QVector<LPITEMIDLIST> idLists;
for (const QString &nativePath: qAsConst(nativePaths))
{
PCWSTR pszFile = PCWSTR(nativePath.utf16());
LPITEMIDLIST idl = ILCreateFromPathW(pszFile);
if (idl == nullptr)
{
for (LPITEMIDLIST pid : qAsConst(idLists))
ILFree(pid);
return nullptr;
}
idLists.append(idl);
}
IShellItemArray *iArray = nullptr;
HRESULT result = SHCreateShellItemArrayFromIDLists(UINT(idLists.size()), (LPCITEMIDLIST*)idLists.data(), &iArray);
for (LPITEMIDLIST pid : qAsConst(idLists))
ILFree(pid);
This seems to me as the way that could lead to the desired outcome but there are still some steps missing. I do not know how to wrap this data (are they of any use at all?) into QMimeData::setData(). Any ideas?
Related
I'm trying to implement autofill functionality in a Qt Webengine based app.
My approach is based off Viper browser's implementation: running script on page's loadFinished signal. The script run just looks up all the fields that can be populated and fills them as simple as elem.value = 'value'.
It works with simple example like e.g. my wifi router config page (which uses very simple logic for forms). But it doesn't work with e.g. https://onlyfans.com/ login page.
When I run my script I get JS errors like:
"js: Uncaught TypeError: Cannot set property 'value' of null".
The minimal example demonstrating the issue:
#include <QApplication>
#include <QObject>
#include <QTimer>
#include <QWebEngineView>
#include <QWebEnginePage>
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
const char* URL_ONLYFANS = "https://onlyfans.com/";
const char* URL_ROUTER = "http://192.168.0.1/";
const char* SCRIPT_ROUTER(R"(
document.querySelector('input[type=text]').value = 'admin';
document.querySelector('input[type=password]').value = 'mypass';
)");
const char* SCRIPT_ONLYFANS(R"(
document.querySelector('input[name=email]').value = "random#gmail.com";
document.querySelector('input[name=password]').value = "randompass";
)");
class Observer : public QObject
{
Q_OBJECT
public:
explicit Observer(QWebEnginePage *page): _page(page)
{}
public slots:
void onPageLoad(bool ok)
{
qDebug() << "Page loaded" << ok;
// Running script right away pretty much guarantees JS errors
// No JS errors if delay is >= 1500 ms:
QTimer::singleShot(1500, this, &Observer::runScript);
qDebug() << "Scheduled running script";
}
void runScript()
{
_page->runJavaScript(SCRIPT_ONLYFANS);
qDebug() << "Ran script";
}
private:
QWebEnginePage *_page{nullptr};
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWebEngineView view;
QWebEngineProfile profile;
QWebEnginePage page(&profile);
view.setPage(&page);
view.setUrl(QUrl(URL_ONLYFANS));
view.resize(1024, 750);
Observer observer(view.page());
QObject::connect(view.page(), &QWebEnginePage::loadFinished,
&observer, &Observer::onPageLoad);
/*
{
QWebEngineScript script;
script.setName("autofill");
script.setSourceCode(SCRIPT_ONLYFANS);
// Also tried `DocumentCreation` and `Deferred`, didn't work for Onlyfans
script.setInjectionPoint(QWebEngineScript::DocumentReady);
script.setRunsOnSubFrames(true);
script.setWorldId(QWebEngineScript::ApplicationWorld);
view.page()->scripts().insert(script);
}
*/
view.show();
return app.exec();
}
#include "main.moc"
As can be seen I tried to insert my scripts in two ways: on page's loadFinished signal (both immediately and with a time delay) and by inserting my script into page's scripts collection. I tried all available insertion points: DocumentCreation, DocumentReady and Deferred.
In all cases I get JS error shown above. Except for the case when I delay script execution by about 1500 ms, which is obviously a guess and is not robust.
I do understand that the elements I'm trying to set are probably not yet created, so I'd like some advice on how to properly time the script execution.
I am using Qt 5.12 and trying to write test (using QTest) for opening of project stored in some xml format.
In test I use QTimer::singleShot to wait QFileDialog to appear as proposed in QT close window by QTest in locked thread.
The problem is that QFileDialog::selectFile doesn't select anything. This means that OK button is not active, so I can't click on it in my test.
I assume in the following example that full path to file is /tmp/project.xml. Notice that QFileDialog::setDirectory works great: when the following example starts, you are in /tmp dir instead of /.
#include <QApplication>
#include <QFileDialog>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer::singleShot(300, []() {
QWidget* window = nullptr;
while (!window) {
window = qApp->activeModalWidget();
}
QFileDialog* fd = qobject_cast<QFileDialog*>(window);
fd->setDirectory("/tmp");
fd->selectFile("project.xml");
});
QString path = QFileDialog::getOpenFileName(nullptr, "Open Project",
"/", QString(),
nullptr, QFileDialog::DontUseNativeDialog);
}
The function selectFile didn't work because of the file name text box (lineEdit) is focused. This behavior comes from the implementation of QFileDialog::selectFile() :
void QFileDialog::selectFile(const QString &filename)
{
// ... function body
//Put the filename into the lineEdit when :
//1. The dialog is not visible or
//2. The lineEdit is not focused.
if (!isVisible() || !d->lineEdit()->hasFocus())
d->lineEdit()->setText(index.isValid() ? index.data().toString() : fileFromPath(d->rootPath(), filename));
}
To make the program works, there are two ways :
Put the file name directly in the text box
Give the focus away then call selectFile
fd->setDirectory("/tmp");
QLineEdit * lineEdit = qobject_cast<QLineEdit*>(fd->focusWidget());
if( lineEdit ){
//Method 1
lineEdit->setText("project.xml");
//Method 2
lineEdit->nextInFocusChain()->setFocus();
fd->selectFile("project.xml");
}else { //no lineEdit focus found
fd->selectFile("project.xml");
}
I have a class that composes a palette and assigns it to the application using QApplication::instance()->setPalette(QPalette palette).
And it effectively works.
But then I try to use QPalette QApplication::instance()->palette() to extract some colours.
But here it does not work, it just returns the default palette, not the current one.
After I have discovered that it is working as supposed and described in the documentation.
And now I have just 2 questions:
Why it is working in such a strange, useless and counter-intuitive
mode?
How I can retrieve the palette which was set using
QApplication::instance()->setPalette(QPalette palette)?
P.S. No, I can't keep that palette elsewhere.
I think it is an issue of your Qt version (you marked the question as Qt 5 but didn't indicate a specific version), or you have something else in your project that is resetting the palette (you mentioned it has a large code base).
This minimum example shows correct behavior, at least with Qt 5.12.3 32bits, Windows, VS 2017:
#include <QApplication>
#include <QPalette>
#include <QDebug>
#include <QTimer>
#include <QWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
const auto group = QPalette::Active;
const auto role = QPalette::Text;
auto palette = QApplication::palette();
qDebug() << "palette before:" << palette.color(group, role).name();
palette.setColor(group, role, "#123456");
qDebug() << "palette set:" << palette.color(group, role).name();
QApplication::setPalette(palette);
const auto palette2 = QApplication::palette();
qDebug() << "palette after:" << palette2.color(group, role).name();
QTimer::singleShot(100, [=]() { // check palette after the events loop has started
const auto palette3 = QApplication::palette();
qDebug() << "palette after 100ms:" << palette3.color(group, role).name();
});
QWidget w;
w.show();
return a.exec();
}
I've used QApplication::palette my self to retrieve custom palettes in different projects and had no issues at all.
QGuiApplication::setPalette is documented to change the default palette, so basically I think default palette means the palette used if a widget doesn't specify the other one; not the default system palette.
PS: I couldn't make it compile when using QApplication::instance()->setPalette since QApplication doesn't defines instance() but it falls to QCoreApplication::instance(), which obviously returns a QCoreApplication. Probably just a typo when you wrote the question, but I thought it deserved some lines. Given that the palette related methods are static, I decided to use those in the example, but I had the same results using the singleton from qApp.
i have a few questions regarding the gui of cplusplus using Qt creator
well i output an array using a forloop when user's choice is for example "1"
so in qt i created a button for that and i linked it with another window
so when i press on the button it opens another window
now i want to add the output of the forloop into this window
should i include iostream in the new window's .cpp file?
or what should i enter exactly?
in the mainwindow.cpp file here is the code i used to open a new window
void MainWindow::on_pushButton_clicked()
{
movies movies;
movies.setModal(true);
movies.exec();
}
thanks.
You should add a QTextEdit to your window (can be done via Qdesigner). And give this object a name e.g. Textout. Then in the code you should get a pointer to this object through your ui object. And you can use one of many methods to set the text of this object. setText is one option
ui->Textout->setText(Your_output_as_qstring)
Your can use QTextStream to format your text if necessary. Formating can be done with QString as well.
example:
#include <sstream>
#include <QLabel>
#include <QApplication>
int main(int argc, char *argv[])
{
std::stringstream ss;
for (auto s: {"first line", "second line"})
ss << s << std::endl;
QApplication a(argc, argv);
QLabel l;
l.setText(ss.str().c_str());
l.show();
return a.exec();
}
In my program, my users can copy a string of text from anywhere and paste it into my program. I use the simple QApplication::clipboard()->text(); function and everything works as expected. However, several of my users are having problems when trying to copy and paste on Windows 8.1
Here is how I access the clipboard text from my paste function:
QString clipboard = QApplication::clipboard()->text();
//check if the clipboard is empty
if(QApplication::clipboard()->text().isEmpty())
return;
//do something with clipboard
But if the text was copied from Notepad or Chrome, the text is ALWAYS empty. Windows 7 users have not had any problems. Not ALL Windows 8 users have this issue, it's only a handful but the issue it consistent. When copied from some other random places or within my program itself, the clipboard works fine.
I've tried using mimeData. When using the function formats(), only plain text is an available format, but the text is always empty.
The text being copied from Notepad/Chrome shows up fine in clipboard viewers and stuff and can be pasted elsewhere in other programs.
Copying and pasting is a very important feature in my program and its frustrating that my users can't copy and paste from Notepad or Chrome.
Any ideas? Thanks for your time. :)
EDIT: I tried using the "windows" style technique. There was no change. Here is my current, still unworking code:
QString clipboard = QApplication::clipboard()->text();
//check if the clipboard is empty
if(clipboard.isEmpty())
{
//might not actually be empty. Check using the other technique
if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(NULL))
{
HGLOBAL hGlobal = GetClipboardData(CF_TEXT) ;//hGlobal is NULL
if (hGlobal != NULL)//This never gets called because it is NULL
{
LPTSTR lpszData = (LPTSTR) GlobalLock(hGlobal) ;
if (lpszData != NULL)
{
clipboard.fromLocal8Bit((const char *)lpszData);
GlobalUnlock(hGlobal) ;
}
}
CloseClipboard() ;
}
if(clipboard.isEmpty())
return;
}
The copied text shows up fine in a clipboard viewer, but my program can't get to it no matter what:
How come GetClipboardData() isn't picking anything up? Again, the copied text CAN be pasted in any other program I've tried... just not mine. But if copied from somewhere else, it works no problem.
EDIT: With this version, When the user copies text and text gets to clipboard, a function copies the text this time to an internal text file not directly to your program, using QFile. Another function copies the text from the internal text file to your program. By this way, i think your program wouldn't have to directly deal with text in the clipboard
clipboard.h
#ifndef CLIPBOARD_H
#define CLIPBOARD_H
#include <QDialog>
#include <QClipboard>
#include <QLabel>
#include <QHBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QFile>
#include <QTextStream>
class ClipBoard : public QDialog {
Q_OBJECT
public:
explicit ClipBoard(QWidget *parent = 0);
~ClipBoard();
private slots:
//the functions that will copy and paste text from the text file
void copyToTextFile();
void paste();
private:
QPushButton *button;
QTextEdit *edit;
QString textFromClipBoard;
QClipboard *clipBoardText;
QString clipboard;
};
#endif // CLIPBOARD_H
clipboard.cpp
#include "clipboard.h"
#include "ui_clipboard.h"
ClipBoard::ClipBoard(QWidget *parent) : QDialog(parent) {
button = new QPushButton("&Paste Copied Text");
edit = new QTextEdit;
/*when user copies text and text gets to clipboard, the text is copied
from clipboard to a text file then copied from the text file to the
program*/
connect(button, SIGNAL(clicked()), this, SLOT(copyToNotepad()));
connect(button, SIGNAL(clicked()), this, SLOT(paste()));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(button);
layout->addWidget(edit);
setLayout(layout);
}
/*This function copies text from the clipboard to an internal text file
created by the program*/
void ClipBoard::copyToTextFile() {
clipboard = QApplication::clipboard()->text();
QFile output("out.txt");
if (output.open(QIODevice::ReadWrite | QFile::Truncate |
QIODevice::Text)) {
QTextStream out(&output);
out << clipboard;
}
}
/*This function then copies the text from the internal text file and pastes
it to the text edit. So the program doesn't have to deal directly with the
clipboard*/
void ClipBoard::paste() {
QFile input("out.txt");
if (input.exists()) {
if (input.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&input);
clipboard = in.readAll();
}
}
edit->setText(clipboard);
}
ClipBoard::~ClipBoard() {
}
main.cpp
#include "clipboard.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
ClipBoard w;
w.show();
return a.exec();
}
Look through and compare with your part of the code to see if there's something you did wrong. But as to how you claim it works on some Windows 8.1 systems and don't on others baffles me.
I had similar issue,
the solution for me is sleep.
void MainWindow::on_clipboard_change(){
QThread::msleep(1); //without this line I get allways empty clipboard
QString text = QGuiApplication::clipboard()->text();
qDebug() << "clipboard change event triggered ( " << text << " )";
}