Complex Compound Files: Patron

English grammar defines a number of sentence structures. A simple sentence expresses one idea, such as "The rabbit sat in its form."4 Compound sentences express more than one independent idea, such as "The rabbit sat in its form, and the photographer set up her camera." These sentences have a vague notion of concurrency but no hard evidence of it. A complex sentence, however, defines such a relationship, as in "The rabbit sat in its form while the photographer set up her camera." A complex compound sentence is more on the order of "Although the rabbit felt trepidation about most humans, it calmly sat in its form, and the photographer continued to set up her camera."

The idea of a single flat traditional file is an analog of the simple sentence structure. A compound file, one with a single storage and a single stream, is much like a compound sentence: two elements that are related by being in the same place at the same time. When we add more streams in the root storage, we make things more complex—the meaning of the data in one stream might be partially or completely defined by the context of the data in another stream. As we use even more complex structures, we add substorages alongside these streams and new streams under those substorages. We can build any complexity we want, and thus we can call such structures complex compound files.

For this chapter's version of Patron (CHAP07\PATRON), we'll implement the storage structure shown in Figure 7-8, which is the same as that shown in Figure 7-6 on page 353, but without the page header streams or tenant storages because we haven't yet added the capability of creating tenants. Each Patron file is a root storage, underneath which live two streams. The first contains the printer configuration; the second contains the list of pages within the document, including the number of pages, the current page (the one to view when opening the file), and any number of page identifiers. Each page is assigned a unique DWORD according to the order of creation, and the page list stream contains the next ID to use. The ordering of pages in the document is determined by the sequence of IDs in the page list, not by the ID values themselves, because the fourth page created might have been inserted at the beginning of the document. Each page is given its own substorage under the root; the substorage name is Page <ID>, where <ID> is the ASCII representation of the identifier. For this chapter, these substorages will not contain anything else—they do, however, provide the structure in which we can create tenants later on.

Figure 7-8.

Exact layout of Patron's compound files as described in this chapter, prior to adding tenant support.

Adding file I/O capabilities using compound files requires considerable modifications and additions to Patron: filling out its CPatronDoc members Load and Save (DOCUMENT.CPP), adding structural support in the CPages class (PAGES.CPP), and adding functionality to the CPage class (PAGE.CPP). All the code is available on the companion CD and need not be shown here. For the most part, Patron follows the same sorts of call sequences for saving and loading files that were described for Cosmo earlier in this chapter, except that not everything happens at the same time and in the same place. Patron's file handling is summarized as follows:

I encourage you to follow through the code and see where each piece of Patron's architecture (I don't call it ideal by any means) works with its piece of the storage hierarchy. The CPatronDoc and CPages classes both work with the root storage, whereas instances of CPage work with their own substorage. Let's look at two of the more interesting parts of this implementation.

4 A form is a rabbit's resting place.

5 Patron does not gracefully handle the case in which a document created for one printer, with a specific DEVMODE, is taken to another machine without that printer installed. What will happen is that Patron will call CreateIC with that information and will fail to find the driver specified in DEVMODE. As a result, the user gets blasted with one of those ugly "Cannot find MSHPPCL5.DRV" messages. If you encounter this, remember it's just a sample.