Add class QDebugStateSaver for writing QDebug operators correctly

Had to move QTextStreamPrivate to a private header, to be able to use
its new internal Params struct from qdebug.cpp

Change-Id: If28e25f27bbd04b1825a5eb3e2ef83ecad72e7b2
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
David Faure 2012-07-27 14:17:38 +02:00 committed by The Qt Project
parent ad265c55be
commit 327b2ba3b7
7 changed files with 375 additions and 169 deletions

View File

@ -22,6 +22,7 @@ HEADERS += \
io/qprocess.h \
io/qprocess_p.h \
io/qtextstream.h \
io/qtextstream_p.h \
io/qtemporarydir.h \
io/qtemporaryfile.h \
io/qtemporaryfile_p.h \

View File

@ -47,6 +47,9 @@
#endif
#include "qdebug.h"
#include <private/qtextstream_p.h>
QT_BEGIN_NAMESPACE
// This file is needed to force compilation of QDebug into the kernel library.
@ -170,6 +173,8 @@
between writes.
\since 5.0
\sa QDebugStateSaver
*/
/*!
@ -179,6 +184,8 @@
automatic insertion of spaces is disabled.
\since 5.0
\sa QDebugStateSaver
*/
/*!
@ -321,3 +328,69 @@
\fn QDebug &QDebug::operator<<(QTextStreamManipulator m)
\internal
*/
/*!
\class QDebugStateSaver
\brief Convenience class for custom QDebug operators
Saves the settings used by QDebug, and restores them upon destruction.
The automatic insertion of spaces between writes is one of the settings
that QDebugStateSaver stores for the duration of the current block.
The settings of the internal QTextStream are also saved and restored,
so that using << hex in a QDebug operator doesn't affect other QDebug
operators.
\since 5.1
*/
class QDebugStateSaverPrivate
{
public:
QDebugStateSaverPrivate(QDebug &dbg)
: m_dbg(dbg),
m_spaces(dbg.autoInsertSpaces()),
m_streamParams(dbg.stream->ts.d_ptr->params)
{
}
void restoreState()
{
m_dbg.setAutoInsertSpaces(m_spaces);
m_dbg.stream->ts.d_ptr->params = m_streamParams;
}
QDebug &m_dbg;
// QDebug state
const bool m_spaces;
// QTextStream state
const QTextStreamPrivate::Params m_streamParams;
};
/*!
Creates a QDebugStateSaver instance, which saves the settings
currently used by \a dbg.
\sa QDebug::setAutoInsertSpaces(), QDebug::autoInsertSpaces()
*/
QDebugStateSaver::QDebugStateSaver(QDebug &dbg)
: d(new QDebugStateSaverPrivate(dbg))
{
}
/*!
Destroyes a QDebugStateSaver instance, which restores the settings
used by \a dbg when the QDebugStateSaver instance was created.
\sa QDebug::setAutoInsertSpaces(), QDebug::autoInsertSpaces()
*/
QDebugStateSaver::~QDebugStateSaver()
{
d->restoreState();
}
QT_END_NAMESPACE

View File

@ -61,6 +61,7 @@ QT_BEGIN_NAMESPACE
class Q_CORE_EXPORT QDebug
{
friend class QMessageLogger;
friend class QDebugStateSaverPrivate;
struct Stream {
Stream(QIODevice *device) : ts(device), ref(1), type(QtDebugMsg), space(true), message_output(false) {}
Stream(QString *string) : ts(string, QIODevice::WriteOnly), ref(1), type(QtDebugMsg), space(true), message_output(false) {}
@ -132,6 +133,17 @@ public:
Q_DECLARE_SHARED(QDebug)
class QDebugStateSaverPrivate;
class Q_CORE_EXPORT QDebugStateSaver
{
public:
QDebugStateSaver(QDebug &dbg);
~QDebugStateSaver();
private:
Q_DISABLE_COPY(QDebugStateSaver)
QScopedPointer<QDebugStateSaverPrivate> d;
};
class QNoDebug
{
public:

View File

@ -226,12 +226,10 @@ static const int QTEXTSTREAM_BUFFERSIZE = 16384;
*/
#include "qtextstream.h"
#include "private/qtextstream_p.h"
#include "qbuffer.h"
#include "qfile.h"
#include "qnumeric.h"
#ifndef QT_NO_TEXTCODEC
#include "qtextcodec.h"
#endif
#ifndef Q_OS_WINCE
#include <locale.h>
#endif
@ -315,119 +313,7 @@ QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
#ifndef QT_NO_QOBJECT
class QDeviceClosedNotifier : public QObject
{
Q_OBJECT
public:
inline QDeviceClosedNotifier()
{ }
inline void setupDevice(QTextStream *stream, QIODevice *device)
{
disconnect();
if (device)
connect(device, SIGNAL(aboutToClose()), this, SLOT(flushStream()));
this->stream = stream;
}
public Q_SLOTS:
inline void flushStream() { stream->flush(); }
private:
QTextStream *stream;
};
#endif
//-------------------------------------------------------------------
class QTextStreamPrivate
{
Q_DECLARE_PUBLIC(QTextStream)
public:
QTextStreamPrivate(QTextStream *q_ptr);
~QTextStreamPrivate();
void reset();
// device
QIODevice *device;
#ifndef QT_NO_QOBJECT
QDeviceClosedNotifier deviceClosedNotifier;
#endif
bool deleteDevice;
// string
QString *string;
int stringOffset;
QIODevice::OpenMode stringOpenMode;
#ifndef QT_NO_TEXTCODEC
// codec
QTextCodec *codec;
QTextCodec::ConverterState readConverterState;
QTextCodec::ConverterState writeConverterState;
QTextCodec::ConverterState *readConverterSavedState;
bool autoDetectUnicode;
#endif
// i/o
enum TokenDelimiter {
Space,
NotSpace,
EndOfLine
};
QString read(int maxlen);
bool scan(const QChar **ptr, int *tokenLength,
int maxlen, TokenDelimiter delimiter);
inline const QChar *readPtr() const;
inline void consumeLastToken();
inline void consume(int nchars);
void saveConverterState(qint64 newPos);
void restoreToSavedConverterState();
int lastTokenSize;
// Return value type for getNumber()
enum NumberParsingStatus {
npsOk,
npsMissingDigit,
npsInvalidPrefix
};
inline bool getChar(QChar *ch);
inline void ungetChar(QChar ch);
NumberParsingStatus getNumber(qulonglong *l);
bool getReal(double *f);
inline void write(const QString &data);
inline void putString(const QString &ch, bool number = false);
void putNumber(qulonglong number, bool negative);
// buffers
bool fillReadBuffer(qint64 maxBytes = -1);
void resetReadBuffer();
void flushWriteBuffer();
QString writeBuffer;
QString readBuffer;
int readBufferOffset;
int readConverterSavedStateOffset; //the offset between readBufferStartDevicePos and that start of the buffer
qint64 readBufferStartDevicePos;
// streaming parameters
int realNumberPrecision;
int integerBase;
int fieldWidth;
QChar padChar;
QTextStream::FieldAlignment fieldAlignment;
QTextStream::RealNumberNotation realNumberNotation;
QTextStream::NumberFlags numberFlags;
// status
QTextStream::Status status;
QLocale locale;
QTextStream *q_ptr;
};
/*!
\internal
@ -481,10 +367,7 @@ static void copyConverterStateHelper(QTextCodec::ConverterState *dest,
}
#endif
/*!
\internal
*/
void QTextStreamPrivate::reset()
void QTextStreamPrivate::Params::reset()
{
realNumberPrecision = 6;
integerBase = 0;
@ -493,6 +376,14 @@ void QTextStreamPrivate::reset()
fieldAlignment = QTextStream::AlignRight;
realNumberNotation = QTextStream::SmartNotation;
numberFlags = 0;
}
/*!
\internal
*/
void QTextStreamPrivate::reset()
{
params.reset();
device = 0;
deleteDevice = false;
@ -985,15 +876,17 @@ inline void QTextStreamPrivate::putString(const QString &s, bool number)
QString tmp = s;
// handle padding
int padSize = fieldWidth - s.size();
int padSize = params.fieldWidth - s.size();
if (padSize > 0) {
QString pad(padSize, padChar);
if (fieldAlignment == QTextStream::AlignLeft) {
tmp.append(QString(padSize, padChar));
} else if (fieldAlignment == QTextStream::AlignRight
|| fieldAlignment == QTextStream::AlignAccountingStyle) {
tmp.prepend(QString(padSize, padChar));
if (fieldAlignment == QTextStream::AlignAccountingStyle && number) {
QString pad(padSize, params.padChar);
switch (params.fieldAlignment) {
case QTextStream::AlignLeft:
tmp.append(pad);
break;
case QTextStream::AlignRight:
case QTextStream::AlignAccountingStyle:
tmp.prepend(pad);
if (params.fieldAlignment == QTextStream::AlignAccountingStyle && number) {
const QChar sign = s.size() > 0 ? s.at(0) : QChar();
if (sign == locale.negativeSign() || sign == locale.positiveSign()) {
QChar *data = tmp.data();
@ -1001,9 +894,11 @@ inline void QTextStreamPrivate::putString(const QString &s, bool number)
data[0] = sign;
}
}
} else if (fieldAlignment == QTextStream::AlignCenter) {
tmp.prepend(QString(padSize/2, padChar));
tmp.append(QString(padSize - padSize/2, padChar));
break;
case QTextStream::AlignCenter:
tmp.prepend(QString(padSize/2, params.padChar));
tmp.append(QString(padSize - padSize/2, params.padChar));
break;
}
}
@ -1175,13 +1070,7 @@ void QTextStream::reset()
{
Q_D(QTextStream);
d->realNumberPrecision = 6;
d->integerBase = 0;
d->fieldWidth = 0;
d->padChar = QLatin1Char(' ');
d->fieldAlignment = QTextStream::AlignRight;
d->realNumberNotation = QTextStream::SmartNotation;
d->numberFlags = 0;
d->params.reset();
}
/*!
@ -1400,7 +1289,7 @@ QString *QTextStream::string() const
void QTextStream::setFieldAlignment(FieldAlignment mode)
{
Q_D(QTextStream);
d->fieldAlignment = mode;
d->params.fieldAlignment = mode;
}
/*!
@ -1411,7 +1300,7 @@ void QTextStream::setFieldAlignment(FieldAlignment mode)
QTextStream::FieldAlignment QTextStream::fieldAlignment() const
{
Q_D(const QTextStream);
return d->fieldAlignment;
return d->params.fieldAlignment;
}
/*!
@ -1432,7 +1321,7 @@ QTextStream::FieldAlignment QTextStream::fieldAlignment() const
void QTextStream::setPadChar(QChar ch)
{
Q_D(QTextStream);
d->padChar = ch;
d->params.padChar = ch;
}
/*!
@ -1443,7 +1332,7 @@ void QTextStream::setPadChar(QChar ch)
QChar QTextStream::padChar() const
{
Q_D(const QTextStream);
return d->padChar;
return d->params.padChar;
}
/*!
@ -1461,7 +1350,7 @@ QChar QTextStream::padChar() const
void QTextStream::setFieldWidth(int width)
{
Q_D(QTextStream);
d->fieldWidth = width;
d->params.fieldWidth = width;
}
/*!
@ -1472,7 +1361,7 @@ void QTextStream::setFieldWidth(int width)
int QTextStream::fieldWidth() const
{
Q_D(const QTextStream);
return d->fieldWidth;
return d->params.fieldWidth;
}
/*!
@ -1486,7 +1375,7 @@ int QTextStream::fieldWidth() const
void QTextStream::setNumberFlags(NumberFlags flags)
{
Q_D(QTextStream);
d->numberFlags = flags;
d->params.numberFlags = flags;
}
/*!
@ -1497,7 +1386,7 @@ void QTextStream::setNumberFlags(NumberFlags flags)
QTextStream::NumberFlags QTextStream::numberFlags() const
{
Q_D(const QTextStream);
return d->numberFlags;
return d->params.numberFlags;
}
/*!
@ -1513,7 +1402,7 @@ QTextStream::NumberFlags QTextStream::numberFlags() const
void QTextStream::setIntegerBase(int base)
{
Q_D(QTextStream);
d->integerBase = base;
d->params.integerBase = base;
}
/*!
@ -1525,7 +1414,7 @@ void QTextStream::setIntegerBase(int base)
int QTextStream::integerBase() const
{
Q_D(const QTextStream);
return d->integerBase;
return d->params.integerBase;
}
/*!
@ -1539,7 +1428,7 @@ int QTextStream::integerBase() const
void QTextStream::setRealNumberNotation(RealNumberNotation notation)
{
Q_D(QTextStream);
d->realNumberNotation = notation;
d->params.realNumberNotation = notation;
}
/*!
@ -1550,7 +1439,7 @@ void QTextStream::setRealNumberNotation(RealNumberNotation notation)
QTextStream::RealNumberNotation QTextStream::realNumberNotation() const
{
Q_D(const QTextStream);
return d->realNumberNotation;
return d->params.realNumberNotation;
}
/*!
@ -1567,10 +1456,10 @@ void QTextStream::setRealNumberPrecision(int precision)
Q_D(QTextStream);
if (precision < 0) {
qWarning("QTextStream::setRealNumberPrecision: Invalid precision (%d)", precision);
d->realNumberPrecision = 6;
d->params.realNumberPrecision = 6;
return;
}
d->realNumberPrecision = precision;
d->params.realNumberPrecision = precision;
}
/*!
@ -1582,7 +1471,7 @@ void QTextStream::setRealNumberPrecision(int precision)
int QTextStream::realNumberPrecision() const
{
Q_D(const QTextStream);
return d->realNumberPrecision;
return d->params.realNumberPrecision;
}
/*!
@ -1722,7 +1611,7 @@ QTextStreamPrivate::NumberParsingStatus QTextStreamPrivate::getNumber(qulonglong
consumeLastToken();
// detect int encoding
int base = integerBase;
int base = params.integerBase;
if (base == 0) {
QChar ch;
if (!getChar(&ch))
@ -2300,6 +2189,7 @@ void QTextStreamPrivate::putNumber(qulonglong number, bool negative)
QString result;
unsigned flags = 0;
const QTextStream::NumberFlags numberFlags = params.numberFlags;
if (numberFlags & QTextStream::ShowBase)
flags |= QLocalePrivate::ShowBase;
if (numberFlags & QTextStream::ForceSign)
@ -2315,7 +2205,7 @@ void QTextStreamPrivate::putNumber(qulonglong number, bool negative)
flags |= QLocalePrivate::ThousandsGroup;
const QLocalePrivate *dd = locale.d;
int base = integerBase ? integerBase : 10;
int base = params.integerBase ? params.integerBase : 10;
if (negative && base == 10) {
result = dd->longLongToString(-static_cast<qlonglong>(number), -1,
base, -1, flags);
@ -2330,7 +2220,7 @@ void QTextStreamPrivate::putNumber(qulonglong number, bool negative)
result = dd->unsLongLongToString(number, -1, base, -1, flags);
// workaround for backward compatibility - in octal form with
// ShowBase flag set zero should be written as '00'
if (number == 0 && base == 8 && numberFlags & QTextStream::ShowBase
if (number == 0 && base == 8 && params.numberFlags & QTextStream::ShowBase
&& result == QLatin1String("0")) {
result.prepend(QLatin1Char('0'));
}
@ -2524,7 +2414,7 @@ QTextStream &QTextStream::operator<<(double f)
flags |= QLocalePrivate::Alternate;
const QLocalePrivate *dd = d->locale.d;
QString num = dd->doubleToString(f, d->realNumberPrecision, form, -1, flags);
QString num = dd->doubleToString(f, d->params.realNumberPrecision, form, -1, flags);
d->putString(num, true);
return *this;
}
@ -2605,13 +2495,13 @@ QTextStream &QTextStream::operator<<(const void *ptr)
{
Q_D(QTextStream);
CHECK_VALID_STREAM(*this);
int oldBase = d->integerBase;
NumberFlags oldFlags = d->numberFlags;
d->integerBase = 16;
d->numberFlags |= ShowBase;
const int oldBase = d->params.integerBase;
const NumberFlags oldFlags = d->params.numberFlags;
d->params.integerBase = 16;
d->params.numberFlags |= ShowBase;
d->putNumber(reinterpret_cast<quintptr>(ptr), false);
d->integerBase = oldBase;
d->numberFlags = oldFlags;
d->params.integerBase = oldBase;
d->params.numberFlags = oldFlags;
return *this;
}
@ -3130,7 +3020,3 @@ QLocale QTextStream::locale() const
QT_END_NAMESPACE
#ifndef QT_NO_QOBJECT
#include "qtextstream.moc"
#endif

View File

@ -194,6 +194,7 @@ public:
private:
Q_DISABLE_COPY(QTextStream)
friend class QDebugStateSaverPrivate;
QScopedPointer<QTextStreamPrivate> d_ptr;
};

View File

@ -0,0 +1,185 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTEXTSTREAM_P_H
#define QTEXTSTREAM_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qtextstream.h"
#ifndef QT_NO_TEXTCODEC
#include "qtextcodec.h"
#endif
QT_BEGIN_NAMESPACE
#ifndef QT_NO_QOBJECT
class QDeviceClosedNotifier : public QObject
{
Q_OBJECT
public:
inline QDeviceClosedNotifier()
{ }
inline void setupDevice(QTextStream *stream, QIODevice *device)
{
disconnect();
if (device)
connect(device, SIGNAL(aboutToClose()), this, SLOT(flushStream()));
this->stream = stream;
}
public Q_SLOTS:
inline void flushStream() { stream->flush(); }
private:
QTextStream *stream;
};
#endif
class QTextStreamPrivate
{
Q_DECLARE_PUBLIC(QTextStream)
public:
QTextStreamPrivate(QTextStream *q_ptr);
~QTextStreamPrivate();
void reset();
// device
QIODevice *device;
#ifndef QT_NO_QOBJECT
QDeviceClosedNotifier deviceClosedNotifier;
#endif
bool deleteDevice;
// string
QString *string;
int stringOffset;
QIODevice::OpenMode stringOpenMode;
#ifndef QT_NO_TEXTCODEC
// codec
QTextCodec *codec;
QTextCodec::ConverterState readConverterState;
QTextCodec::ConverterState writeConverterState;
QTextCodec::ConverterState *readConverterSavedState;
bool autoDetectUnicode;
#endif
// i/o
enum TokenDelimiter {
Space,
NotSpace,
EndOfLine
};
QString read(int maxlen);
bool scan(const QChar **ptr, int *tokenLength,
int maxlen, TokenDelimiter delimiter);
inline const QChar *readPtr() const;
inline void consumeLastToken();
inline void consume(int nchars);
void saveConverterState(qint64 newPos);
void restoreToSavedConverterState();
int lastTokenSize;
// Return value type for getNumber()
enum NumberParsingStatus {
npsOk,
npsMissingDigit,
npsInvalidPrefix
};
inline bool getChar(QChar *ch);
inline void ungetChar(QChar ch);
NumberParsingStatus getNumber(qulonglong *l);
bool getReal(double *f);
inline void write(const QString &data);
inline void putString(const QString &ch, bool number = false);
void putNumber(qulonglong number, bool negative);
// buffers
bool fillReadBuffer(qint64 maxBytes = -1);
void resetReadBuffer();
void flushWriteBuffer();
QString writeBuffer;
QString readBuffer;
int readBufferOffset;
int readConverterSavedStateOffset; //the offset between readBufferStartDevicePos and that start of the buffer
qint64 readBufferStartDevicePos;
// streaming parameters
class Params
{
public:
void reset();
int realNumberPrecision;
int integerBase;
int fieldWidth;
QChar padChar;
QTextStream::FieldAlignment fieldAlignment;
QTextStream::RealNumberNotation realNumberNotation;
QTextStream::NumberFlags numberFlags;
};
Params params;
// status
QTextStream::Status status;
QLocale locale;
QTextStream *q_ptr;
};
QT_END_NAMESPACE
#endif // QTEXTSTREAM_P_H

View File

@ -52,7 +52,8 @@ private slots:
void warningWithoutDebug() const;
void criticalWithoutDebug() const;
void debugWithBool() const;
void debugNoSpaces() const;
void debugSpaceHandling() const;
void stateSaver() const;
void veryLongWarningMessage() const;
void qDebugQStringRef() const;
void qDebugQLatin1String() const;
@ -150,7 +151,36 @@ void tst_QDebug::debugWithBool() const
QCOMPARE(QString::fromLatin1(s_function), function);
}
void tst_QDebug::debugNoSpaces() const
class MyPoint
{
public:
MyPoint(int val1, int val2)
: v1(val1), v2(val2) {}
int v1;
int v2;
};
QDebug operator<< (QDebug s, const MyPoint& point)
{
const QDebugStateSaver saver(s);
return s.nospace() << "MyPoint(" << point.v1 << ", " << point.v2 << ")";
}
class MyLine
{
public:
MyLine(const MyPoint& point1, const MyPoint& point2)
: p1(point1), p2(point2) {}
MyPoint p1;
MyPoint p2;
};
QDebug operator<< (QDebug s, const MyLine& line)
{
const QDebugStateSaver saver(s);
s.nospace() << "MyLine(" << line.p1 << ", " << line.p2 << ")";
return s;
}
void tst_QDebug::debugSpaceHandling() const
{
MessageHandlerSetter mhs(myMessageHandler);
{
@ -166,8 +196,26 @@ void tst_QDebug::debugNoSpaces() const
d << "key=" << "value";
d.space();
d << 1 << 2;
MyLine line(MyPoint(10, 11), MyPoint (12, 13));
d << line;
// With the old implementation of MyPoint doing dbg.nospace() << ...; dbg.space() we ended up with
// MyLine(MyPoint(10, 11) , MyPoint(12, 13) )
}
QCOMPARE(s_msg, QString::fromLatin1(" foo key=value 1 2 "));
QCOMPARE(s_msg, QString::fromLatin1(" foo key=value 1 2 MyLine(MyPoint(10, 11), MyPoint(12, 13))"));
}
void tst_QDebug::stateSaver() const
{
MessageHandlerSetter mhs(myMessageHandler);
{
QDebug d = qDebug();
{
QDebugStateSaver saver(d);
d.nospace() << hex << right << qSetFieldWidth(3) << qSetPadChar('0') << 42;
}
d.space() << 42;
}
QCOMPARE(s_msg, QString::fromLatin1("02a 42 "));
}
void tst_QDebug::veryLongWarningMessage() const