PDA

View Full Version : pseudo modal dialog via a widget!



rockdemon
22nd July 2013, 18:26
Hi all,

A little conundrum...

We have an application which needs to pop up a 'dialog' which is only modal to it's parent widget, i.e. another pane of the application should not have it's flow interrupted by the dialog's modality, and to prevent it being lost it needs to actually be a widget overlaid onto it's parent widget.

I've used event filters and childevent callbacks to handle all key and mouse events and prevent calls being passed to the parent widget when appropriate. I call ->exec on an event loop and the widget works fine as a modal style dialog.

The problem i then have is that if the parent widget or application are closed while the widget is in it's exec() call the application crashes inside QEventLoop::exec() because the widget is being deleted. I've attempted to call quit() on the event loop in the destructor and waited for the isRunning flag to return false but the entire object is gone at this point.

Is there a simple trick i can use to get this sort've behaviour?

I'll get a little sample to show the behaviour together later.

Thanks,

Richard.

rockdemon
23rd July 2013, 11:48
Having problems distilling this down. It appears it may be something to do with it being an mdi app, as just using straight widgets it seems to work ok...

Will update when i have progress.

Thanks,

Rich

rockdemon
23rd July 2013, 18:50
Seems to be down to the focuschanged event i'm using to bar the focus changes. It's triggered by killing the mdi widget which contains my pseudo dialog, which then causes the focus to change again rather than the cleanup for the dialog being triggered correctly.

Now, what i really need then is for a widget to know when it's parent and therefore it are in the process of being hidden, but i cant see how to access this. Any ideas?

Thanks,

Richard.

Added after 6 minutes:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui>
#include <QMainWindow>


class MainWindow : public QWidget
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QMdiArea *mdiArea;
};

#endif // MAINWINDOW_H


mainwindow.cpp

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

return a.exec();
}


ctabmodaldialog.h

#ifndef CTABMODALDIALOG_H
#define CTABMODALDIALOG_H

//---------------------------------------------------------------------------------------

#include <QtGui>

//---------------------------------------------------------------------------------------

class CBlankWindow : public QWidget
{
Q_OBJECT

friend class CTabModalDialog;

CBlankWindow(QWidget* parent)
:QWidget(parent)
{
this->parent = parent;
//this->setAttribute(Qt::WA_DeleteOnClose, true);
this->setMouseTracking(true);
this->setGeometry(parent->contentsRect());

this->show();

startime = QDateTime::currentMSecsSinceEpoch();

hbl = new QHBoxLayout(this);
}

virtual ~CBlankWindow()
{

}

virtual void paintEvent(QPaintEvent *event)
{
QPainter p(this);

qreal progress = (qreal)(QDateTime::currentMSecsSinceEpoch() - startime) / 333.0;

progress = qMin(1.0, progress);

//Resize to cover parent.
this->setGeometry(parent->contentsRect());

p.setOpacity(0.75 * progress);
p.fillRect(this->rect(), Qt::black);

if(progress < 1.0)
QTimer::singleShot(33, this, SLOT(update()));

QWidget::paintEvent(event);
}

void SetWidget(QWidget* widget)
{
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
hbl->insertWidget(0, widget, 0, Qt::AlignCenter);
}


protected:
//Eat events so thay are not passed onto the dimmed widgets below...
virtual void mouseMoveEvent(QMouseEvent *event)
{
event->accept();
}

virtual void mousePressEvent(QMouseEvent *event)
{
event->accept();
}

virtual void mouseReleaseEvent(QMouseEvent *event)
{
event->accept();
}

virtual void keyPressEvent(QKeyEvent *event)
{
event->accept();
}

virtual void keyReleaseEvent(QKeyEvent *event)
{
event->accept();
}

QHBoxLayout *hbl;

qint64 startime;

QWidget* parent;
};

//---------------------------------------------------------------------------------------

class CTabModalDialog : public QWidget
{
Q_OBJECT
public:
explicit CTabModalDialog(QWidget *parent);
virtual ~CTabModalDialog();

// make sure you call this base after any showdialog setup in derived classes
virtual int ShowDialog( );

protected:
virtual void paintEvent(QPaintEvent*);
virtual void closeEvent(QCloseEvent *event);

QEventLoop l;

private:
CBlankWindow *bw;
int result;
bool bClosing;
signals:
void finished();
public slots:
void focusChanged(QWidget* from, QWidget * toWidget);
void ACCEPT();
void REJECT();
};


//---------------------------------------------------------------------------------------

class CTabModalMessageBox : public CTabModalDialog
{
public:

static int ShowDialog(QString title, QString text, QPixmap icon, QWidget* parent, bool ok_button = true, bool cancel_button = false);
private:
int result;

CTabModalMessageBox(QString title, QString text, QPixmap icon, QWidget* parent, bool ok_button = true, bool cancel_button = false);

};


#endif // CTABMODALDIALOG_H


ctabmodaldialog.cpp


#include "ctabmodaldialog.h"

//---------------------------------------------------------------------------------------

CTabModalDialog::CTabModalDialog(QWidget *parent):
QWidget( parent,Qt::Popup|Qt::Dialog)
{
this->bClosing = false;
this->setFocus();

bw = new CBlankWindow(parent);

bw->SetWidget(this);

this->show();
this->raise();

this->setAttribute(Qt::WA_DeleteOnClose,false);

connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget* )),this,SLOT(focusChanged(QWidget*,QWidget*)));
}


void CTabModalDialog::focusChanged(QWidget* from , QWidget* to)
{
if ( isAncestorOf(from))
{
if (!isAncestorOf(to)&& !this->bClosing)
{
QWidget * widget = dynamic_cast<QWidget * >(from);

// if this is being hidden we need to not do this!
if ( widget)
widget->setFocus(Qt::OtherFocusReason);
}
}
// else do nothing
}



///
/// On destruction remove event filters. RAF 12/7/13
///
CTabModalDialog::~CTabModalDialog()
{

this->bClosing = true;
// disconnect(qApp,SIGNAL(focusChanged(QWidget*,QWidg et*)),this,SLOT(focusChanged(QWidget*,QWidget*)));

// disconnect (this,SIGNAL(finished()),&l,SLOT(quit()));
bw->hide();
bw->deleteLater();
}

void CTabModalDialog::paintEvent(QPaintEvent *)
{
QPainter p(this);

QBrush brush(this->palette().color(QPalette::Base));
p.setBrush(brush);

p.drawRoundedRect(this->rect(), 5, 5);
}

//---------------------------------------------------------------------------------------

void CTabModalDialog::closeEvent(QCloseEvent *event)
{
this->bClosing = true;
bw->hide();
bw->close();
QWidget::closeEvent(event);
}

//---------------------------------------------------------------------------------------

int CTabModalDialog::ShowDialog( )
{
this->result = QDialog::Rejected;
this->show();
this->raise();
this->setFocus();
this->activateWindow();
connect (this,SIGNAL(finished()),&l,SLOT(quit()),Qt::QueuedConnection);

l.exec();

this->bClosing = true;
this->hide();

return this->result;
}



void CTabModalDialog::ACCEPT()
{
this->result = QDialog::Accepted;
this->bClosing = true;
this->finished();
}

void CTabModalDialog::REJECT()
{
this->result = QDialog::Rejected;
this->bClosing = true;
this->finished();
}


int CTabModalMessageBox::ShowDialog(QString title, QString text, QPixmap icon, QWidget* parent, bool ok_button, bool cancel_button )
{
CTabModalMessageBox * mb = new CTabModalMessageBox(title,text,icon,parent,ok_butt on,cancel_button);
return (static_cast<CTabModalDialog*>(mb))->ShowDialog();
}

CTabModalMessageBox::CTabModalMessageBox(QString title, QString text, QPixmap icon, QWidget* parent, bool ok_button, bool cancel_button )
:CTabModalDialog(parent)
{
result = QDialog::Rejected;
QVBoxLayout* vbl = new QVBoxLayout(this);

QHBoxLayout* hbl = new QHBoxLayout;
hbl->setAlignment(Qt::AlignLeft);

QLabel *page_icon = new QLabel(this);
page_icon->setPixmap(icon);
page_icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
hbl->addWidget(page_icon);

QLabel* page_text = new QLabel(title, this);
QFont font = page_text->font();
font.setPointSize(16);
page_text->setFont(font);

page_text->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
hbl->addWidget(page_text);

QFrame* line = new QFrame(this);
line->setGeometry(QRect(50, 40, 641, 16));
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);

vbl->addLayout(hbl);
vbl->addWidget(line);

QLabel* lbl_txt = new QLabel(text, this);
lbl_txt->setWordWrap(true);
vbl->addWidget(lbl_txt);

QHBoxLayout* hbl2 = new QHBoxLayout;
vbl->addLayout(hbl2);

if(ok_button)
{
QPushButton* btn_ok = new QPushButton(tr("Ok"), this);
connect(btn_ok, SIGNAL(clicked()), this, SLOT(ACCEPT()));
hbl2->addWidget(btn_ok);

}

if(cancel_button || (!(ok_button | cancel_button)))
{
QPushButton* btn_cancel = new QPushButton(tr("Cancel"), this);
connect(btn_cancel, SIGNAL(clicked()), this, SLOT(REJECT()));
hbl2->addWidget(btn_cancel);

}

this->setLayout(vbl);

this->show();
this->raise();
this->activateWindow();
}
mymdiwindow.h

#ifndef MYMDIWINDOW_H
#define MYMDIWINDOW_H

#include <QMdiSubWindow>
#include <QtGui>

class myMDIWindow : public QMdiSubWindow
{
Q_OBJECT
public:
explicit myMDIWindow(QWidget *parent = 0);
virtual ~myMDIWindow();

QPushButton *btnPopup;

public slots:
void popup();

};

#endif // MYMDIWINDOW_H

mymdiwindow.cpp


#include "mymdiwindow.h"
#include "ctabmodaldialog.h"

myMDIWindow::myMDIWindow(QWidget *parent) :
QMdiSubWindow(parent)
{
this->setMinimumSize(400,400);

btnPopup = new QPushButton("PopUp",this);
QVBoxLayout * lytMain = new QVBoxLayout(this);
lytMain->addWidget(btnPopup);
this->setLayout(lytMain);
connect(btnPopup,SIGNAL(clicked()),this,SLOT(popup ()));
}

myMDIWindow::~myMDIWindow()
{
foreach(QObject* o, this->children())
{
QWidget*w = dynamic_cast<QWidget*>(o);
if (w)
w->hide();
}
}

void myMDIWindow::popup()
{
CTabModalMessageBox::ShowDialog("blah","blah",QPixmap(10,10),this,true,true);
}


To reproduce, Click one of the popup buttons and then close the containings mdi widget.

anda_skoa
24th July 2013, 10:09
Would be easier to test if you could also attach it as a ZIP with .pro and all.

Cheers,
_

rockdemon
24th July 2013, 11:28
sure - give me a moment :)

zipped up :)

9357

sure - give me a moment :)

zipped up :)

9357

Added after 1 16 minutes:

Tried this but doesnt seem to fix. Maybe not as focus related as i thought?

Rich


bool CTabModalDialog::eventFilter(QObject *obj, QEvent * event)
{
qDebug()<<"type "<<event->type()<<endl;
if ( event->type()==QEvent::Hide)
this->bClosing = true;
return QWidget::eventFilter(obj,event);
}


(I thought i'd fixed it until i realised i had an extra condition in my test program which invalidated the test!)

rockdemon
24th July 2013, 13:44
I'm coming to the realisation i think that it's impossible to stop focus being shifted in this way. If i open up two of these pseudo modal dialogs it gets into a right tiz. Becomes a focus fight!

Does anybody have any other ways of achieving this pseudo focus grab and not letting it disappear into the parent control, but not interfering with other mdi windows?

Thanks,

Rich

rockdemon
25th July 2013, 16:29
We appear to have a solution which works. This involves finding the parent widget, and getting all it's children. Disable them one by one and store in a list then insert our 'dialog' and after it's shown re enable the stored children.

Hope this helps someone else trying to achieve something similar :)