I would like to have a QLineEdit that shows the clearbutton only when the mouse is over the QLineEdit (and of course the field is not empty).
I've captured the enter- and leave- events which set the property respectively. This works fine with the exception that it needs an initial enter and leave of the QLineEdit manually with the mouse. How can I initiate the QLineEdit correctly, so that it works fine from the beginning?
Trying to simulate the initial mouse movements did not have the expecting results.
cmplLineEdit.h
class cmplLineEdit : public QLineEdit {
Q_OBJECT
public:
explicit cmplLineEdit( QWidget* a_par = 0);
~cmplLineEdit();
private:
void enterEvent( QEvent* a_ev);
void leaveEvent( QEvent* a_ev);
void enableClearButton( bool a_set, int a_del = 0);
private slots:
void initialize( void);
};
cmplLineEdit.cpp
cmplLineEdit::cmplLineEdit( QWidget* a_par) : QLineEdit( a_par) {
m_completeIt = a_cmpl;
setClearButtonEnabled( false);
setFocusPolicy( Qt::StrongFocus);
QTimer::singleShot( 0, this, [=]( void) { initialize(); });
}
cmplLineEdit::~cmplLineEdit() {
}
bool cmplLineEdit::cursorIsInField() {
return rect().contains( mapFromGlobal( QCursor::pos()));
}
void cmplLineEdit::initialize( void) {
QApplication::postEvent( this, new QEvent( ! cursorIsInField() ? QEvent::Enter : QEvent::Leave));
QApplication::postEvent( this, new QEvent( cursorIsInField() ? QEvent::Enter : QEvent::Leave));
}
void cmplLineEdit::enableClearButton( bool a_set, int a_del) {
if( a_del < 0) {
setClearButtonEnabled( a_set);
} else
QTimer::singleShot( a_del, this, [=]( void) { setClearButtonEnabled( a_set); });
}
void cmplLineEdit::enterEvent( QEvent* a_ev) {
enableClearButton( true, 0);
}
void cmplLineEdit::leaveEvent( QEvent* a_ev) {
enableClearButton( false, 0);
}
Yes, mouse tracking is on (otherwise I wouldn' get the enter- and leave-events).
I rewrote the code, implementing setClearButtonEnabled() by myself. Now it's working. For anybody who is interested:
cmplEdit.h:
#ifndef CMPLLINEEDIT_H
#define CMPLLINEEDIT_H
#include <QWidget>
#include <QLineEdit>
#include <QCompleter>
#include <QAction>
class cmplLineEdit : public QLineEdit {
Q_OBJECT
public:
explicit cmplLineEdit( bool a_cmpl = true, QWidget* a_par = 0);
~cmplLineEdit();
static QIcon m_icoOff;
static QIcon m_icoOn;
private:
QAction* m_act = nullptr;
bool m_completeIt = true;
void enterEvent( QEvent* a_ev);
void leaveEvent( QEvent* a_ev);
bool cursorIsInField( void);
private slots:
void initialize( void);
void setClearIcon( bool a_set);
void setClearIcon( const QString& a_txt);
};
#endif // CMPLLINEEDIT_H
cmplEdit.cpp:
#include "cmplLineEdit.h"
#include <QTimer>
#include <QDebug>
#include <QStandardItemModel>
#include <QAbstractItemView>
#include <QEvent>
#include <QApplication>
QIcon cmplLineEdit::m_icoOn;
QIcon cmplLineEdit::m_icoOff;
cmplLineEdit::cmplLineEdit( bool a_cmpl, QWidget* a_par) : QLineEdit( a_par) {
if( m_icoOn.isNull()) {
m_icoOn = QIcon( qApp->style()->standardPixmap( QStyle::SP_LineEditClearButton));
}
m_completeIt = a_cmpl;
m_act = addAction( m_icoOn, QLineEdit::ActionPosition::TrailingPosition);
connect( this, SIGNAL( textChanged( QString)), this, SLOT( setClearIcon( QString)));
connect( m_act, SIGNAL( triggered( bool)), this, SLOT( clear()));
QTimer::singleShot( 0, [ this]( void) { initialize(); });
}
cmplLineEdit::~cmplLineEdit() {
}
bool cmplLineEdit::cursorIsInField() {
return rect().contains( mapFromGlobal( QCursor::pos()));
}
void cmplLineEdit::initialize( void) {
setClearIcon( cursorIsInField());
}
void cmplLineEdit::enterEvent( QEvent* a_ev) {
setClearIcon( true);
}
void cmplLineEdit::leaveEvent( QEvent* a_ev) {
setClearIcon( false);
}
void cmplLineEdit::setClearIcon( bool a_set) {
if( m_act == nullptr)
return;
a_set = a_set && ! text().isEmpty();
m_act->setIcon( a_set ? m_icoOn : QIcon());
m_act->setVisible( a_set);
}
void cmplLineEdit::setClearIcon( const QString& a_txt) {
setClearIcon( ! a_txt.isEmpty());
}
Related
I need to create a group box that supports HTML text when we set
MyGroupBox *gb = new MyGroupBox();
gb->setTitle("<u> This is underlined text</u>");
I have tried some searches but no results. In my head right now I think only about to set style for my groupbox. Something like this:
MyGroupBox.cpp
MyGroupBox::MyGroupBox( QWidget *p_parent ) : QGroupBox( p_parent )
{
setStyle( &m_style );
}
TitleStyle.hpp
class TitleStyle : public QProxyStyle
{
public:
TitleStyle() = default;
virtual void drawComplexControl( ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = nullptr ) const override;
};
TitleStyle.cpp
void TitleStyle::drawComplexControl( ComplexControl p_control, const QStyleOptionComplex *p_option, QPainter *p_painter, const QWidget *p_widget ) const
{
if ( p_control == CC_GroupBox )
{
if ( const QStyleOptionGroupBox *title = qstyleoption_cast<const QStyleOptionGroupBox *>( p_option ) )
{
QTextDocument td;
td.setHtml( title->text );
td.drawContents( p_painter );
}
}
else
{
QProxyStyle::drawComplexControl( p_control, p_option, p_painter, p_widget );
}
}
This still does not work. I know my drawComplexControl is weird, but that is what in my mind now. Can anyone tell me if I am going in the right direction? If yes, how could I change the class TitleStyle. If not, how could I do?
Solution:
It took me a while to find my mistake. With the code above, the title should be rich text supported already.
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QProxyStyle>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class TitleStyle : public QProxyStyle
{
public:
TitleStyle() = default;
virtual void drawComplexControl( ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = nullptr ) const override;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
TitleStyle m_style;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTextDocument>
void TitleStyle::drawComplexControl( ComplexControl p_control, const QStyleOptionComplex *p_option, QPainter *p_painter, const QWidget *p_widget ) const
{
if ( p_control == CC_GroupBox )
{
if ( const QStyleOptionGroupBox *title = qstyleoption_cast<const QStyleOptionGroupBox *>( p_option ) )
{
QTextDocument td;
td.setHtml( title->text );
td.drawContents( p_painter );
}
}
else
{
QProxyStyle::drawComplexControl( p_control, p_option, p_painter, p_widget );
}
}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->groupBox->setStyle(&m_style);
ui->groupBox->setTitle("<b><u>This is an underlined title</u></b>");
}
MainWindow::~MainWindow()
{
delete ui;
}
Result:
Actually I have found my mistake in other positions. With this code, the title should be rich text supported already. I have tried with Qt Creator and it worked. I update my solution above.
I tried to create QProgressBar according to Manual. Yet it works really bad (for instance, if I create QProgressDialog in the constructor, it will appear as soon as app is running, so I decided to use QProgressBar). But there is a problem:
Although I used advices from the internet. My code:
UPD![2]
// StudentAbsenceTableApp.h
using Job = std::function<void ()>;
Q_DECLARE_METATYPE(Job)
class StudentAbsenceTableApp{
public:
StudentAbsenceTableApp(QWidget *parent = 0);
private:
Q_SIGNAL void reqLoadFile(const QString& fileName);
Q_SIGNAL void reqSaveFile(const QString& fileName);
Q_SIGNAL void reqGui(const Job&);
bool documentModified;
QProgressBar *progressBar;
};
// StudentAbsenceTableApp.cpp
StudentAbsenceTableApp::StudentAbsenceTableApp(QWidget *parent)
: QMainWindow(parent)
{
// ...
setStatusBar(new QStatusBar(this));
qRegisterMetaType<Job>();
progressBar = new QProgressBar(statusBar());
progressBar->setMinimum(0);
progressBar->setMaximum(0);
progressBar->setMaximumWidth(150);
progressBar->hide();
statusBar()->addPermanentWidget(progressBar);
connect(this, &StudentAbsenceTableApp::reqLoadFile, this, [this] (const QString& fileName){
QtConcurrent::run(this, &StudentAbsenceTableApp::loadFile, fileName);
});
connect(this, &StudentAbsenceTableApp::reqGui, [this](const Job & job){
job();
});
}
// funtion that emit reqLoadFile(fileName)
bool StudentAbsenceTableApp::loadFile(const QString& fileName)
{
reqGui([=] () { progressBar->show(); });
auto xmlParser = XMLParser(model);
try
{
reqGui([&] () {
xmlParser.read(fileName);
setCurrentFileName(fileName);
statusBar()->showMessage(tr("Файл загружен"), 2000);
documentModified = false;
});
}
catch(FileOpenException)
{
reqGui([=] () {
QMessageBox::warning(this, "Ошибка!", "Ошибка открытия файла!", QMessageBox::Ok);
statusBar()->showMessage(tr("Загрузка отменена"), 2000);
});
return false;
}
catch(FileReadException)
{
reqGui([=] () {
QMessageBox::warning(this, "Ошибка!", "Ошибка чтения файла!", QMessageBox::Ok);
statusBar()->showMessage(tr("Загрузка отменена"), 2000);
});
return false;
}
reqGui([=] () { progressBar->hide(); });
return true;
}
I don't know how to write code, that is possible to compile, because there is a lot of code.
No QWidget (and derived classes) methods provided by Qt are thread-safe. Thus you can't access QProgressBar nor any other widgets from any thread other then the GUI thread.
The experimentFunction runs in a non-GUI thread and thus must not access widgets. You must figure out some other means of communication, e.g. using signals and slots. Recall that you're free to emit signals in experimentFunction, since the signal implementations are by contract thread-safe.
It's all really simple, and you don't need the future watcher. In your attempts to "fix" the issue, you've hopelessly combobulated your code.
For other ways of invoking methods safely across threads, see this question and that question.
// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-progress-future-44445248
#include <QtConcurrent>
#include <QtWidgets>
#include <exception>
#include <functional>
struct FileOpenException : std::exception {};
struct FileReadException : std::exception {};
struct Model {};
struct XMLParser {
XMLParser(Model &) {}
void read(const QString &) {
static int outcome;
QThread::sleep(3);
switch (outcome++ % 3) {
case 0: return;
case 1: throw FileOpenException();
case 2: throw FileReadException();
}
}
};
using Job = std::function<void()>;
Q_DECLARE_METATYPE(Job)
class StudentAbsenceTable : public QMainWindow {
Q_OBJECT
QStatusBar m_statusBar;
QProgressBar m_progress;
QPushButton m_start{"Start Concurrent Task"};
Model m_model;
bool m_documentModified = {};
public:
StudentAbsenceTable() {
qRegisterMetaType<Job>();
m_statusBar.addPermanentWidget(&m_progress);
m_progress.setMinimum(0);
m_progress.setMaximum(0);
m_progress.setMaximumWidth(150);
m_progress.hide();
setStatusBar(&m_statusBar);
setCentralWidget(&m_start);
connect(&m_start, &QPushButton::clicked, this, [this]{
m_start.setEnabled(false);
QtConcurrent::run(this, &StudentAbsenceTable::loadFile);
});
connect(this, &StudentAbsenceTable::reqGui, this, [this](const Job & job){
job();
});
}
private:
bool loadFile() {
reqGui([=]{ m_progress.show(); });
auto fileName = QStringLiteral("/media/bsuir/data.xml");
auto xmlParser = XMLParser(m_model);
try {
xmlParser.read(fileName);
reqGui([=]{
setCurrentFileName(fileName);
statusBar()->showMessage(tr("Файл загружен"), 2000);
m_documentModified = false;
});
}
catch(FileOpenException&) {
reqGui([=]{
QMessageBox::warning(this, "Ошибка!", "Ошибка открытия файла!", QMessageBox::Ok);
statusBar()->showMessage(tr("Загрузка отменена"), 2000);
});
}
catch(FileReadException&) {
reqGui([=]{
QMessageBox::warning(this, "Ошибка!", "Ошибка чтения файла!", QMessageBox::Ok);
statusBar()->showMessage(tr("Загрузка отменена"), 2000);
});
}
reqGui([=]{ m_progress.hide(); m_start.setEnabled(true); });
return false;
}
Q_SIGNAL void reqGui(const Job &);
void setCurrentFileName(const QString &) {}
};
int main(int argc, char ** argv) {
QApplication app(argc, argv);
StudentAbsenceTable ui;
ui.setMinimumSize(350, 350);
ui.show();
return app.exec();
}
#include "main.moc"
I'm trying to render some QQuickPaintedItems and their children, but I got only the parents rendered. Please tell me what's wrong with my code.
Graph.h (my custom QQuickPaintedItem):
#include <QQuickPaintedItem>
#include "voltrule.h"
class Graph : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit Graph(QQuickItem *parent = 0);
void paint(QPainter *painter);
private:
VoltRule *_rule;
};
Graph::Graph(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
_rule = new VoltRule(this);
_rule->setParentItem(this);
}
void Graph::paint(QPainter *painter)
{
QRect rect(10, 20, 100, 200);
QPen pen(Qt::green);
painter->setPen(pen);
painter->drawRect(rect);
}
VoltRule.h
#include <QQuickPaintedItem>
class VoltRule : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit VoltRule(QQuickItem *parent = 0);
void paint(QPainter *painter);
signals:
public slots:
};
VoltRule.cpp
#include "voltrule.h"
#include <QPainter>
VoltRule::VoltRule(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
setFlag(QQuickItem::ItemHasContents);
}
void VoltRule::paint(QPainter *painter)
{
QRect rect(10, 20, 100, 200);
QPen pen(Qt::white);
painter->setPen(pen);
painter->drawRect(rect);
}
main.qml
ApplicationWindow {
width: 1367
height: 766
Graph{
anchors.fill:parent
}
}
Thanks in advance
jbh answered the question; just setting a size in the VoltRule constructor solves the problem:
VoltRule::VoltRule(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
setSize(QSizeF(100, 200));
}
It looks a bit strange because the child is drawing white on top of the parent's green lines, erasing just part of the parent's lines.
Here is code that sets the size based on the parent's size. Resize the window and you will see the child updates without an explicit "update()" call:
VoltRule.h
class VoltRule : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit VoltRule(QQuickItem *parent = 0);
void paint(QPainter *painter);
private:
QQuickItem* parentItem();
void onParentChanged();
void onParentWidthChanged();
void onParentHeightChanged();
};
VoltRule.cpp
QQuickItem* VoltRule::parentItem()
{
return qobject_cast<QQuickItem*>(parent());
}
VoltRule::VoltRule(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
connect(this, &QQuickItem::parentChanged, this, &VoltRule::onParentChanged);
onParentChanged();
}
void VoltRule::paint(QPainter *painter)
{
QRect rect(.1*width(), .1*height(), .8*width(), .8*height());
QPen pen(Qt::blue);
painter->setPen(pen);
painter->drawRect(rect);
}
void VoltRule::onParentChanged()
{
// disconnect signals from previous parent, if there was one
disconnect();
if (const auto obj = parentItem()) {
connect(obj, &QQuickItem::widthChanged, this, &VoltRule::onParentWidthChanged);
connect(obj, &QQuickItem::heightChanged, this, &VoltRule::onParentHeightChanged);
onParentWidthChanged();
onParentHeightChanged();
}
}
void VoltRule::onParentWidthChanged()
{
if (const auto obj = parentItem()) {
setWidth(obj->width());
}
}
void VoltRule::onParentHeightChanged()
{
if (const auto obj = parentItem()) {
setHeight(obj->height());
}
}
Is there any way to blur a widget in Qt? For instance, supose I want to create a 'Loading...' dialog and blur the background (not active window).
This answer is in a series of my overlay-related answers: first, second, third.
It requires some care if you wish for it to work on all platforms. You can't apply effects directly to top-level windows. The hierarchy needs to look as follows:
ContainerWidget
|
+----------+
| |
**Target** Overlay
You apply the effect to the Target widget (say, a QMainWindow). The ContainerWidget is a helper class that keeps the children occupying the full size of the widget. This obviates the need for an explicit zero-margin layout.
The below works, even on a Mac. It wouldn't, had you foregone the ContainerWidget. This works portably on Qt 5 only, unfortunately. On Qt 4, your "cross platform" support excludes Mac :( It works OK on Windows using either Qt 4 (4.8.5) or Qt 5.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-blur-19383427
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class OverlayWidget : public QWidget {
void newParent() {
if (!parent()) return;
parent()->installEventFilter(this);
raise();
}
public:
explicit OverlayWidget(QWidget *parent = {}) : QWidget(parent) {
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
newParent();
}
protected:
//! Catches resize and child events from the parent widget
bool eventFilter(QObject *obj, QEvent *ev) override {
if (obj == parent()) {
if (ev->type() == QEvent::Resize)
resize(static_cast<QResizeEvent*>(ev)->size());
else if (ev->type() == QEvent::ChildAdded)
raise();
}
return QWidget::eventFilter(obj, ev);
}
//! Tracks parent widget changes
bool event(QEvent *ev) override {
if (ev->type() == QEvent::ParentAboutToChange) {
if (parent()) parent()->removeEventFilter(this);
}
else if (ev->type() == QEvent::ParentChange)
newParent();
return QWidget::event(ev);
}
};
class ContainerWidget : public QWidget
{
public:
explicit ContainerWidget(QWidget *parent = {}) : QWidget(parent) {}
void setSize(QObject *obj) {
if (obj->isWidgetType()) static_cast<QWidget*>(obj)->setGeometry(rect());
}
protected:
//! Resizes children to fill the extent of this widget
bool event(QEvent *ev) override {
if (ev->type() == QEvent::ChildAdded) {
setSize(static_cast<QChildEvent*>(ev)->child());
}
return QWidget::event(ev);
}
//! Keeps the children appropriately sized
void resizeEvent(QResizeEvent *) override {
for(auto obj : children()) setSize(obj);
}
};
class LoadingOverlay : public OverlayWidget
{
public:
LoadingOverlay(QWidget *parent = {}) : OverlayWidget{parent} {
setAttribute(Qt::WA_TranslucentBackground);
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.fillRect(rect(), {100, 100, 100, 128});
p.setPen({200, 200, 255});
p.setFont({"arial,helvetica", 48});
p.drawText(rect(), "Loading...", Qt::AlignHCenter | Qt::AlignTop);
}
};
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
using QT_PREPEND_NAMESPACE(QTimer);
#else
using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
class QTimer : public Q_QTimer {
public:
QTimer(QTimer *parent = nullptr) : Q_QTimer(parent) {}
template <typename F> static void singleShot(int period, F &&fun) {
struct Helper : public QObject {
F fun;
QBasicTimer timer;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() != timer.timerId()) return;
fun();
deleteLater();
}
Helper(int period, F &&fun) : fun(std::forward<F>(fun)) {
timer.start(period, this);
}
};
new Helper(period, std::forward<F>(fun));
}
};
#endif
}
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
ContainerWidget base;
QLabel label("Dewey, Cheatem and Howe, LLC.", &base);
label.setFont({"times,times new roman", 32});
label.setAlignment(Qt::AlignCenter);
label.setGraphicsEffect(new QGraphicsBlurEffect);
LoadingOverlay overlay(&base);
base.show();
compat::QTimer::singleShot(2000, [&]{
overlay.hide();
label.setGraphicsEffect({});
});
return a.exec();
}
See QGraphicsBlurEffect Class and QWidget::setGraphicsEffect().
You can refer to this article if you want to apply blur effect on an image. After you create your blurred image you can draw it in QWidget::paintEvent() function.
Can anybody give me a working example of use CopyFileEx with progress callback in Qt?
I found some scratch and tried to merge it but with no success. I even couldn't pass CopyProgressRoutine function as an argument of CopyFileEx because I couldn't declare pointer to this function.
I'm not so good to port code from other IDEs, so I need help of yours.
The code below is a complete, self-contained example. It works under both Qt 5 and Qt 4, and uses C++11 (e.g. Visual Studio 2015 & newer).
main.cpp
// https://github.com/KubaO/stackoverflown/tree/master/questions/copyfileex-19136936
#include <QtGui>
#include <QtConcurrent>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
#include <windows.h>
#include <comdef.h>
//#define _WIN32_WINNT _WIN32_WINNT_WIN7
static QString toString(HRESULT hr) {
_com_error err{hr};
return QStringLiteral("Error 0x%1: %2").arg((quint32)hr, 8, 16, QLatin1Char('0'))
.arg(err.ErrorMessage());
}
static QString getLastErrorMsg() {
return toString(HRESULT_FROM_WIN32(GetLastError()));
}
static QString progressMessage(ULONGLONG part, ULONGLONG whole) {
return QStringLiteral("Transferred %1 of %2 bytes.")
.arg(part).arg(whole);
}
class Copier : public QObject {
Q_OBJECT
BOOL m_stop;
QMutex m_pauseMutex;
QAtomicInt m_pause;
QWaitCondition m_pauseWait;
QString m_src, m_dst;
ULONGLONG m_lastPart, m_lastWhole;
void newStatus(ULONGLONG part, ULONGLONG whole) {
if (part != m_lastPart || whole != m_lastWhole) {
m_lastPart = part;
m_lastWhole = whole;
emit newStatus(progressMessage(part, whole));
}
}
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
static COPYFILE2_MESSAGE_ACTION CALLBACK copyProgress2(
const COPYFILE2_MESSAGE *message, PVOID context);
#else
static DWORD CALLBACK copyProgress(
LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred,
LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred,
DWORD streamNo, DWORD callbackReason, HANDLE src, HANDLE dst,
LPVOID data);
#endif
public:
Copier(const QString & src, const QString & dst, QObject * parent = nullptr) :
QObject{parent}, m_src{src}, m_dst{dst} {}
Q_SIGNAL void newStatus(const QString &);
Q_SIGNAL void finished();
/// This method is thread-safe
Q_SLOT void copy();
/// This method is thread-safe
Q_SLOT void stop() {
resume();
m_stop = TRUE;
}
/// This method is thread-safe
Q_SLOT void pause() {
m_pause = true;
}
/// This method is thread-safe
Q_SLOT void resume() {
if (m_pause)
m_pauseWait.notify_one();
m_pause = false;
}
~Copier() override { stop(); }
};
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
void Copier::copy() {
m_lastPart = m_lastWhole = {};
m_stop = FALSE;
m_pause = false;
QtConcurrent::run([this]{
COPYFILE2_EXTENDED_PARAMETERS params{
sizeof(COPYFILE2_EXTENDED_PARAMETERS), 0, &m_stop,
Copier::copyProgress2, this
};
auto rc = CopyFile2((PCWSTR)m_src.utf16(), (PCWSTR)m_dst.utf16(), ¶ms);
if (!SUCCEEDED(rc))
emit newStatus(toString(rc));
emit finished();
});
}
COPYFILE2_MESSAGE_ACTION CALLBACK Copier::copyProgress2(
const COPYFILE2_MESSAGE *message, PVOID context)
{
COPYFILE2_MESSAGE_ACTION action = COPYFILE2_PROGRESS_CONTINUE;
auto self = static_cast<Copier*>(context);
if (message->Type == COPYFILE2_CALLBACK_CHUNK_FINISHED) {
auto &info = message->Info.ChunkFinished;
self->newStatus(info.uliTotalBytesTransferred.QuadPart, info.uliTotalFileSize.QuadPart);
}
else if (message->Type == COPYFILE2_CALLBACK_ERROR) {
auto &info = message->Info.Error;
self->newStatus(info.uliTotalBytesTransferred.QuadPart, info.uliTotalFileSize.QuadPart);
emit self->newStatus(toString(info.hrFailure));
action = COPYFILE2_PROGRESS_CANCEL;
}
if (self->m_pause) {
QMutexLocker lock{&self->m_pauseMutex};
self->m_pauseWait.wait(&self->m_pauseMutex);
}
return action;
}
#else
void Copier::copy() {
m_lastPart = m_lastWhole = {};
m_stop = FALSE;
m_pause = false;
QtConcurrent::run([this]{
auto rc = CopyFileExW((LPCWSTR)m_src.utf16(), (LPCWSTR)m_dst.utf16(),
©Progress, this, &m_stop, 0);
if (!rc)
emit newStatus(getLastErrorMsg());
emit finished();
});
}
DWORD CALLBACK Copier::copyProgress(
const LARGE_INTEGER totalSize, const LARGE_INTEGER totalTransferred,
LARGE_INTEGER, LARGE_INTEGER, DWORD,
DWORD, HANDLE, HANDLE,
LPVOID data)
{
auto self = static_cast<Copier*>(data);
self->newStatus(totalTransferred.QuadPart, totalSize.QuadPart);
if (self->m_pause) {
QMutexLocker lock{&self->m_pauseMutex};
self->m_pauseWait.wait(&self->m_pauseMutex);
}
return PROGRESS_CONTINUE;
}
#endif
struct PathWidget : public QWidget {
QHBoxLayout layout{this};
QLineEdit edit;
QPushButton select{"..."};
QFileDialog dialog;
explicit PathWidget(const QString & caption) : dialog{this, caption} {
layout.setMargin(0);
layout.addWidget(&edit);
layout.addWidget(&select);
connect(&select, SIGNAL(clicked()), &dialog, SLOT(show()));
connect(&dialog, SIGNAL(fileSelected(QString)), &edit, SLOT(setText(QString)));
}
};
class Ui : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPlainTextEdit m_status;
PathWidget m_src{"Source File"}, m_dst{"Destination File"};
QPushButton m_copy{"Copy"};
QPushButton m_cancel{"Cancel"};
QStateMachine m_machine{this};
QState s_stopped{&m_machine};
QState s_copying{&m_machine};
Q_SIGNAL void stopCopy();
Q_SLOT void startCopy() {
auto copier = new Copier(m_src.edit.text(), m_dst.edit.text(), this);
connect(copier, SIGNAL(newStatus(QString)), &m_status, SLOT(appendPlainText(QString)));
connect(copier, SIGNAL(finished()), SIGNAL(copyFinished()));
connect(copier, SIGNAL(finished()), copier, SLOT(deleteLater()));
connect(this, SIGNAL(stopCopy()), copier, SLOT(stop()));
copier->copy();
}
Q_SIGNAL void copyFinished();
public:
Ui() {
m_layout.addRow("From:", &m_src);
m_layout.addRow("To:", &m_dst);
m_layout.addRow(&m_status);
m_layout.addRow(&m_copy);
m_layout.addRow(&m_cancel);
m_src.dialog.setFileMode(QFileDialog::ExistingFile);
m_dst.dialog.setAcceptMode(QFileDialog::AcceptSave);
m_status.setReadOnly(true);
m_status.setMaximumBlockCount(5);
m_machine.setInitialState(&s_stopped);
s_stopped.addTransition(&m_copy, SIGNAL(clicked()), &s_copying);
s_stopped.assignProperty(&m_copy, "enabled", true);
s_stopped.assignProperty(&m_cancel, "enabled", false);
s_copying.addTransition(&m_cancel, SIGNAL(clicked()), &s_stopped);
s_copying.addTransition(this, SIGNAL(copyFinished()), &s_stopped);
connect(&s_copying, SIGNAL(entered()), SLOT(startCopy()));
connect(&s_copying, SIGNAL(exited()), SIGNAL(stopCopy()));
s_copying.assignProperty(&m_copy, "enabled", false);
s_copying.assignProperty(&m_cancel, "enabled", true);
m_machine.start();
}
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
Ui ui;
ui.show();
return a.exec();
}
#include "main.moc"