QtWidgets: restore Qt 5 compatibility for save/restore state
Several classes in QWidget use QDataStream internally in order to save and restore state. These QDataStream usages were not versioned, meaning that if Qt changes the serialization for some datatype, then the data saved between different Qt versions becomes incompatible. Note that the save/restore API in question just produce opaque blobs as QByteArrays -- the user has no control over the QDataStream objects and thus versions. Fix by version the usages. In QHeaderView this has caused a regression because QBitArray *did* change version between Qt 5 and 6. In general, using QDataStream without explicit versioning is a mistake, so deploy the same fix elsewhere as well. Fixes: QTBUG-99487 Pick-to: 5.15 6.2 6.3 Change-Id: I82bb5c266f4e5dedc0887cbef855dccab1015e29 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> Reviewed-by: <doctor.whom@gmail.com>
This commit is contained in:
parent
cf3843a268
commit
07d80deeab
@ -418,6 +418,7 @@ QByteArray QFileDialog::saveState() const
|
|||||||
int version = 4;
|
int version = 4;
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
|
|
||||||
stream << qint32(QFileDialogMagic);
|
stream << qint32(QFileDialogMagic);
|
||||||
stream << qint32(version);
|
stream << qint32(version);
|
||||||
@ -452,6 +453,7 @@ bool QFileDialog::restoreState(const QByteArray &state)
|
|||||||
Q_D(QFileDialog);
|
Q_D(QFileDialog);
|
||||||
QByteArray sd = state;
|
QByteArray sd = state;
|
||||||
QDataStream stream(&sd, QIODevice::ReadOnly);
|
QDataStream stream(&sd, QIODevice::ReadOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
if (stream.atEnd())
|
if (stream.atEnd())
|
||||||
return false;
|
return false;
|
||||||
QStringList history;
|
QStringList history;
|
||||||
|
@ -1742,6 +1742,7 @@ QByteArray QHeaderView::saveState() const
|
|||||||
Q_D(const QHeaderView);
|
Q_D(const QHeaderView);
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
stream << QHeaderViewPrivate::VersionMarker;
|
stream << QHeaderViewPrivate::VersionMarker;
|
||||||
stream << 0; // current version is 0
|
stream << 0; // current version is 0
|
||||||
d->write(stream);
|
d->write(stream);
|
||||||
@ -1763,6 +1764,7 @@ bool QHeaderView::restoreState(const QByteArray &state)
|
|||||||
return false;
|
return false;
|
||||||
QByteArray data = state;
|
QByteArray data = state;
|
||||||
QDataStream stream(&data, QIODevice::ReadOnly);
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
int marker;
|
int marker;
|
||||||
int ver;
|
int ver;
|
||||||
stream >> marker;
|
stream >> marker;
|
||||||
|
@ -1217,6 +1217,7 @@ QByteArray QMainWindow::saveState(int version) const
|
|||||||
{
|
{
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
stream << QMainWindowLayout::VersionMarker;
|
stream << QMainWindowLayout::VersionMarker;
|
||||||
stream << version;
|
stream << version;
|
||||||
d_func()->layout->saveState(stream);
|
d_func()->layout->saveState(stream);
|
||||||
@ -1245,6 +1246,7 @@ bool QMainWindow::restoreState(const QByteArray &state, int version)
|
|||||||
return false;
|
return false;
|
||||||
QByteArray sd = state;
|
QByteArray sd = state;
|
||||||
QDataStream stream(&sd, QIODevice::ReadOnly);
|
QDataStream stream(&sd, QIODevice::ReadOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
int marker, v;
|
int marker, v;
|
||||||
stream >> marker;
|
stream >> marker;
|
||||||
stream >> v;
|
stream >> v;
|
||||||
|
@ -1164,10 +1164,12 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDataStream ds(copy);
|
QDataStream ds(copy);
|
||||||
|
ds.setVersion(_stream.version());
|
||||||
if (!checkFormat(ds))
|
if (!checkFormat(ds))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QDataStream stream(copy);
|
QDataStream stream(copy);
|
||||||
|
stream.setVersion(_stream.version());
|
||||||
|
|
||||||
while (!stream.atEnd()) {
|
while (!stream.atEnd()) {
|
||||||
uchar marker;
|
uchar marker;
|
||||||
|
@ -1650,6 +1650,7 @@ QByteArray QSplitter::saveState() const
|
|||||||
int version = 1;
|
int version = 1;
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
|
|
||||||
stream << qint32(SplitterMagic);
|
stream << qint32(SplitterMagic);
|
||||||
stream << qint32(version);
|
stream << qint32(version);
|
||||||
@ -1691,6 +1692,7 @@ bool QSplitter::restoreState(const QByteArray &state)
|
|||||||
int version = 1;
|
int version = 1;
|
||||||
QByteArray sd = state;
|
QByteArray sd = state;
|
||||||
QDataStream stream(&sd, QIODevice::ReadOnly);
|
QDataStream stream(&sd, QIODevice::ReadOnly);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_0);
|
||||||
QList<int> list;
|
QList<int> list;
|
||||||
bool b;
|
bool b;
|
||||||
qint32 i;
|
qint32 i;
|
||||||
|
@ -146,6 +146,7 @@ private slots:
|
|||||||
void moveSectionAndReset();
|
void moveSectionAndReset();
|
||||||
void moveSectionAndRemove();
|
void moveSectionAndRemove();
|
||||||
void saveRestore();
|
void saveRestore();
|
||||||
|
void QTBUG99487_saveRestoreQt5Compat();
|
||||||
void restoreToMoreColumns();
|
void restoreToMoreColumns();
|
||||||
void restoreToMoreColumnsNoMovedColumns();
|
void restoreToMoreColumnsNoMovedColumns();
|
||||||
void restoreBeforeSetModel();
|
void restoreBeforeSetModel();
|
||||||
@ -1716,16 +1717,24 @@ static QByteArray savedState()
|
|||||||
return h1.saveState();
|
return h1.saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QHeaderView::saveRestore()
|
// As generated by savedState()
|
||||||
|
static const QByteArray qt5SavedSate = QByteArrayLiteral("\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\b\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00""d\x00\x00\x00\xD2\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x00\x00\x00\x00\x00\x00");
|
||||||
|
|
||||||
|
enum class SaveRestoreOption
|
||||||
|
{
|
||||||
|
CheckGeneratedState,
|
||||||
|
DoNotCheckGeneratedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void saveRestoreImpl(const QByteArray &state, SaveRestoreOption option)
|
||||||
{
|
{
|
||||||
QStandardItemModel m(4, 4);
|
QStandardItemModel m(4, 4);
|
||||||
const QByteArray s1 = savedState();
|
|
||||||
|
|
||||||
QHeaderView h2(Qt::Vertical);
|
QHeaderView h2(Qt::Vertical);
|
||||||
QSignalSpy spy(&h2, &QHeaderView::sortIndicatorChanged);
|
QSignalSpy spy(&h2, &QHeaderView::sortIndicatorChanged);
|
||||||
|
|
||||||
h2.setModel(&m);
|
h2.setModel(&m);
|
||||||
QVERIFY(h2.restoreState(s1));
|
QVERIFY(h2.restoreState(state));
|
||||||
|
|
||||||
QCOMPARE(spy.count(), 1);
|
QCOMPARE(spy.count(), 1);
|
||||||
QCOMPARE(spy.at(0).at(0).toInt(), 2);
|
QCOMPARE(spy.at(0).at(0).toInt(), 2);
|
||||||
@ -1740,12 +1749,30 @@ void tst_QHeaderView::saveRestore()
|
|||||||
QVERIFY(h2.isSectionHidden(3));
|
QVERIFY(h2.isSectionHidden(3));
|
||||||
QCOMPARE(h2.hiddenSectionCount(), 1);
|
QCOMPARE(h2.hiddenSectionCount(), 1);
|
||||||
|
|
||||||
QByteArray s2 = h2.saveState();
|
switch (option) {
|
||||||
QCOMPARE(s1, s2);
|
case SaveRestoreOption::CheckGeneratedState:
|
||||||
|
{
|
||||||
|
QByteArray s2 = h2.saveState();
|
||||||
|
QCOMPARE(state, s2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SaveRestoreOption::DoNotCheckGeneratedState:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
QVERIFY(!h2.restoreState(QByteArrayLiteral("Garbage")));
|
QVERIFY(!h2.restoreState(QByteArrayLiteral("Garbage")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QHeaderView::saveRestore()
|
||||||
|
{
|
||||||
|
saveRestoreImpl(savedState(), SaveRestoreOption::CheckGeneratedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QHeaderView::QTBUG99487_saveRestoreQt5Compat()
|
||||||
|
{
|
||||||
|
saveRestoreImpl(qt5SavedSate, SaveRestoreOption::DoNotCheckGeneratedState);
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QHeaderView::restoreToMoreColumns()
|
void tst_QHeaderView::restoreToMoreColumns()
|
||||||
{
|
{
|
||||||
// Restore state onto a model with more columns
|
// Restore state onto a model with more columns
|
||||||
|
Loading…
x
Reference in New Issue
Block a user