PDA

View Full Version : QSplashScreen Weirdness



mclark
15th November 2007, 23:58
I have a problem using QSplashScreen. My application generates a log (text) file and has a menu item to let the user view a parsed display of this file in a table in a dialog. Since the file can be up to 500k, it takes a while to parse so I use a QSplashScreen to distract the user ;) during the parsing (sometimes more than 5000 lines). The QSplashScreen is semi-transparent and displays a message indicating how many lines of the log file have been parsed.

I know this is not normal usage of a QSplashScreen but it works fine except for the following.

My problem is that if a user repeatedly clicks outside the QSplashScreen, in the main window title bar for example, after reading between 700 and 1200 lines of the > 5000 line file:
the message in the QSplashScreen freezes at a line number (but the file continues to be parsed)
the main window MAY be drawn over the QSplashScreen
the client area of the main window goes away (repainted in white)
when the file is finished being read, the dialog displays the log file contents correctly but the QSplashScreen is drawn on top and never goes away until the application closes

While all of the results above are irritating, the worst is that the splash screen never goes away (even after the dialog is gone).

I have tried trapping (and ignoring) the mousePressEvents in both the main window and the dialog but the mousePressEvent() function never gets the mouse events while the QSplashScreen is active. This is not surprising since it appears that the QSplashScreen has focus.

I noticed that a mouse press should cause the splash screen to become hidden, but in this case it doesn't happen.

BTW, I'm using 4.2.2 with VS2005.

Any suggestions for fixing this weird mouse click problem?



// From the class definition...
QTableWidget* m_logTable;
MainWin* m_pMainWin;
QSplashScreen* m_pSplash;
QFile m_logFile;

// Show Log file dialog ctor
ShowLog::ShowLog( QWidget *pParent ) : QDialog( pParent )
{
m_pMainWin = static_cast<MainWin*>( pParent );
QDir::setCurrent( QDir::currentPath() );

m_logFile.setFileName( LOG_FILENAME );

QIODevice::OpenMode nOpenMode = QIODevice::ReadOnly | QIODevice::Text;

if ( !m_logFile.exists() )
reject();

if ( !m_logFile.open( nOpenMode ) )
reject();

QPixmap pMap( SHOWLOG_SPLASH_IMG );

// Make the corners fully transparent (rounded-corner bitmap)
if ( !pMap.mask().isNull() )
{
QImage img( SHOWLOG_SPLASH_IMG );
if ( img.hasAlphaChannel() )
pMap.setMask( QBitmap::fromImage( img.createAlphaMask() ) );
else
pMap.setMask( QBitmap::fromImage( img.createHeuristicMask() ) );
}

ui.setupUi( this );

QPalette palette = ui.centralWidget->palette();
palette.setColor( ui.centralWidget->backgroundRole(), COLOR_ManageBkg );
ui.centralWidget->setPalette( palette );

m_logTable = new QTableWidget( 0, 4, this );

// Layout the Show Logfile dialog
.
.
.
// Initialize the text stream
QTextStream logStream( &m_logFile );
QString sLine, sSrc, sType, sTime, sMsg;
QString sMsgText( " Reading %1: line %2" );
Qt::ItemFlags flags;
int start, end, nRow = 0;

// Skip the header
while ( (sLine = logStream.readLine())[0] == '*' );

// Set up the splash screen
m_pSplash = new QSplashScreen( pParent, pMap );
m_pSplash->setWindowOpacity( 0.8 ); // make the image semi-transparent
palette = m_pSplash->palette();
palette.setBrush( QPalette::Window, QBrush( pMap ) );
m_pSplash->setPalette( palette );
m_pSplash->setFixedSize( pMap.size() );
m_pSplash->setMask( pMap.mask() );
QFont font = m_pSplash->font();
font.setBold( true );
m_pSplash->setFont( font ); // Set the m_pSplash font to bold

// Center the splash screen in the main application window
m_pSplash->move( m_pMainWin->pos().x() +
(m_pMainWin->frameGeometry().width() / 2) -
(m_pSplash->frameGeometry().width() / 2),
m_pMainWin->pos().y() +
(m_pMainWin->frameGeometry().height() / 2) -
(m_pSplash->frameGeometry().height() / 2) );
m_pSplash->show();

// Load the text
while ( !logStream.atEnd() || !sLine.isEmpty() )
{
if ( sLine.isEmpty() ) // skip any empty lines
{
sLine = logStream.readLine();
continue;
}

// Parse the Source
.
.
.
// Fill a table row
m_logTable->insertRow( nRow );
.
.
.
nRow++;
sLine = logStream.readLine();

// Incremental splash message
m_pSplash->showMessage( sMsgText.arg( LOG_FILENAME ).arg( nRow ),
nMsgAlignment, textColor );
}

// Close the QFile
if ( m_logFile.isOpen() )
m_logFile.close();

// Set sort indicator in column headers
m_logTable->horizontalHeader()->setSortIndicatorShown( true );
m_logTable->setSortingEnabled( true );

m_pSplash->finish( this );

}

mchara
16th November 2007, 10:14
hi,
Maybe disabling main window while splash is active would fix your error?

wysota
16th November 2007, 12:02
Why not use QProgressDialog or another modal dialog instead?

mclark
16th November 2007, 15:55
disabling main window while splash is active

Disabling the main window would seem like the way to go, unfortunately it doesn't seem to take effect until after the splash screen goes away and after the dialog is shown.

void MainWin::showLog( void )
{
setEnabled( false );

ShowLog showDlg( this );
showDlg.exec();

setEnabled( true );

} // showLog(
This call is a slot for the menu selection triggered() signal and it appears that the setEnabled() is preempted by the splash screen action in the dialog ctor.




Why not use QProgressDialog or another modal dialog instead?
I wanted to use a splash screen because I don't know in advance what the maximum number of parsable (is this a word?) lines I have to deal with. Although, I may be able to get similar functionality with some other type of modal dialog. I just liked the idea of having a semi-transparent, rounded corner bitmap and the QSplashScreen allowed me to do that with just a few lines of code.

Do you have another modal dialog type in mind?

wysota
16th November 2007, 16:10
Do you have another modal dialog type in mind?

Subclass QDialog, make it frameless and display a label on it that contains the bitmap of your choice. You can also use QWidget::setMask to make it round or something...

mclark
16th November 2007, 18:17
Subclass QDialog, make it frameless and display a label on it that contains the bitmap of your choice. You can also use QWidget::setMask to make it round or something...

Thanks for the suggestion wysota.

The code below 'kind of' works. A semi-transparent, rounded-corner dialog appears with the pixmap in it. If I only add the label with the pixmap, the pixmap appears as if it is inside the dialog. What I mean by that is there is a band of semi-transparent area around all 4 sides of the pixmap. Shrinking the dialog size only clips the pixmap.

For parsing progress display, I thought I could add a second label to display the message text (similar to what QSplashScreen offers.) This never shows.

Is there a way to have the pixmap in the label to completely fill the dialog area without clipping, and, for a line of text to be superimposed on the pixmap?


QPixmap pMap( SHOWLOG_SPLASH_IMG );

// Make the corners fully transparent (rounded-corner bitmap)
if ( !pMap.mask().isNull() )
{
QImage img( SHOWLOG_SPLASH_IMG );
if ( img.hasAlphaChannel() )
pMap.setMask( QBitmap::fromImage( img.createAlphaMask() ) );
else
pMap.setMask( QBitmap::fromImage( img.createHeuristicMask() ) );
}

QDialog splashDlg( this, Qt::FramelessWindowHint );

splashDlg.setWindowOpacity( 0.8 ); // make the dialog semi-transparent
palette = splashDlg.palette();
palette.setBrush( QPalette::Window, QBrush( pMap ) );
splashDlg.setPalette( palette );
splashDlg.setFixedSize( pMap.size().width(), pMap.size().height() + 40 );
splashDlg.setMask( pMap.mask() );

QFont font = splashDlg.font();
font.setBold( true );
splashDlg.setFont( font ); // Set the splashDlg font to bold

QLabel* pTxtLabel = new QLabel();
QLabel* pImgLabel = new QLabel();
pImgLabel->resize( pMap.size() );
pImgLabel->setPixmap( pMap );

QVBoxLayout *vSplashBox = new QVBoxLayout( &splashDlg );
vSplashBox->addWidget( pTxtLabel, Qt::AlignTop | Qt::AlignLeft );
vSplashBox->addWidget( pImgLabel, Qt::AlignCenter );

splashDlg.show();
.
.
.
while ( !logStream.atEnd() || !sLine.isEmpty() )
{
.
.
.
// Increment splash message every 50 iterations
if ( !(nRow % 50) )
pTxtLabel->setText( sMsgText.arg( LOG_FILENAME ).arg( nRow ) );
}

splashDlg.close();

wysota
16th November 2007, 20:09
If you override paint event of the dialog and draw the image there yourself, it should work. You can also use a label for that - just make sure it's not part of the layout and that it is lowered behind other widgets of the dialog. I'd suggest reimplementing the paint event though.

mclark
16th November 2007, 21:22
I went with you're earlier suggestion of using a progress dialog. I get the file size and use that as my max value. In my line reading loop I increment the progress value with the length of the current line.

But, if clicking around the main window during the progress dialog progression, when the progress dialog goes away and the log display dialog shows, I get some unexpected data in the table in the dialog, and when closing it, a crash. This only happens in the release version of the app, I assume that the MS IDE is handling these problems in the debug version although I get no indications of problems.

I'm doing the file parsing in a loop in the log display dialog constructor, could this be a problem? If so, I'm not sure how else to do this using QDialog.

wysota
16th November 2007, 21:47
I'd have to see the exact code. What you describe shouldn't happen, provided your progress dialog is modal.

mclark
16th November 2007, 21:55
UPDATE: The crashing is due to bad data (log file stored in the debug directory is good while the file in the release directory was bad). So, I'll skip the QSplashScreen for now - still has the white-out problem I mentioned in the beginning of this thread - and go with the QProgressDialog. Thanks for your time, you do a great service!

Here is the whole bloody mess... I'm going to remove all progress dialog modifications (FramelessWindowHint, opacity, font type and color...) and see what happens. Thanks for taking the time to review this with me. I very much appreciate it.

Doing only the following has no effect. Still get a crash if multi-clicking on the main window header.
QProgressDialog progDlg( QString( "Reading %1 . . ." ).arg( LOG_FILENAME ),
"Cancel", 0, nProgDlgMax, this );
progDlg.setModal( true );



class ShowLog : public QDialog
{
Q_OBJECT

public:
ShowLog( QWidget* pParent = 0 );
virtual ~ShowLog() {}

private:
Ui::ShowLogClass ui;

QTableWidget* m_logTable;
MainWin* m_pMainWin;

private slots:
void on_okBtn_clicked( void ) { accept(); }
void on_clearBtn_clicked( void );

};

ShowLog::ShowLog( QWidget *pParent ) : QDialog( pParent )
{
m_pMainWin = static_cast<MainWin*>( pParent );
QDir::setCurrent( QDir::currentPath() );

QFile logFile( LOG_FILENAME );

QIODevice::OpenMode nOpenMode = QIODevice::ReadOnly | QIODevice::Text;

if ( !logFile.exists() || !logFile.open( nOpenMode ) )
reject();

ui.setupUi( this );

QPalette palette = ui.centralWidget->palette();
palette.setColor( ui.centralWidget->backgroundRole(), COLOR_ManageBkg );
ui.centralWidget->setPalette( palette );

m_logTable = new QTableWidget( 0, 4, this );

// Layout the Show Logfile dialog
QStringList sLabels;
sLabels << "Source" << "Type" << "Time" << "Message";

m_logTable->setHorizontalHeaderLabels( sLabels );
m_logTable->verticalHeader()->hide();
m_logTable->setColumnWidth( COL0_Src, 78 );
m_logTable->setColumnWidth( COL1_Type, 70 );
m_logTable->setColumnWidth( COL2_Time, 130 );
m_logTable->setColumnWidth( COL3_Msg, 510 );
m_logTable->setMinimumWidth( 775 );
// Enable sorting after the table is populated, otherwise sorting
// may interfere with the insertion order
m_logTable->setSortingEnabled( false );

QSize bs = ui.okBtn->sizeHint().expandedTo( ui.clearBtn->sizeHint() );
ui.okBtn->setFixedSize( bs );
ui.clearBtn->setFixedSize( bs );
ui.okBtn->setDefault( true );

// Set the "What's This?" text for controls
m_logTable->setWhatsThis( WT_LOG_TABLE );
ui.okBtn->setWhatsThis( WT_OK_BTN );
ui.clearBtn->setWhatsThis( WT_CLEARLOG_BTN );

QVBoxLayout *vbox = new QVBoxLayout( this );
vbox->addSpacing( 10 );
vbox->addWidget( m_logTable, Qt::AlignHCenter );
vbox->addStretch( 0 );
vbox->addSpacing( 15 );

// Add a stretch to either side of the box to keep the
// button centered whenever stretching horizontally
QHBoxLayout* hbox = new QHBoxLayout();
hbox->addStretch();
hbox->addSpacing( 100 );
hbox->addWidget( ui.clearBtn );
hbox->addSpacing( 60 );
hbox->addWidget( ui.okBtn );
hbox->addSpacing( 100 );
hbox->addStretch();

vbox->addLayout( hbox );
vbox->addSpacing( 7 );

resize( sizeHint() );

// Create a splash-like dialog
qint64 nProgDlgMax = logFile.size();
QProgressDialog progDlg( QString( "Reading %1 . . ." ).arg( LOG_FILENAME ),
"Cancel", 0, nProgDlgMax, this, Qt::FramelessWindowHint );
progDlg.setModal( true );
progDlg.setWindowOpacity( 0.92 ); // make the dialog semi-transparent
palette = progDlg.palette();
palette.setColor( QPalette::WindowText, QColor( 64, 64, 64 ) );
progDlg.setPalette( palette );
progDlg.setFixedSize( 350, 110 );
QFont font = progDlg.font();
font.setBold( true );
progDlg.setFont( font ); // Set the splash font to bold
progDlg.setForegroundRole( QPalette::WindowText );

// Center the splash screen in the main application window
progDlg.move( m_pMainWin->pos().x() +
(m_pMainWin->frameGeometry().width() / 2) -
(progDlg.frameGeometry().width() / 2),
m_pMainWin->pos().y() +
(m_pMainWin->frameGeometry().height() / 2) -
(progDlg.frameGeometry().height() / 2) );

// Initialize the text stream
QTextStream logStream( &logFile );
QString sLine, sSrc, sType, sTime, sMsg;
Qt::ItemFlags flags;
int start, end, nRow = 0;
int nProgDlgValue = 0;

const int nTextAlignment = Qt::AlignCenter;
const int nMsgTextAlign = Qt::AlignLeft | Qt::AlignVCenter;
const Qt::ItemFlags itemFlags = Qt::ItemIsEditable | Qt::ItemIsSelectable;

// Skip the header
while ( (sLine = logStream.readLine())[0] == '*' )
nProgDlgValue += sLine.length();

// Load the text
while ( !logStream.atEnd() || !sLine.isEmpty() )
{
if ( sLine.isEmpty() ) // skip any empty lines
{
sLine = logStream.readLine();
continue;
}

sSrc.clear();
sType.clear();
sTime.clear();
sMsg.clear();
start = 0;

// Parse the Source
end = sLine.indexOf( ' ', start, Qt::CaseSensitive );
for ( int i = 0; i < 3; i++ )
sSrc[i] = sLine[start + i];

if ( sSrc == "LIB" )
sSrc = "Library";
else if ( sSrc == "APP" )
sSrc = "GCE";
else if ( sSrc == "DBG" )
sSrc = "Debug";
else if ( sSrc == "SYS" )
sSrc = "System";
else if ( sSrc == "SEC" )
sSrc = "Security";
else
sSrc = "Unknown";

start = end + 1;

// Parse the Type
end = sLine.indexOf( ' ', start, Qt::CaseSensitive );
for ( int i = 0; i < 3; i++ )
sType[i] = sLine[start + i];

if ( sType == "INF" )
sType = "Information";
else if ( sType == "ERR" )
sType = "Error";
else if ( sType == "WRN" )
sType = "Warning";
else
sType = "Unknown";

start = end + 1;

// Parse the Time
end = sLine.indexOf( CDL_COOKIE, start, Qt::CaseSensitive );
for ( int i = 0; i < 19; i++ )
sTime[i] = sLine[start + i];

sTime[sTime.indexOf( ' ', 0 )] = '/'; // for year/month
sTime[sTime.indexOf( ' ', 0 )] = '/'; // for month/day
start = end + 4;

// Get the message
sMsg = sLine.right( sLine.count() - start );

// Fill a table row
m_logTable->insertRow( nRow );

// Add message source
m_logTable->setItem( nRow, COL0_Src, new QTableWidgetItem( sSrc ) );
m_logTable->item( nRow, COL0_Src )->setTextAlignment( nTextAlignment );
// Set as read-only
flags = m_logTable->item( nRow, COL0_Src )->flags();
flags ^= itemFlags;
m_logTable->item( nRow, COL0_Src )->setFlags( flags );

// Add message type
m_logTable->setItem( nRow, COL1_Type, new QTableWidgetItem( sType ) );
m_logTable->item( nRow, COL1_Type )->setTextAlignment( nTextAlignment );
// Set as read-only
flags = m_logTable->item( nRow, COL1_Type )->flags();
flags ^= itemFlags;
m_logTable->item( nRow, COL1_Type )->setFlags( flags );

// Add message time
m_logTable->setItem( nRow, COL2_Time, new QTableWidgetItem( sTime ) );
m_logTable->item( nRow, COL2_Time )->setTextAlignment( nTextAlignment );
// Set as read-only
flags = m_logTable->item( nRow, COL2_Time )->flags();
flags ^= itemFlags;
m_logTable->item( nRow, COL2_Time )->setFlags( flags );

// Add message
m_logTable->setItem( nRow, COL3_Msg, new QTableWidgetItem( sMsg ) );
m_logTable->item( nRow, COL3_Msg )->setTextAlignment( nMsgTextAlign );
// Set as read-only
flags = m_logTable->item( nRow, COL3_Msg )->flags();
flags ^= itemFlags;
m_logTable->item( nRow, COL3_Msg )->setFlags( flags );

nRow++;
sLine = logStream.readLine();

// Increment progress bar
progDlg.setValue( nProgDlgValue += sLine.length() );
m_pMainWin->m_pApp->processEvents();

if ( progDlg.wasCanceled() )
break;
}

// Close the QFile
if ( logFile.isOpen() )
logFile.close();

// Enable sorting
m_logTable->setSortingEnabled( true );

}

wysota
16th November 2007, 23:51
I'd suggest doing it a bit another way. You need to implement your functionality in a slot that can be called multiple times - once for every iteration. Then create the progress dialog, make a connection between a single shot timer and your slot and call exec() on the dialog.

Set a 0 timeout for the timer and at the end of your slot start a 0-timeout single shot timer again. Something like:

QProgressDialog dlg(this);
iteration = 0;
startSlot();
dlg.exec();
//...
void X:startSlot(){
iteration++;
if(iteration<1000)
QTimer::singleShot(0, this, SLOT(startSlot()));
}

The main idea is to make sure you call exec on the progress dialog. See if your app crashes then.

mchara
19th November 2007, 06:49
Hi,
disabling main window may fail because you are not processing event loop while reading log and setEnabled is queued there so it's done after log reading.
Even if you want to use progress dialog, you shall call QCoreApplication::processEvents from time to time according to qt manual.

And one another approach - maybe read your log file in separate thread.