Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Working With OpenDoc Part Kinds

From Higher Intellect Vintage Wiki

by Tantek Çelik and Dave Curbow

If you're ready to create your first full-featured OpenDoc part editor but have some questions about part kinds and how to work with them, you'll find the answers here. We explain how your choice of part kinds will affect whether users will be able to read your content with different part editors and even across different platforms. We also discuss some human interface principles and describe how to handle the most common user actions having to do with part kinds.

We imagine that every computer user on earth has had the experience of trying to open a document created by someone else but not being able to because the application it was created with is missing. In the context of OpenDoc, users can run into this when the part editor that created a part is missing. OpenDoc provides several ways to mitigate this "missing editor" problem. One way is for developers to create and freely distribute part viewers for all the kinds of parts that they support; a part viewer is a subset of its corresponding editor's code that displays and prints a part's contents but can't be used to create or edit a part.

But suppose a user doesn't have either an editor or a viewer for a particular part. That's where part kinds come in. A part kind is a data format in which a part's intrinsic content is stored, analogous to a file type in a traditional application. OpenDoc allows a part editor to support multiple part kinds -- that is, to store the same content in multiple data formats -- to increase the probability that a user will be able to see and copy the contents of a part. A user who doesn't have the same part editor that created a part may have a different part editor that can read at least one of the data formats in which that part is stored. Alternatively, one or more of the data formats can perhaps be translated into a part kind for which the user has an editor or viewer.

What this means to you is that your choice of part kinds to support is a crucial step in developing a part editor. This article discusses how to choose which part kinds to support -- standard (to Macintosh or across platforms) or proprietary -- and whether to support one or multiple part kinds. We also discuss how to decide which category your part kinds fit into, some human interface principles having to do with part kinds, and what to do in a few key situations in which user actions cause your editor to have to deal with part kinds.

If you're not already familiar with the OpenDoc human interface, you should first read "The OpenDoc User Experience" in develop Issue 22 to get up to speed. This article also requires you to know something about OpenDoc storage and how to use the ODStorageUnit class. "Getting Started With OpenDoc Storage" in develop Issue 24 is a good introduction; further details can be found in the OpenDoc Programmer's Guide for the Mac OS and its accompanying OpenDoc Class Reference CD.

CHOOSING YOUR PART KINDS AND CATEGORY

In developing your part editor, you first need to decide which part kind or kinds to support. This choice is worthy of careful consideration. The decision you make about whether to support standard vs. proprietary part kinds and how many part kinds to support will affect the number of users able to read your content across documents and platforms. We'll look at the tradeoffs here. We'll also give you the information you need in order to decide which category or categories your part kinds fit into.

STANDARD VS. PROPRIETARY PART KINDS

First, you need to decide whether to support standard or proprietary part kinds, or some combination of each. Standard part kinds are those data formats that, either through an official decree or by some de facto means, have become widely used and accepted. There are industry-standard part kinds, which are

standard across more than one platform, and standard Macintosh part kinds.

Because new data formats are being created all the time, we can't give you a complete list, but here's a sample:

  • industry standards -- ASCII, TIFF, GIF, JPEG, MPEG

  • Macintosh standards -- TEXT, PICT, stxt, MOOV, 3DMF

Part kinds are

usually specified as ISO strings (null-terminated ASCII strings using 7-bit characters) for manipulation by OpenDoc. As you can see from our list, standard Macintosh part kinds are actually today's standard Macintosh file types, except that instead of being file-type signatures they're ISO strings, which can be derived by using methods of the class ODTranslation. (See the Data Interchange recipes on the OpenDoc Class Reference CD for more details on how to properly support a standard Macintosh part kind based on a standard Macintosh file type.) Your part editor needs to provide user-readable names for part kinds in a name-mapping resource; more on this later.

    The ASCII standard is actually pretty loosely defined. It doesn't specify whether you should use 7- or 8-bit encoding, nor does it say whether you should use LF, CR, or CRLF for line separators. In the near future, Unicode, which OpenDoc uses internally, is likely to become the standard. In the meantime, your part may need to be prepared to handle several variants on the ASCII standard without failure.

If the part kind you choose to support is an industry standard, users will benefit because they'll be more likely to avoid the missing editor problem mentioned earlier. Furthermore, supporting standard part kinds enables your part editor to support more of the content that's already out there. Let's face it -- data formats don't live forever, but the standard ones have a much better

chance of being long-lived than any proprietary kinds you create.

On the other hand, if there's no standard for the content your part editor creates, or if the standard won't suffice to capture the functionality your part editor offers, you'll need to create a proprietary part kind. You must weigh the advantages of using a proprietary part kind against the disadvantage of users possibly not being able to read your part's content.

In any case, don't redefine an existing standard. For example, the TEXT part kind should be used only for plain text, not for some data format that uses text as part of its definition, such as PostScript, HTML, or BinHex. These data formats should be part kinds in their own right. Otherwise, there will be confusion when OpenDoc needs to find a substitute part editor for a part that claims to be TEXT but is in fact another kind such as HTML. The user won't be happy with the result.

If you decide to use an industry-standard part kind, the Bento container suite (part of the storage system in OpenDoc 1.0) can help you solve internal byte-ordering problems and ensure that a document written on any OpenDoc platform can be read and written on any other OpenDoc platform. However, your part editor is responsible for proper byte ordering of the values in the content property of your storage unit. (Data formats typically specify byte ordering, so OpenDoc stays out of your way here.) The Standard Type I/O utilities (see the file StdTypIO.h and the functions declared there) solve the byte-ordering problem for a variety of simple data formats. These utilities can be used in combination to build up more complex data formats.

SUPPORTING MULTIPLE PART KINDS

As we've said, your editor can support one or more part kinds. If it supports more than one part kind, one of these will be the preferred kind. Users implicitly indicate the preferred kind when they choose a stationery pad or cut and paste content. They can also change the preferred kind in the Part Info

dialog if they desire; more on this later.

Supporting multiple part kinds increases the probability that other users can see the contents of a part created with your editor, even if they don't have your part editor (see "Editor Substitution Explained" for why this is so). Your choice of part kinds to support comes into play both when the user saves a document with parts created by your editor and when the user transfers data with a paste or drop operation.


    EDITOR SUBSTITUTION EXPLAINED

    When a user tries to open a document or edit a part and the editor that created it is missing, OpenDoc searches for a substitute. This occurs as part of OpenDoc's binding process -- the process of assigning the correct part editor to a given part. When a document is opened, the OpenDoc binding subsystem binds editors to all parts that need to be displayed. During execution, OpenDoc binds editors to part data when a part is read in or when its editor is explicitly

    changed.

    Let's look at a simplified example of editor substitution. Suppose we've created a text editor named SurfWriter that stores its content in three formats: a proprietary part kind (SurfWriter Text) and two standard part kinds (RTF and TEXT). And suppose that SurfWriter Text is the preferred kind. When OpenDoc tries to display the part, its binding subsystem looks first for SurfWriter -- the last editor that was used. If that isn't found, the binding subsystem looks for an editor that can read SurfWriter Text -- the preferred kind. If that can't be found, it looks for one that can read RTF or TEXT. Thus, storing multiple part kinds increases the probability that users will be able to read your content with different part editors and across different platforms.

    Now let's look at editor substitution in a little more detail. When attempting to find an editor to bind to a part, OpenDoc looks first for the editor that last edited the part, specified in the kODPropPreferredEditor property in the part's storage unit. If this editor isn't present on the user's system, the binding subsystem examines each of the part kinds in the stored part and the list of kinds supported by the editor or editors installed on the user's system, looking for a match. For each supported kind, there's a default editor. The user can inspect and modify the list of default editors in the Editor Setup control panel (Figure 1).

    (image missing)

    Figure 1. The Editor Setup control panel

    During the matching process, the binding subsystem looks first for the default editor for the preferred kind. If this editor isn't present, it looks for the default editor for the preferred kind's category, and finally for any editor that can read the preferred kind. If such an editor can't be found, the binding subsystem repeats the whole process for each of the remaining part kinds in the part, from highest fidelity to lowest.



If no editor for any of the part kinds is installed on the user's machine, the part remains unviewable and uneditable. But OpenDoc still binds an editor to the part -- the "editor of last resort." This editor is always available and represents the part as an icon within the document, so that there's never a blank spot in the document where a part can't be displayed. The user can examine the part's kind in the Part Info dialog, which gives a clue as to which editor or viewer should be installed, although if there's no editor for the part, there's probably no user string for the preferred kind. The user can also

decide to translate the part to another part kind.

When deciding how many part kinds to support when your editor is saving its parts of a document, you'll want to consider the tradeoff between portability and the space required to store your part as multiple kinds. The most transportable part kind (that is, the standard one) may not be the most compact or the one that will represent the underlying contents with the greatest fidelity. Typically, you'll want to store only the one preferred part kind, or the preferred kind and one standard part kind. If there isn't a standard kind that's roughly equivalent to your preferred kind, consider also storing a TEXT or PICT representation, simply to maximize the chances that the user will be able to see something for your part. For example, if your part's preferred kind is 3DMF, there isn't an equivalent standard kind, so you should also store a PICT representation. You might want to present the user with a Settings or Preferences dialog giving a choice of part kinds to store in addition to the preferred kind. See pages 476 and 479 of the OpenDoc Programmer's Guide for implementation details.

When your editor is providing data for a data transfer operation (such as a copy to the Clipboard), you may want to write out a greater number of standard part kinds than during a save operation. This is because during data transfer it's more likely that the user is trying to move content to a different editor or application. Providing standard part kinds in this situation is therefore even more important. On the other hand, remember that the user can use the Paste As command to get more options, including translation, so you needn't go overboard in supporting lots of kinds.

CATEGORY CONSIDERATIONS

After you've chosen the part kinds to support, you need to determine which category or categories these belong to. A part category is a set of part kinds that are conceptually similar. You might think of it as a generic term for several "brand name" variants. For example, the kODCategoryStyledText category might include the part kinds SurfWriter Text 3.0, SurfWriter Text 2.0, and

others.

OpenDoc looks at a part's category to decide which part editors or part viewers can be substituted if an editor is missing and whether to merge or embed data when content is copied from one part into another. Categories are specified by your editor in a name-mapping resource and can't be changed by the user.

Categories for existing part kinds have already been determined and should be adhered to; this set of categories is broad enough to include most new part kinds as well. A list of the predefined categories is given in Table 1. This list can be found in the OpenDoc Programmer's Guide on pages 477-478, but note that a new category has been added since the publication of the book: kODCategoryArchive.

Table 1. Predefined part categories

Part category Explanation
kODCategoryPlainTextPlain ASCII text
kODCategoryStyledText Styled text
kODCategoryDrawing Object-based graphics
kODCategory3DGraphic 3D object-based graphics
kODCategoryPainting Pixel-based graphics
kODCategoryMovie Movies or animations
kODCategorySampledSound Simple sampled sounds
kODCategoryStructuredSound Sampled sounds with additional information
kODCategoryChart Chart data
kODCategoryFormula Formula or equation data
kODCategorySpreadsheet Spreadsheet data
kODCategoryTable Tabular data
kODCategoryDatabase Database information
kODCategoryQuery Stored database queries
kODCategoryConnection Network-connection information
kODCategoryScript User scripts
kODCategoryOutline Outlines created by an outliner program
kODCategoryPageLayout Page layouts
kODCategoryPresentation Slide shows or other presentations
kODCategoryCalendar Calendar data
kODCategoryForm Forms created by a forms generator
kODCategoryExecutable Stored executable code
kODCategoryCompressed Compressed data
kODCategoryControlPanel Data stored by a control panel
kODCategoryControl Data stored by a control, such as a button
kODCategoryPersonalInfo Data stored by a personal information manager
kODCategorySpace Stored server, disk, or subdirectory data
kODCategoryProject Project-management data
kODCategorySignature Digital signatures
kODCategoryKey Passwords or keys
kODCategoryUtility Data stored by a utility function
kODCategoryMailingLabel Mailing labels
kODCategoryLocator Locators or addresses, such as URLs
kODCategoryPrinter Stored printer data
kODCategoryTime Stored clock data
kODCategoryArchive Archive or partial archive data such as TAR

The majority of the entries in the list (such as kODCategoryPlainText and kODCategoryStyledText) are self-explanatory, but a few need some clarification.

  • kODCategoryOutline -- Use this category when your part's content has some hierarchy -- that is, when the content is assigned to different nested levels. For example, the Cyberdog Notebook, an excerpt from which is shown in Figure 2, presents a collection of URLs in hierarchical form and thus is an outline.

  • kODCategorySpace -- Use this category when the content has no intrinsic order, as in the case of server, disk, or subdirectory (folder) data. For example, in a pre-System 7 Finder folder, the order of the contents depends entirely on the settings of the View menu. A part with content like this would belong to this category.

  • kODCategoryPersonalInfo -- Use this category for the various kinds of information represented in personal information management (PIM) applications.

  • kODCategoryPageLayout -- Use this instead of kODCategoryDrawing when the part contains only embedded content. In contrast, the category kODCategoryDrawing is for a drawing that has intrinsic content, such as circles and rectangles.

(image missing)

Figure 2. Example of an outline from the Cyberdog Notebook

Some of the categories seem as though they could be subsets of other categories -- for instance, kODCategoryPlainText could be a subset of kODCategoryStyledText, and kODCategory3DGraphic could be a subset of kODCategoryDrawing. But categories aren't hierarchical -- that is, one category can't include others.

When you're considering which category or categories your part kinds should belong to, ask yourself the following question for each of the categories: If users pasted my kind of data into a part belonging to this category, would they expect the content to be merged, or embedded as a separate part? If they would expect the content to be merged, that's a category your part kind should belong to. (Note that whether a part kind supports embedding doesn't affect which category it's in.)

For example, if users pasted a slide (a part belonging to the kODCategoryPresentation category) into some text (a part belonging to the kODCategoryStyledText category), they would expect the slide to be embedded within the text because the operations on slides and text are very different. But if they pasted one slide into another slide, they would expect the contents of the first slide to be merged into the destination slide; thus, the two parts should belong to the same category.

Consider another example. If users pasted a picture from a Web page into a part belonging to the category kODCategoryPainting, they would probably expect it to be merged. But if they pasted the picture into a part belonging to the category kODCategoryDrawing or kODCategory3DGraphic, they would probably expect it to be embedded, because the operations available in a painting part are usually very different from those in a drawing part. Thus, the picture should belong to the category kODCategoryPainting.

You need to choose one or more categories for each of the part kinds that your part editor supports. A part kind can be in multiple categories; for example, a part that can shift its view from table to chart should have a preferred kind that's a member of both categories. The same category can be specified for a part kind that represents a single object and a part kind that represents a collection of those objects; for example, you can specify kODCategoryDatabase for a part kind that represents a single database record and for a part kind that represents a collection of such records.

As mentioned earlier, when your part editor provides content to the Clipboard or a drag and drop object, you may want to write out a greater number of standard part kinds than during a save operation, to increase the probability of being able to interchange data with other parts. In fact, it will help if you support kinds in more than one category. Here's an example: Suppose a user copies some spreadsheet cells and pastes them into a chart. Because the operations on cells and charts are different, the user will expect the spreadsheet cells to be embedded. However, if the spreadsheet provides its copied data in a format that the chart is prepared to merge, the user gets a higher level of interoperability. If the spreadsheet and the chart both support kinds that are in the kODCategoryPlainText category, for instance, the chart can take the content of the spreadsheet and chart it instead of embedding the spreadsheet.

Here are some more examples of part kinds and the categories they fit into:

  • PostScript -- This page description language is used to define images in a structured fashion. The PostScript format might fit into either kODCategoryPageLayout or kODCategoryDrawing. We recommend kODCategoryDrawing because a part in PostScript format has intrinsic content like a drawing, such as arcs and clip shapes.

  • HTML -- Hypertext Markup Language (HTML) is similar to PostScript in that it defines a page layout. However, when HTML is displayed it typically looks more like styled text than like a drawing. Therefore, the appropriate category for HTML is kODCategoryStyledText.

  • BinHex -- Like many other formats that claim to be text but are only making use of text to define some richer format, BinHex is actually an archive format. Hence BinHex belongs in kODCategoryArchive.

  • URL -- Another kind that uses text to define some richer format, a URL should be in kODCategoryLocator.

If your part kinds don't appear to fit in

any of the predefined categories, you can request a new category. The list of predefined categories is maintained by CI Labs, a consortium that coordinates cross-platform OpenDoc development. See the CI Labs Web page for instructions on how to request a new category.

RESOURCES REQUIRED

Both part kinds and part categories are assigned in your part editor's name-mapping ('nmap') resources. You can learn how to construct these resources by looking at the Dynamic Binding recipes on the OpenDoc Class Reference CD. These resources are required:

  • EditorKinds -- lists every part kind your editor supports, except standard Macintosh part kinds

  • EditorPlatformKind -- lists the standard Macintosh part kinds your editor supports

  • KindCategories -- lists the category or categories your part kinds belong to

  • KindUserString -- lists the part kind user strings

If you request a

new category and CI Labs approves your request, you'll also need a CategoryUserString resource listing your category user strings. OpenDoc

already contains user strings for predefined categories.

Listing 1 shows an EditorPlatformKind resource indicating that your editor supports TEXT files and TEXT scrap data. Listing 2 demonstrates how a part editor would declare two part kinds that are in the same category in a KindCategories resource.



Listing 1. An example EditorPlatformKind resource

resource kODNameMappings (kPlatformEditorKindMapId) 
{
   kODEditorPlatformKind,
   {   /* array KeyList: 1 element */
      /* [1] */
      kYourEditorID,
      kODIsPltfmTypeSpac 
      {   /* array PltfmTypeSpacList: 2 elements */
         {
            /* [1] */
            kODPlatformFileType, 
            'TEXT',
            smRoman,
            langEnglish,
            "Plain Text",
            kODCategoryPlainText,
            /* [2] */
            kODPlatformDataType, 
            'TEXT',
            smRoman,
            langEnglish,
            "Plain Text",
            kODCategoryPlainText,
         }
      }
   }
};



Listing

2. An example KindCategories resource

resource kODNameMappings (kKindCategoryMapId)
{
   kODKind,
   {   /* array kinds: 2 elements */
      /* [1] */
      kStyledTextKind1,
      kODIsAnISOStringList
      {
         {   /* array categories: 1 element */
            /* [1] */
            kODCategoryStyledText
         }
      },
      /* [2] */
      kStyledTextKind2,
      kODIsAnISOStringList
      {
         {   /* array categories: 1 element */
            /* [1] */
            kODCategoryStyledText
         }
      }
   }
};

At run time, if you need to convert a Mac OS file type such as 'TEXT' to an ISO type, first get the translation object from the session:

ODTranslation* translation = session->GetTranslation(ev);

Then call the translation object to convert the Mac OS file type, or what we call the platform kind (a platform-neutral term), to an ISO type:

ODValueType valueType = 
         translation->GetISOTypeFromPlatformType('TEXT', kODPlatformFileType); 

You'll find kODPlatformFileType defined in StdDefs.xh, ODTranslation defined in Translt.xh, and ODSession defined in ODSessn.xh. Use kODPlatformDataType instead of kODPlatformFileType if you're converting a scrap type from the

Clipboard as opposed to a file type from the file system.


SOME HUMAN INTERFACE PRINCIPLES

There are some important human interface principles regarding part kinds that you should incorporate in the design of your part editor. They boil down to maintaining the fidelity of parts as they pass through various operations. One key principle of the user model is that editors shouldn't change the part kind of content without warning, because the translation may cause information to be lost. Only the user should be able to change the preferred kind of a part, and then only through an explicit action. This supports the concept that content copied into or out of an OpenDoc document should retain its fidelity. For example, when the user drags a drawing document from the desktop into an OpenDoc document and then back to the desktop, the initial document and the final document should be identical, as far as the user is concerned. The final document should have the same part kind as the original document, unless the user elects to change the part kind. (See the recipe for promising a

non-OpenDoc file on the OpenDoc Class Reference CD for more details.)

Users can change the preferred kind of a part with the Part Info (or Document Info) command in the Edit menu. This command brings up a dialog like the one shown in Figure 3. A pop-up menu offers a list of part kinds supported by the current editor, plus the possibility of translating to a different format with the "Translate to" command.

(image missing)

Figure 3. The Part Info dialog

When the user wants to save a document, your editor should write it out in the format of the preferred kind. The highest-fidelity kind that your editor writes should be the preferred kind. Don't change the preferred kind, because that would be implicit translation, or translating formats behind the user's back -- not a good idea, although some applications behave this way today. Perhaps you've seen this: the application claims to read or write a particular data format, but when a document of that file type is opened with that application and then saved, the application converts the document to its own proprietary format. Users are left wondering why their documents can't stick with the format they were created with.

To maximize interchange between OpenDoc, traditional applications, and system software, OpenDoc does not arbitrarily promote platform kinds (which, remember, is our platform-neutral term for Mac OS file types) to OpenDoc part kinds.*

In today's applications, this unexpected format change is also often associated with the creation of a new document named "Untitled x" or "FooDocument - converted." In OpenDoc, parts don't have control over the name of the document, so this errant behavior is prevented. The name of the document, just like the preferred kind of a part, should be considered a user setting. Editors shouldn't tamper with user settings.

There are situations where it's appropriate for the editor to query the user about changing a part kind. If the user tries some operation, or tries to add some content, that's not supported in the current kind but is supported in another kind that the editor understands, it's appropriate to suggest changing to the kind with more functionality.

As an example of the first situation, suppose the user is editing a plain-text document with an editor that supports styled text. If the user selects some text and tries to change it to bold, the part editor must allow this change but should warn the user that the operation will require a change in the part kind -- and the user must be allowed to veto this operation before it's done. In this situation the part editor should display an alert like the one shown in Figure 4.

(image missing)

Figure 4. Warning the user that an operation requires the part kind to be changed

As an example of the second situation, suppose the user now pastes some text that includes a page break and an indentation, which isn't supported in styled text but is supported in a proprietary format the part editor uses. The part editor should allow this change but present an alert (see Figure 5) and let the user veto the change.

(image missing)

Figure 5. Warning the user that adding content requires the part kind to be changed

HANDLING USER ACTIONS

A number of user actions require your editor to deal with part kinds and categories, though in most cases this interaction is transparent to the user. For example, when a user pastes content into a part, the editor of the part where the content is about to be pasted examines the part kinds and categories of the content being pasted. The editor decides which, if any, of the multiple part kinds available will be pasted. In this case, as in many others, the user doesn't realize what's going on behind the scenes with part kinds and

categories.

We'll discuss in detail what your editor should do with part kinds and categories in response to each of the following user actions:

  • creating a document

  • opening a document

  • saving a document

  • transferring data

  • changing the preferred kind

  • translating or converting a part

CREATING A DOCUMENT

To create a document, the user double-clicks on a stationery pad that you supply with your part editor. You must provide at least one stationery pad for each part category that your editor supports. For example, if your editor supports the "styled text" category and the SurfWriter Text, AcmeWriter Text, and RTF part kinds, you must supply (and your product's installer must install on the user's system) a stationery pad for at least one of these part kinds. Typically, you'll install a stationery pad for the highest-fidelity part kind

that you support.

You can optionally provide more than one stationery pad. When users decide to double-click on one stationery pad instead of another, they've made an explicit decision about the preferred kind of the document they want to be created.

Rules and conventions for installing part editors and stationery pads can be found in the OpenDoc Programmer's Guide, Appendix C, "Installing OpenDoc Software and Parts."

OPENING A DOCUMENT

Whenever a user opens a document containing one of your parts, your part must be reconstituted from external storage by your InitPartFromStorage method, described in detail in the article "Getting Started With OpenDoc Storage" in develop Issue 24. Your editor needs to find out the preferred kind and read in

the content data accordingly.

If your editor supports any platform kinds (Mac OS file types), you should first check for the HFSFlavor value type in the content property (kODPropContents) of the part's storage unit. If it's there, you've been bound to an empty storage unit that's pointing to a file that you should use to internalize from. This binding can happen in one of two ways: the user may have dragged and dropped a traditional Macintosh file onto an OpenDoc document and your part editor was bound to the drop, or the user may have opened a traditional Macintosh file with the OpenDoc launcher application. For detailed information on how to make this work, see the Drag and Drop Recipes on the OpenDoc Class Reference CD, specifically the section "Incorporating Data From a Non-OpenDoc Document." Also, see the section "Accepting Non-OpenDoc Data" on page 371 of the OpenDoc Programmer's Guide.

If your editor doesn't support any platform kinds, follow these steps:

  • Get the preferred kind -- that is, read the value from the kODPropPreferredKind property of the part's storage unit. If this property doesn't exist, the editor can assume that the preferred kind of the part is the value type of the first value in the content property. Keep the preferred kind in a field, as shown in the following example using utility functions from StdTypIO and TempObj:
#include <StdTypIO.h>
#include <TempObj.h>
...
// The following code belongs in your InitPartFromStorage method.
ODStorageUnit* su = self->GetStorageUnit(ev);
TempISOStr preferredKind = ODGetISOStrProp(ev, su, kODPropPreferredKind,
   kODISOStr, kODNULL);
if (preferredKind == kODNULL) {
   su->Focus(ev, kODPropContents, kODPosUndefined, kODNULL, 1, 
      kODPosUndefined);
   preferredKind = su->GetType(ev);
}
  • Focus your part's storage unit to the value of the content property whose value type is the preferred kind.
self->GetStorageUnit(ev)->Focus(ev, kODPropContents, kODPosUndefined,
   preferredKind, 0, kODPosUndefined);
  • Read the contents of that value and create the in-memory data structures necessary to represent that content. Use the ODStorageUnit method GetValue to accomplish this step.

Note that it's possible for your editor to be bound

to a part that previously had a different editor, as described earlier in "Editor Substitution Explained." In this case, the OpenDoc binding subsystem will automatically notify the user. If your editor doesn't support the preferred kind, use the highest-fidelity kind in the content property that your editor does support as the de facto preferred kind. Do not update the preferred kind property until Externalize or ChangeKind is called on your part.

SAVING A DOCUMENT

Whenever a user saves the document, your part must be written out to the storage unit, or externalized, by your Externalize method, described in detail in the article "Getting Started With OpenDoc Storage" in develop Issue 24. Your editor should write out the preferred kind, at a minimum; you may also decide to write out one or more alternate part kinds, as discussed earlier under

"Supporting Multiple Part Kinds."

The first two steps that are required have to do with preparing the storage unit for clean externalization from your part editor, also known as "prepping the storage unit." You should only have to do this the first time Externalize is called on your part.

  • Clean up the storage unit by removing any values that you won't be updating. This means calling the Remove method for any values in the content property that have value types (part kinds) that your editor doesn't support or that your editor won't externalize.

  • Add values if necessary. Use AddValue to create or recreate the value types that you want to externalize in proper fidelity order (from highest fidelity to lowest fidelity). Fidelity ordering is important because OpenDoc looks at it to determine which editor would best edit any given part.

  • Externalize your content in the format of the preferred kind that your editor kept track of in a local field.

  • Optionally, write out alternative part kinds. As mentioned earlier, the typical part editor should by default write out only the one preferred kind, or the preferred kind and one standard part kind. If you present users with a Settings or Preferences dialog to indicate a set of alternative part kinds to store, write out the alternative kinds indicated there.

Your Externalize

method may be called at times other than when the user saves a document. For example, depending on the Save model of the current document and the idle-time optimizations that may or may not be present, your part may be told to externalize only when the user saves a document or as often as every minute. Therefore, your editor shouldn't have preconceived notions about why it's asked to write out your part. As an optimization, your editor should keep an fDirty flag that's set whenever the user changes the part's content and cleared whenever externalization is completed. If your fDirty flag is clear, your Externalize method should be a no-op.

TRANSFERRING DATA

Whenever the user transfers data with Cut, Copy, Paste, Paste As, or drag and drop, your CloneInto method is called. See the section "The CloneInto Method of Your Part Editor" on pages 327-329 of the OpenDoc Programmer's Guide for the

precise details of implementing the CloneInto method.

For the purposes of multiple part kind support, however, your editor should do the following:

  • Write the same part kinds you would if you were externalizing, plus any standard part kinds you support. As explained earlier, it's more important to write out standard part kinds during CloneInto than Externalize because the user is more likely to be trying to move content to a different editor or application.

  • Call SetPromiseValue for each part kind if you're using promises (explained in the OpenDoc Programmer's Guide).

If your part editor is a

container, it's important for it to treat pasted content appropriately. When your container receives a Paste command or is the destination of a drag and drop, it should check the preferred kind of the incoming content to decide whether to merge or embed that content. If the category of the preferred kind of the incoming content is the same as the category of your content's kind, merge the incoming content; otherwise, embed the incoming content into a new

part.

As mentioned earlier, it's possible for kinds to belong to more than one category. If the incoming content's kind or your content's kind belongs to multiple categories, or if both do, as long as they share at least one category they can be said to be of the same category. If the incoming content isn't an OpenDoc part, simply use the data type that's closest to your own content kind as the de facto preferred kind for the incoming content.

If the user drops a part onto your part that you determine should be merged, and you find there's no content when you try to merge it, the operation will appear to be a no-op, which is very confusing to the user. You may want to actually embed an empty part in this case rather than merging nothing, so that the user at least receives some feedback.

You also should be aware of a concern about format fidelity that arises if the user attempts a paste or drop operation with your editor that involves content with other content embedded. Some data or formatting may be lost if one or more of the part kinds supported by your part editor is of lesser fidelity and can't handle embedded content, and at the time of the paste or drop the destination part editor can work only with the lower-fidelity kind. In this case, the destination part editor can't know that it's losing the embedded content.

What can you do to minimize these cases, or at least make them easier on the user? We strongly recommend that your part editor support embedding. If it doesn't, it shouldn't claim to support a kind that includes embedding. For example, the part editor that's the destination for a paste or drop shouldn't strip embedded content or links out of the data format. If your editor can't preserve the fidelity of the paste or drop, it must choose a lower-fidelity part kind; if there are no other kinds present that your editor supports, it shouldn't allow the paste or accept the drop. The only exception to this is when a plain-text editor receives a paste of styled text; in this case, it can use only the text and ignore the style information. Because text is so ubiquitous, it's handled differently from other kinds of content.

    If your part editor supports embedding, it should allow the user to embed any content that can't be merged; it shouldn't restrict the kinds that can be embedded.

Remember that if your part editor supports data interchange, it must completely support Undo, so that if data or formatting is lost in a transfer operation, the user can undo and recover what was lost. Although most of today's applications don't alert the user when data or formatting is lost, users seem to recognize with ease when they've experienced such a loss and need to choose Undo to recover. With the multiple-level Undo support in OpenDoc, recovering from a loss of data or formatting is much easier.

CHANGING THE PREFERRED KIND

Whenever the user changes the preferred kind of a part, your ChangeKind method is called. This is usually done from the Part Info (or Document Info) dialog shown earlier, but you shouldn't assume that that will be the only user interface that can cause this method to be called. Your editor should do the following:

  • Externalize the part in the new preferred data format. Make sure that the fidelity order of the values in your content property is maintained by creating the values for the supported part kinds in the right order. You may need to prep your storage unit again and recreate the values to ensure that they're in the proper fidelity order. It's up to your part editor whether you keep the previous preferred kind or not.

  • Write the new preferred kind into the preferred kind property of the part, as shown in the following example using utility functions from StdTypIO and TempObj:
#include <StdTypIO.h>
#include <TempObj.h>
...
// The following code belongs in your ChangeKind method; the kind
// that the user selected is passed in the changeKind parameter.
ODStorageUnit* su = self->GetStorageUnit(ev);
ODSetISOStrProp(ev, su, kODPropPreferredKind, kODISOStr, changeKind);

TRANSLATING OR CONVERTING A PART

The user can force translation of a part with the Part Info (or Document Info) command in the Edit menu, which brings up a dialog like the one shown earlier in Figure 3. The part kind pop-up menu in the dialog, in addition to listing part kinds supported by the current editor, offers the possibility of choosing "Translate to" and then choosing a part kind from the Translate To dialog. The part kind pop-up menu shown in Figure 6 illustrates a number of different ways that picture data can be stored on the Macintosh, including standard MIME types, standard Macintosh file types, and standard Macintosh data types. Of course, most part editors won't support this many different kinds.

(image missing)

Figure 6. The part kind pop-up menu

There are also data interchange utilities, such as converters and grinders, that convert parts or entire documents to different part kinds. This operation involves asking each part in the original document to externalize itself in a set of standard part kinds. The user may initiate this action by dropping a document on a converter or grinder icon (like the one shown in Figure 7) on the desktop. Your ExternalizeKinds method is called in response.

(image missing)

Figure 7. A converter icon

ExternalizeKinds is passed a list of kinds to externalize. Your part editor doesn't need to write other values it might ordinarily write in addition to the preferred kind. Your editor should do the following in its ExternalizeKinds method:

  • Externalize the set of part kinds specified. Make sure that the fidelity order of the values in your content property is maintained by creating the values for these part kinds in the right order. You may need to prep your storage unit again and recreate the values to ensure that they're in the proper fidelity order. Be sure to write out these kinds in addition to the preferred kind, not instead of the preferred kind.

  • Ignore any part kinds in the set that you don't support.

PARTING WORDS

By now you should have a good idea of all the ramifications of choosing the part kinds to support with your part editor. We hope that by spelling out what the tradeoffs are and suggesting how your part editor should respond to various user actions related to part kinds, we're helping to promote a consistent approach to working with part kinds. This is bound to result in more portable parts and happier users.


RELATED READING

  • OpenDoc Programmer's Guide for the Mac OS by Apple Computer, Inc. (Addison-Wesley, 1995). This book is accompanied by the OpenDoc Class Reference CD and includes the OpenDoc human interface guidelines.

  • OpenDoc Cookbook for the Mac OS by Apple Computer, Inc. (Addison-Wesley, 1995).

  • "The OpenDoc User Experience" by Dave Curbow and Elizabeth Dykstra-Erickson, develop Issue 22.

  • "Getting Started With OpenDoc Storage" by Vincent Lo, develop Issue 24.

  • Byte Guide to OpenDoc by Andrew MacBride and Joshua Susser (Osborne McGraw-Hill, 1996)

See Also[edit]