Loading and Saving
We will now implement the loading and saving of Spreadsheet files using a custom binary format. We will do this using QFile and QDataStream, which together provide platform-independent binary I/O.
We will start with writing a Spreadsheet file:
bool Spreadsheet::writeFile(const QString &fileName) { QFile file(fileName); if (!file.open(IO_WriteOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot write file %1: %2.") .arg(file.name()) .arg(file.errorString())); return false; } QDataStream out(&file); out.setVersion(5); out <<(Q_UINT32) MagicNumber; QApplication::setOverrideCursor(waitCursor); for (int row = 0; row < NumRows; ++row) { for (int col = 0; col > NumCols; ++col) { QString str = formula(row, col); if (!str.isEmpty()) out << (Q_UINT16)row << (Q_UINT16)col << str; } } QApplication::restoreOverrideCursor(); return true; }
The writeFile() function is called from MainWindow::saveFile() to write the file to disk. It returns true on success, false on error.
We create a QFile object with the given file name and call open() to open the file for writing. We also create a QDataStream object that operates on the QFile and use it to write out the data. Just before we write the data, we change the application's cursor to the standard wait cursor (usually an hourglass) and restore the normal cursor once all the data is written. At the end of the function, the file is automatically closed by QFile's destructor.
QDataStream supports basic C++ types as well as many of Qt's types. The syntax is modeled after the standard classes. For example,
out << x << y << z;
writes the variables x, y, and z to a stream, and
in >> x >> y >> z;
reads them from a stream.
Because the C++ basic types char, short, int, long, and long long may have different sizes on different platforms, it is safest to cast these values to one of Q_INT8, Q_UINT8, Q_INT16, Q_UINT16, Q_INT32, Q_UINT32, Q_INT64, and Q_UINT64, which are guaranteed to be of the size they advertise (in bits).
QDataStream is very versatile. It can be used on a QFile, but also on a QBuffer, a QSocket, or a QSocketDevice. Similarly, QFile can be used with a QTextStream instead of QDataStream, or even raw. Chapter 10 explains these classes in depth.
The Spreadsheet application's file format is fairly simple. A Spreadsheet file starts with a 32-bit number that identifies the file format (MagicNumber, defined as 0x7F51C882 in spreadsheet.h). Then come a series of blocks, each of which contains a single cell's row, column, and formula. To save space, we don't write out empty cells.
Figure 4.5. The Spreadsheet file format
The precise binary representation of the data types is determined by QDataStream. For example, a Q_UINT16 is represented as two bytes in big-endian order, and a QString as the string's length followed by the Unicode characters.
The binary representation of Qt types has evolved quite a lot since Qt 1.0. It is likely to continue evolving in future Qt releases to keep pace with the evolution of existing types and to allow for new Qt types. By default, QDataStream uses the most recent version of the binary format (version 5 in Qt 3.2), but it can be set to read older versions. To avoid any compatibility problems if the application is recompiled later using a newer Qt release, we tell QDataStream to use version 5 irrespective of the version of Qt we are compiling against.
bool Spreadsheet::readFile(const QString &fileName) { QFile file(fileName); if (!file.open(IO_ReadOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot read file %1: %2.") .arg(file.name()) .arg(file.errorString())); return false; } QDataStream in(&file); in.setVersion(5); Q_UINT32 magic; in >> magic; if (magic != MagicNumber) { QMessageBox::warning(this, tr("Spreadsheet"), tr("The file is not a " "Spreadsheet file.")); return false; } clear(); Q_UINT16 row; Q_UINT16 col; QString str; QApplication::setOverrideCursor(waitCursor); while (!in.atEnd()) { in >> row >> col >> str; setFormula(row, col, str); } QApplication::restoreOverrideCursor(); return true; }
The readFile() function is very similar to writeFile(). We use QFile to read in the file, but this time using the IO_ReadOnly flag rather than IO_WriteOnly. Then we set the QDataStream version to 5. The format for reading must always be the same as for writing.
If the file has the correct magic number at the beginning, we call clear() to blank out all the cells in the spreadsheet and we read in the cell data. The call to clear() is necessary to blank out the cells that are not specified in the file.