home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
odtlktv6.zip
/
ODTLKT
/
TOOLKIT
/
BOOK
/
ODPGGDE1.INF
(
.txt
)
next >
Wrap
OS/2 Help File
|
1995-12-14
|
1MB
|
14,438 lines
ΓòÉΓòÉΓòÉ 1. How to Use this Book ΓòÉΓòÉΓòÉ
This guide is intended for application developers creating programs using the
OpenDoc interface for OS/2 Warp who want to gain an understanding of the
OpenDoc programming environment and to develop part editors.
Before you begin to use this information, it would be helpful to understand how
you can:
Expand the Contents to see all available topics
Obtain additional information for a highlighted word or phrase
Use action bar choices
Use the programming information.
How to Use the Contents
When the Contents window first appears, some topics have a plus (+) sign
beside them. The plus sign indicates that additional topics are available.
To expand the Contents if you are using a mouse, click on the plus sign. If
you are using the keyboard, use the Up or Down Arrow key to highlight the
topic, and press the plus (+) key. For example, Code Pages has a plus sign
beside it. To see additional topics for that heading, click on the plus sign
or highlight that topic and press the plus (+) key.
To view a topic, double-click on the topic (or press the Up or Down Arrow key
to highlight the topic, and then press the Enter key).
How to Obtain Additional Information
After you select a topic, the information for that topic appears in a window.
Highlighted words or phrases indicate that additional information is
available. You will notice that certain words and phrases are highlighted in
green letters, or in white letters on a black background. These are called
hypertext terms. If you are using a mouse, double-click on the highlighted
word. If you are using a keyboard, press the Tab key to move to the
highlighted word, and then press the Enter key. Additional information then
appears in a window.
How to Use Action Bar Choices
Several choices are available for managing information presented in the
OpenDoc Class Reference for the Macintosh. There are three pull-down menus on
the action bar: the Services menu, the Options menu, and the Help menu.
The actions that are selectable from the Services menu operate on the active
window currently displayed on the screen. These actions include the following:
Bookmark
Allows you to set a placeholder so you can retrieve information of interest
to you.
When you place a bookmark on a topic, it is added to a list of bookmarks
you have previously set. You can view the list, and you can remove one or
all bookmarks from the list. If you have not set any bookmarks, the list is
empty.
To set a bookmark, do the following:
1. Select a topic from the Contents.
2. When that topic appears, choose the Bookmark option from the Services
pull-down.
3. If you want to change the name used for the bookmark, type the new
name in the field.
4. Click on the Place radio button (or press the Up or Down Arrow key to
select it).
5. Click on OK (or select it and press Enter). The bookmark is then added
to the bookmark list.
Search
Allows you to find occurrences of a word or phrase in the current topic,
selected topics, or all topics.
You can specify a word or phrase to be searched. You can also limit the
search to a set of topics by first marking the topics in the Contents list.
To search for a word or phrase in all topics, do the following:
1. Choose the Search option from the Services pull-down.
2. Type the word or words to be searched for.
3. Click on All sections (or press the Up or Down Arrow keys to select
it).
4. Click on Search (or select it and press Enter) to begin the search.
5. The list of topics where the word or phrase appears is displayed.
Print
Allows you to print one or more topics. You can also print a set of topics
by first marking the topics in the Contents list.
To print the document Contents list, do the following:
1. Choose Print from the Services pull-down.
2. Click on Contents (or press the Up or Down Arrow key to select it).
3. Click on Print (or select it and press Enter).
4. The Contents list is printed on your printer.
Copy
Allows you to copy a topic that you are viewing to the System Clipboard or
to a file that you can edit. You will find this particularly useful for
copying syntax definitions and program samples into the application that
you are developing.
You can copy a topic that you are viewing in two ways:
Copy copies the topic that you are viewing into the System Clipboard.
If you are using a Presentation Manager editor (for example, the
System Editor) that copies or cuts (or both) to the System Clipboard,
and pastes to the System Clipboard, you can easily add the copied
information to your program source module.
Copy to file copies the topic that you are viewing into a temporary
file named TEXT.TMP. You can later edit that file by using any editor.
You will find TEXT.TMP in the directory where your viewable document
resides.
To copy a topic, do the following:
1. Expand the Contents list and select a topic.
2. When the topic appears, choose Copy to file from the Services
pull-down.
3. The system puts the text pertaining to that topic into the
temporary file named TEXT.TMP.
For information on one of the other choices in the Services pull-down,
highlight the choice and press the F1 key.
The actions that are selectable from the Options menu allow you to change the
way your Contents list is displayed. To expand the Contents and show all
levels for all topics, choose Expand all from the Options pull-down. You can
also press the Ctrl and * keys together. For information on one of the other
choices in the Options pull-down, highlight the choice and press the F1 key.
The actions that are selectable from the Help menu allow you to select
different types of help information. You can also press the F1 key for help
information about the Information Presentation Facility (IPF).
ΓòÉΓòÉΓòÉ 1.1. OpenDoc Fundamentals ΓòÉΓòÉΓòÉ
OpenDoc is an architecture designed to enable the construction of compound,
collaborative, customizable, and cross-platform applications. It enables the
creation of compound documents, which are created and edited by several
cooperating applications working within a single document. OpenDoc also
supports scripting and extension mechanisms that allow for communication among
parts of a document. OpenDoc allows for multiple parts in a single document.
These different parts can range from a spread sheet to voice inside of the
document.
OpenDoc consists of a set of shared libraries that can be used to build editors
and viewers for compound documents, as well as other compound software to
provide services to documents.
ΓòÉΓòÉΓòÉ 2. About Component Integration Laboratories ΓòÉΓòÉΓòÉ
OpenDoc is presented and maintained through an organization devoted to
promoting cross-platform standards, architectures, and protocols in a
vendor-independent fashion. This organization, Component Integration
Laboratories (CI Labs), is composed of a number of platform and application
vendors with a common interest in solving OpenDoc issues and promoting
interoperability.
CI Labs supports several levels of participation through different membership
categories. If you are interested in shaping the future direction of component
software, or if you simply need to be kept abreast of the latest developments,
you can become a member. For an information packet, send your mailing address
to:
Component Integration Laboratories
PO Box 61747
Sunnyvale, CA 94088-1747
Telephone: 408-864-0300
FAX: 408-864-0380
Internet: cilabs@cilabs.org
ΓòÉΓòÉΓòÉ 3. Introduction ΓòÉΓòÉΓòÉ
OpenDoc is a revolutionary technology that brings a new class of applications
and documents to the Windows, Mac OS, OS/2, UNIX, and other personal-computer
platforms. With OpenDoc, hardware and software developers can deliver:
New software technologies to individual users
Better server integration to corporate users
Enhanced multimedia content to all users
OpenDoc enables the creation of cooperative component software that supports
compound documents, can be customized, can be used collaboratively across
networks, and is available across multiple platforms. In doing so, OpenDoc
fundamentally changes the nature of software development for personal
computers.
This chapter starts with a review of the reasons why OpenDoc was created,
followed by an overview of OpenDoc concepts:
How OpenDoc documents are structured
How OpenDoc software handles events and user interaction
How to extend OpenDoc's capabilities
How to ensure cross-platform compatibility for your OpenDoc software
Each of these topics is described more fully in subsequent chapters.
ΓòÉΓòÉΓòÉ 3.1. Why OpenDoc? ΓòÉΓòÉΓòÉ
Customer demand for increasingly sophisticated and integrated software
solutions has led to large and sometimes unwieldy application packages,
feature-laden but difficult to maintain and modify. Developing, maintaining,
and upgrading these large, cumbersome applications can require a vast
organization. The programs are difficult to create, expensive to maintain, and
can take years to revise.
Upgrades or bug fixes in one component of such an application require the
developer to release-and the customer to buy-a completely new version of the
entire package. Some developers have added extension mechanisms to their
packages to allow addition or replacement of certain components, but the
extensions are proprietary, incompatible with other applications, and
applicable to only certain parts of the package.
Because of the barriers put up by the current application architecture, users
are often frustrated in attempting to perform common tasks:
Users often cannot assemble complex documents from multiple sources
because of the many error-prone, manual operations required.
Users often cannot edit different kinds of content within a single
document. Most applications support only one or a few different kinds of
content, such as a single format for text and a single format for
graphics. Furthermore, the editing procedures for a given kind of content
are different across applications, complicating data transfer among
applications.
Business users are forced to choose between the reliability of
shrink-wrapped software and the extra features of custom solutions
designed for their needs. To increase reliability while maintaining and
adding custom features, businesses need simple and standardized designs
and procedures.
Users may not be able to use an editor or tool provided by an individual
developer or small development team because the editor may be
incompatible with the users' current application package, and the
developer may not have sufficient resources to develop an entire
integrated package.
Computers often frustrate users' efforts to collaborate with others.
Users cannot share documents across applications, recover changes to
shared documents, or, with rare exceptions, manipulate the contents of
one document from within another.
OpenDoc addresses these issues by enabling the development of a new kind of
application, one that provides advantages to both users and developers in the
increasingly competitive software markets of today and the future. OpenDoc
replaces the architecture of conventional monolithic applications in which a
single software package is responsible for all its documents' contents, with
one of components, in which each software module edits its own content, no
matter what document that content might be in. This is illustrated in the
following figure.
OpenDoc allows developers, large or small, to take a modular approach to
development and maintenance. Its component-software architecture makes the
design, development, testing, and marketing of integrated software packages
far easier and more reliable. Developers can make incremental improvements to
products without a complete revision cycle and can get those improvements to
users far more rapidly than is possible today.
For developers, this is a radical shift in approach, although its
implementation is not difficult. For users, this is only a minor shift in
working style; its main effect is to remove the barriers to constructing and
using complex documents imposed by conventional monolithic application
architecture.
OpenDoc components allow users to assemble customized compound documents out
of diverse types of data. They also support cross-platform sharing of
information. They resolve user frustrations with conventional applications by
removing the barriers listed earlier:
OpenDoc makes it easy for users to assemble any kind of content in any
kind of document. OpenDoc documents will accept all kinds of media for
which application components exist, now and in the future.
OpenDoc makes it easy for users to edit any kind of data, in-place, in
any document. Users can readily transfer that data to any other document
and edit it there just as easily. This lets users focus on document
content and lets them take advantage of the context provided by the
surrounding document.
OpenDoc allows businesses to customize their software solutions by
assembling components into shrink-wrapped packages, thereby obtaining
needed features, increasing reliability, and saving on training costs.
In-house developers can then enhance the packages by developing
components that integrate smoothly with the off-the-shelf software. Some
developers bundle components together into packages that are similar to
conventional monolithic applications; others sell individual components
for specialized purposes, to users or to other developers for bundling.
Users can purchase packages and use them as is, or they can modify a
package by adding or replacing individual components.
OpenDoc lowers market barriers to wide, cross-platform distribution for
small developers. Components can be ported easily to other
OpenDoc-supported platforms. Small development teams can create
individual components with the knowledge that they will integrate
smoothly with existing packages created by larger developers.
OpenDoc promotes collaboration, allowing users simultaneously or
separately to build documents as a team, on a single machine or across
networks. Documents are not owned by single applications, and changes are
recoverable through a draft history that is available for every document.
Pervasive scripting support, rich data-transfer capabilities, and an
extension mechanism allow users to manipulate their own and other
documents in powerful ways.
While providing all of these advantages, OpenDoc exists harmoniously with
existing monolithic applications; the user need not abandon conventional
applications in order to start using OpenDoc. The following table summarizes
some of the principal advantages of the OpenDoc approach to software for both
users and developers.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé ΓöéFor UsersΓöéFor Software ΓöéFor Software ΓöéFor Small Γöé
Γöé Γöé ΓöéEngineering ΓöéMarketing ΓöéDevelopment Γöé
Γöé Γöé Γöé Γöé ΓöéTeams Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéModularity ΓöéCan ΓöéCan easily ΓöéCan assemble ΓöéCan create Γöé
Γöé Γöéeasily Γöétest and Γöépackages withΓöécomponents Γöé
Γöé Γöéadd or Γöéupgrade Γöégreat Γöéthat work Γöé
Γöé Γöéreplace Γöécomponents; Γöéflexibility Γöéseamlessly Γöé
Γöé Γöédocument Γöécan reuse Γöé Γöéwith all Γöé
Γöé Γöéparts Γöécomponents Γöé Γöéothers Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéSmall size ΓöéLess ΓöéEasier to ΓöéEasier, ΓöéFaster Γöé
Γöé Γöémemory Γöédesign, code,Γöécheaper Γöédevelopment Γöé
Γöé Γöéand disk Γöédebug, and Γöédistribution Γöéand easier Γöé
Γöé Γöéspace Γöétest Γöé ΓöédistributionΓöé
Γöé Γöéneeded Γöé Γöé Γöéof a Γöé
Γöé Γöé Γöé Γöé Γöécomponent Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéMultiple ΓöéDocumentsΓöéDevelopment ΓöéOpportunitiesΓöéApplication Γöé
Γöéplatforms Γöétravel Γöéeffort on oneΓöéfor increasedΓöéof limited Γöé
Γöé Γöéacross Γöéplatform can Γöémarket share Γöéresources Γöé
Γöé ΓöéplatformsΓöéat times be Γöé Γöécan at timesΓöé
Γöé Γöéso users Γöéleveraged to Γöé Γöébe used Γöé
Γöé Γöécan Γöéothers Γöé Γöéacross Γöé
Γöé Γöéselect Γöé Γöé Γöédifferent Γöé
Γöé Γöéfamiliar Γöé Γöé Γöéplatforms Γöé
Γöé Γöéeditors Γöé Γöé Γöé Γöé
Γöé Γöéon each Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéScriptabilityΓöéUsers ΓöéIncreased ΓöéBetter ΓöéIncreased Γöé
Γöéand Γöéhave Γöéability for Γöécoordination Γöéability to Γöé
ΓöéextensibilityΓöégreater ΓöécommunicationΓöéamong Γöécommunicate Γöé
Γöé Γöécontrol Γöéamong parts Γöécomponents inΓöéwith other Γöé
Γöé Γöéover Γöé Γöéa package Γöécomponents Γöé
Γöé Γöébehavior Γöé Γöé Γöé Γöé
Γöé Γöéof Γöé Γöé Γöé Γöé
Γöé Γöédocument Γöé Γöé Γöé Γöé
Γöé Γöéparts Γöé Γöé Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 3.2. Overview of OpenDoc ΓòÉΓòÉΓòÉ
OpenDoc is a set of DLLs designed to facilitate the easy construction of
compound, customizable, collaborative, and cross-platform documents. To do
this, OpenDoc replaces today's application-centered user model with a
document-centered one. The user focuses on constructing a document or
performing an individual task, rather than using any particular application.
The software that manipulates a document is hidden, and users feel that they
are manipulating the parts of the document without having to launch or switch
applications.
This document-centered model does not mean that OpenDoc supports only those
kinds of data found in paper documents. An OpenDoc document can contain data as
diverse as navigable movies, sounds, animation, database information such as
networked calendars, as well as traditional spreadsheets, graphics, and text.
OpenDoc is an ideal architecture for multimedia documents. In OpenDoc, each new
kind of medium that is developed-video, sound, animation, simulation, and so
on- can be represented as part of any document. Thus an OpenDoc document is
automatically able to contain future kinds of media, even kinds not yet
envisioned, without any modification.
Although OpenDoc lends itself directly to complex and sophisticated layout, its
usefulness is by no means restricted to page-layout kinds of applications or
even compound documents. The scripting and extension mechanisms allow for
communication among parts of a document for any imaginable purpose. Tools such
as spelling checkers can be created as components and can then access the
contents of any parts in a document that support them; database-access
components can feed information to any parts of a document; larger programs
such as high-end printing applications can use specialized components to
manipulate the data of all parts of a document for purposes such as proof
printing and color matching.
The rest of this chapter summarizes the main features of OpenDoc for both users
and developers. The rest of this book explains in more detail how to develop
software that provides those features.
ΓòÉΓòÉΓòÉ 3.3. Parts ΓòÉΓòÉΓòÉ
OpenDoc uses a few simple ideas to create a structure that integrates a wide
range of capabilities. The basic elements are documents, their parts, their
frames, and the part-editor code that manipulates them. Those elements,
represented in a set of object-oriented class libraries, define a number of
object classes. The classes provide interoperability protocols that allow
independently developed software components to cooperate in producing a single
document for the end user. Through the class libraries, these cooperating
components share user-interface resources, negotiate over document layout on
screen and on printing devices, share storage containers, and create data links
to one another.
This section describes part editors, parts and frames, and how parts are
categorized, drawn, and stored in documents. It also describes the two
different kinds of part editors and the capability to develop other kinds of
OpenDoc components as well.
ΓòÉΓòÉΓòÉ 3.3.1. Documents, Parts, and Embedding ΓòÉΓòÉΓòÉ
Documents, not applications, are at the center of OpenDoc. Individual documents
are not tied to individual applications. In creating OpenDoc documents,
software components called part editors replace the conventional monolithic
applications in common use today. Each part editor is responsible only for
manipulating data of one or more specific kinds in a document.
The user does not directly launch or execute part editors, however. The user
works with document parts (or just parts), the pieces of a document that, when
executing, include both the document data and the part-editor code that
manipulates it. See the following figure.
A part can exist as document on its own, or it can be embedded in other parts,
allowing the user to create documents of arbitrary complexity.
ΓòÉΓòÉΓòÉ 3.3.1.1. Parts and the User ΓòÉΓòÉΓòÉ
In general, a user can perform all the tasks of an application by manipulating
a part instead of separately launching or executing a part editor. For example,
a user can create and use a spreadsheet part that looks and acts just like a
spreadsheet created by a spreadsheet application. However, there are four main
differences between a document consisting of parts and a document created by a
conventional application:
An OpenDoc document is built and manipulated differently. OpenDoc users
can assemble a document out of parts of any kind-using their graphics
part editor of choice, for example, to embed illustrations within a word
processor document. Developers can write or assemble such groups of part
editors for sale as integrated packages, or users can purchase individual
part editors separately.
New parts of any kind can be added to an OpenDoc document. The user can
purchase additional part editors-for example, a charting utility to
accompany a spreadsheet-and immediately use them, for example, to embed
charts into existing documents.
In editing, copying, or pasting a part, the user need not be aware of the
code that is executing. In fact, the user cannot directly launch a part
editor at all. The user manipulates the part data itself, within the
context of the document. (It is not necessary to open the part into a
separate window for editing). The document containing that part is opened
and closed independently of the part's part editor.
The user can replace part editors. If the editor for a specific kind of
part in a document is not available, or if the user prefers to use a
different part editor-for example, to replace one charting utility with
another-the user can specify that the new editor be used with all parts
created under the previous editor. This gives the user the freedom to
work with all of the editors of a package or replace any of them with
others that the user prefers.
ΓòÉΓòÉΓòÉ 3.3.1.2. Part Editors ΓòÉΓòÉΓòÉ
A part editor has fewer responsibilities than a conventional application. Each
part editor must:
Display its part, both on-screen and when printing.
Edit its part by changing the state of the part in response to events
caused by user actions.
Store its part, both persistently and at run time.
At run time, a part is an object that encapsulates both the state and
behavior. The part data provides the state information, and the part editor
provides the behavior; when bound together, they form an editable object. As
with any object, only the state is stored when the object is stored. Also,
multiple instantiations of an object do not mean multiple copies of the editor
code; one part editor in memory serves as the code portion for any number of
separate parts that it edits.
OpenDoc dynamically links part editors to their parts at run time, choosing an
editor based on the kinds of parts that the document contains. Dynamic linking
is necessary for a smooth user experience, because any sort of part might
appear in any document at any time.
ΓòÉΓòÉΓòÉ 3.3.1.3. Other Software Components ΓòÉΓòÉΓòÉ
A user can apply several kinds of OpenDoc components to compound documents. A
part editor, as noted earlier, is a full-featured application component; it
allows the creation, editing, and viewing of parts of a particular kind. Part
editors are the functional replacements for conventional applications; they
represent the developer's primary investment. Like applications, part editors
are sold or licensed and are legally protected from unauthorized copying and
distribution.
Part Viewers are application components that can display a part of particular
kind but cannot be used to create or even edit such a part. To enhance the
portability of OpenDoc compound documents across machines and across platforms,
it is important that part viewers of all kinds be widely available. Developers
are expected to create and freely distribute part viewers without restriction
for all kinds of parts that they support. A part viewer is really just a part
editor with its editing and part-creation capability removed; the developer can
create both from the same code base.
Wide availability of a part viewer encourages purchase and use of its
equivalent part editor, because users will know that other users will be able
to view parts created with that editor. (In some cases it is possible to view a
part even when neither its editor nor viewer is present. See the discussion of
translation in Part Data Types).
The OpenDoc architecture also allows for the development of software components
that, unlike part editors and part viewers, are not directly involved in
creating or displaying document parts. Special components called services, for
example, provide software services to parts. Spell checking or database-access
tools, when developed as service components, can increase the capabilities of
part editors. Users could apply them to a single part, to all the parts of a
document, or even across separate documents.
Version 1.0 of OpenDoc provides complete support for part editors and part
viewers. Future releases may explicitly support services and other types of
component software as well.
ΓòÉΓòÉΓòÉ 3.3.1.4. Frames and Embedding ΓòÉΓòÉΓòÉ
Parts in an OpenDoc document have a hierarchical arrangement with each other.
The logical structure of a document, which underlies its graphical
presentation, consists of the embedding relationships among its parts and their
frames.
Parts are displayed in frames, bounded areas that display a part's contents.
Frames are commonly rectangular, but may be any shape. The display frame of one
part-the frame within which the part is viewed-can itself be embedded within
the content of another part. The following figure shows such a relationship:
the inner frame is an embedded frame of the outer part, the inner part is an
embedded part of the outer part, and the outer part is the containing part of
the inner part.
An embedded part is logically contained in its containing part, just as its
frame is visually embedded in the containing part's frame. The containing part,
however, largely ignores the characteristics of its embedded parts. A
containing part treats its embedded frames as regular elements of its own
content; it can move, select, delete, or otherwise manipulate them, without
regard to what they display. The embedded part itself takes care of all drawing
and event handling within an embedded frame.
Every document has a single part at its top level, the root part, in which all
other parts in the document are directly or indirectly embedded. The root part
controls the basic layout structure of the document (such as text or graphics)
and the document's overall printing behavior. The following figure shows an
example of the relationships of the root part and two embedded parts.
Part 1, a text part, is the root part.
Part 2, a graphics part, is embedded in part 1.
Part 3, another text part, is embedded in part 2.
Each embedded part is displayed within its frame, which is embedded at some
location in the containing part's content. (See the figure in Activation and
Selection for an explanation of the visual characteristics of the frame
borders). The following figure shows an example of the relationships of the
root part and two embedded frames.
Because it can now contain embedded frames displaying any kind of content, the
document is not a monolithic block of data under the control of a single
application, but is instead composed of many smaller blocks of content
controlled by many smaller software components. In large part, OpenDoc exists
to provide the protocols that keep the components from getting in each other's
way at run time and to keep documents editable and uncorrupted.
All parts can be embedded in other parts, and every part must be able to
function as the root part of a document. However, not all parts are required
to be able to contain other parts; simple or specialized utility programs such
as clocks or sound players might not have a reason to embed other parts. Such
parts are called noncontainer parts; they can be embedded in any part, but
they cannot embed other parts within themselves. Parts that can embed as well
as be embedded are called container parts. It is somewhat simpler to write an
editor for a noncontainer part than for a container part, although container
parts provide a far more general and flexible user experience. Unless
embedding makes absolutely no sense for your purposes, you should create part
editors that support embedding.
Parts can have more than one frame
A part embedded in a document is not restricted to appearing in a single
frame. Parts can have multiple frames, displaying either duplicate views or
different aspects of the same part. See Presentation for more information.
ΓòÉΓòÉΓòÉ 3.3.2. Part Data Types ΓòÉΓòÉΓòÉ
Fundamental to the idea of a compound document is that it can hold different
types of data. Each part editor can manipulate only its own kinds of data,
called its intrinsic content. For a simple drawing part, for example, the
elements of its intrinsic content might be bit maps. For a simple text part,
they might be characters, words, and paragraphs. No part editor is asked to
manipulate the content elements of any part whose data it does not understand.
OpenDoc supports both specific and general classifications of data type, so
that it can associate parts with specific part editors as well as with general
classes of part editors when appropriate.
ΓòÉΓòÉΓòÉ 3.3.2.1. Part Kind ΓòÉΓòÉΓòÉ
Different parts in a document hold data of different purpose and different
format, understandable to different part editors. For meaningful drawing and
editing to occur, OpenDoc must be able to associate a part editor only with
data that the editor can properly manipulate.
OpenDoc uses the concept of part kind, a typing scheme analogous to file type,
to determine which part editor is to be associated with a given part in a
document. Because OpenDoc documents are not associated with any single
application, a file type is insufficient in this case; each part within a
document needs its own "type", or in this case, part kind.
Part kinds are specified as ISO strings, (null-terminated 7-bit ASCII strings),
although they are usually manipulated as tokens (short, regularized
representations of those strings) by OpenDoc and by part editors. A part kind
specifies the exact data format manipulated by an editor; it may have a name
similar to the editor name, such as "SurfDraw" or "SurfBase III", although
names more descriptive of the creator and the specific data format itself, such
as "SurfCorp:Movie:AVI", are preferable. (The user of such a part would see a
different but related string describing the part kind, which in this case might
be "SurfTime movie").
Part-editor developers define the part kinds of their own editors. A single
editor can manipulate more than one part kind, if it is designed to do so, and
a single part can be stored with multiple representations of its data, each of
a different part kind. All parts created by an editor initially have its part
kind (or kinds), although that can change; see Changing Part Editors.
ΓòÉΓòÉΓòÉ 3.3.2.2. Part Category ΓòÉΓòÉΓòÉ
The concept of part kind does not address the similarities among many data
formats. For example, data stored as plain ASCII or Unicode text could
conceivably be manipulated by any of a number of part editors. Likewise, video
data stored according to a standard might be manipulated by many different
video editors. Furthermore, text that closely resembles ASCII or Unicode, and
stored video that adheres in all but a few details to a particular standard,
might nevertheless be editable or displayable to some extent by many text
editors or video editors.
It might be unduly confining to restrict the editing or display of a part of a
given kind to the exact part editor that actually created it. Unless the user
has every part editor that created a document, the user cannot edit or even
view all parts of it. Instead, OpenDoc facilitates the substitution of part
editors by defining part category, a general description of the kind of data
manipulated by a part editor. When users speak of a "text part" or a "graphics
part", they are using an informal designation of part category.
Like part kinds, part categories are specified as ISO strings. Part categories
have broad designations, such as "plain text," "styled text," "bitmap", or
"database." Part-editor developers work with Component Integration Laboratories
(CI Labs), a consortium created to coordinate cross-platform OpenDoc
development, to define the list of part categories recognized by OpenDoc. See
Cross-Platform Consistency and CI Labs for more information on CI Labs. See the
table under Part Categories Supported by an Editor for a list of defined part
categories.
Each part editor specifies the part categories that it can manipulate. For each
defined part category (such as "plain text") the user can then specify a
default editor (such as "SurfWriter 3.0") to use as a default with any part in
that category whose preferred editor-the part editor that created it or last
edited it-is missing.
ΓòÉΓòÉΓòÉ 3.3.2.3. Embedding Versus Incorporating ΓòÉΓòÉΓòÉ
When the user pastes data into a document, the pasted data can either be
incorporated into the intrinsic content of the destination part (the part
receiving the data), or it can become a separate embedded part. As the
following figure shows, the destination part, the part receiving the data,
decides whether to embed or incorporate the data. It bases the decision on how
closely the part kind and category of the pasted data match the part kind and
category of the destination content.
This is how the decision is made:
If the part kinds of pasted data and destination match, the data should
be incorporated; the SurfWriter text shown in the preceding figure, for
example, is pasted into the intrinsic data of the SurfWriter (text) part.
If the part categories are different, the pasted data should be embedded;
the SurfPaint bitmap shown in the preceding figure, for example, is
embedded in the SurfWriter text part.
If the part categories are the same but the part kinds are different (a
possibility not shown in the preceding figure), the destination part
should, if possible, convert the data to its own kind and then
incorporate it. Of course, if the destination part cannot read the part
kind of the pasted data, it should embed it as a separate part.
In all of these cases, it is the destination part that decides whether to
embed or to incorporate. The user can guide that decision by manually forcing
a paste to be an incorporation or an embedding; see Handling the Paste As
Dialog Box for more information.
Note: For the data being pasted, part kind and part category refer to the
kind and category of the outermost, or enclosing, data. There may be
any number of parts embedded within that data, of various kinds and
categories. Those parts are always transferred as embedded parts,
regardless of whether the outermost data is itself incorporated or
embedded.
ΓòÉΓòÉΓòÉ 3.3.2.4. Changing Part Editors ΓòÉΓòÉΓòÉ
When a part editor stores or retrieves the data of its part, it can only
manipulate it using the part kind or kinds that the editor understands and
prefers. Furthermore, different systems may have different sets of available
part editors. Therefore, the editor assigned to a part can change over the
part's lifetime.
For example, the user might open a document (or paste data) containing a part
of a kind for which the user does not have the original part editor. In that
case, OpenDoc substitutes the user's default editor for that part kind or part
category (if the default editor can read any of the part's existing part
kinds). If the default editor cannot read the part, OpenDoc searches for an
editor that can. Once OpenDoc locates and assigns an editor to the part, the
new editor then becomes the part's preferred editor, and it subsequently stores
the part's data using its own part kinds. See Binding for more detailed
information on this process.
Lack of a part editor never prevents a user from opening a document. If no
editor on the user's system can read any of the part kinds in a part, the part
contents remain unviewable and uneditable, and its preferred part editor and
part kinds do not change. Nevertheless, OpenDoc still displays a gray box
representing the part within the area of the part's frame, and the user may be
given the option of translating the data into an editable format, as described
next.
ΓòÉΓòÉΓòÉ 3.3.2.5. Translation ΓòÉΓòÉΓòÉ
Changing the part editor for a part often means changing data formats and
therefore may involve loss of information or may not be directly possible. For
example, any text editor might be able to display and edit plain ASCII text
without loss, but a sophisticated word processor may be able to read another
word processor's data only imperfectly, if at all.
Nevertheless, the user can in some cases choose to employ a part editor with a
part that the editor cannot directly manipulate. In such a case, OpenDoc or a
part editor performs the necessary translation to convert the part into a
format usable by the editor. Translation is possible only if the appropriate
translator, or filter, is available on the user's machine to perform the
translation between part kinds. The fidelity, or quality, of the translation
depends on the sophistication of the translators. Translators are
platform-specific utilities that are independent of OpenDoc; OpenDoc simply
provides an object wrapper for a given platform's translation facilities.
Note: This function is not supported in the current release of OpenDoc.
OpenDoc provides support for translation when a document is first opened
during data transfers such as drag and drop, through semantic events, and at
any time the user chooses.
ΓòÉΓòÉΓòÉ 3.3.3. Displaying Parts ΓòÉΓòÉΓòÉ
In preparing a compound document for viewing, two issues are of prime
importance: managing the competition for space among the parts of the document
and making provisions for each part to draw itself.
OpenDoc provides a platform-independent interface for part editors, although
the interface does not include any drawing commands or detailed graphics
structures. OpenDoc provides an environment for managing the geometric
relationships among frames, and it defines object wrappers for accessing
platform-specific graphic and imaging structures.
ΓòÉΓòÉΓòÉ 3.3.3.1. Drawing Structures ΓòÉΓòÉΓòÉ
Each part in an OpenDoc document is responsible for drawing its own content
only (including, at certain times, the borders of the frames embedded within
it). Thus, a part does not draw the interiors of its embedded frames because
they contain the content of other parts (which must draw themselves).
Drawing a document is therefore a cooperative effort, for which no part editor
is completely responsible; each part editor is notified by OpenDoc when it must
draw its own part.
Drawing in OpenDoc relies on three fundamental graphics-system-specific
structures for which OpenDoc provides wrapper objects:
Canvas, a description of a drawing environment.
A dynamic canvas, such as a screen display, can potentially be changed
through scrolling or paging.
A static canvas, such as a printer page, cannot be changed once it has
been rendered. OpenDoc allows for different behavior when drawing onto
dynamic and static canvases.
Shape, a structure that describes a geometric shape.
Transform, a structure that describes an offset, plus possibly a scaling
factor or other geometric transformation.
When a part actually draws itself within a frame, it uses an object closely
related to the frame. A facet is an object that is the visible representation
of a frame (or a portion of a frame) on a canvas. A frame typically has a
single facet, but for offscreen buffering, split views of a part, or special
graphics effects, it may have more than one.
In general, frames control the geometric relationships of the content that
they display, whereas facets control the geometric relationships of frames to
their containing frames or windows. The facet is associated with a specific
drawing canvas, and both frames and facets have associated shapes and
transforms. The following figure summarizes some of the basic relationships
among frames, facets, shapes, transforms, and canvas in drawing; for more
detail see Transforms and Shapes.
The left side of the preceding figure shows the content area of an embedded
part as a page with text on it. The portion of the part that is available for
drawing is the portion within the frame shape assigned to the part by its
containing part. (A part may have more than one frame, but only one is shown
here). The frame's internal transform positions the frame over the part
content. (Another shape, the used shape, defines the portion of the frame
shape that is actually drawn to; in this case, the used shape equals the frame
shape).
The center of the preceding figure shows the facet associated with this
embedded part's frame. (A frame may have more than one facet, but only one is
shown here). The clip shape defines where drawing can occur in relation to the
content of the containing part. In this case, the containing part is the root
part in a document window, but it could be an embedded part displayed in its
own frame. The clip shape is typically similar to the frame shape except that,
as shown in the preceding figure, it might have additional clipping to account
for other parts or for elements of the containing part that overlap it. The
facet's external transform positions the facets within the containing part's
frame and facet. (Another shape, the active shape, defines the portion of the
area of the facet that the embedded part will respond to events within. in
this case, the active shape equals the frame shape).
The right side of the preceding figure shows the result of drawing both parts
on the window's canvas. The portion of the embedded part defined by the frame
is drawn in the area defined by the facet. If this embedded part is the active
part, meaning that the user can edit its contents, OpenDoc draws the active
frame border around it; the shape of that border is based on the facet's
active shape.
ΓòÉΓòÉΓòÉ 3.3.3.2. Presentation ΓòÉΓòÉΓòÉ
There are many ways that a part editor can draw the contents of a part. A word
processor can draw its data as plain text, styled text, or complete page
layouts; a spreadsheet can draw its data as text, tables, graphs, or charts; a
3-D drawing program can draw its shapes as wire-mesh polyhedrons, filled
polyhedrons, or surface-rendered shapes with specified lighting parameters.
For any kind of program, individual frames of a single part can display
different portions of different views of its data. For example, in a page
layout part, one frame of a page could display the header or footer, while
another could display the text of the page, and yet another could show the
outline of the document. For a 3-D graphics part, different frames could show
different views (top, front, side) of the same object. For any kind of program,
an auxiliary palette, tool bar, or other set of controls might be placed in a
separate frame and be considered an alternative "view" of the part to which it
applies.
OpenDoc calls such different part-display aspects part presentations and
imposes no restrictions on them. The following figure shows some examples of
different presentations for individual parts in a document. There are only two
embedded parts, but each has several display frames, and each frame has a
different presentation.
Your part editor determines the presentations that its parts can have, and it
stores a presentation designation (for your own drawing functions to make use
of) in each of your part's frames. You can store other display-related
information as the part info data of a frame. Each frame and each facet has the
capability to store part info data that you can access; in that data, you can
store any useful private information relating to the display of your part in
that frame or facet. Only you use the part info data of your frames and facets.
ΓòÉΓòÉΓòÉ 3.3.3.3. View Type ΓòÉΓòÉΓòÉ
OpenDoc does not specify presentation type, but it nevertheless defines some
aspects of part display. Each part has a view type, a designation of its
fundamental display form of the part in that frame. Basically, view type states
whether a part is to be displayed in icon form or with its contents visible.
In most situations in most documents, each part displays its contents, or some
portion of its contents, within its frame. However, a part can also display
itself as one of several kinds of icons. See the following figure for examples.
Each basic display form (standard icon, small icon, thumbnail icon, or framed)
is a separate view type. Your part editor stores a designation of the part's
view type in each of your part's display frames. A single part can, of course,
have different view types in different frames.
On the desktop and in Workplace Shell folders, parts are individual closed
documents and by default have an icon view type. When such a document is
opened, the part gives its frame a frame view type, and its contents (including
embedded parts) then become visible. Nevertheless, open documents can have
embedded parts displayed as icons. Keep in mind that a part with an icon view
type is not necessarily closed or inactive. A sound part, for example, might
have nothing but an icon view type, even when playing its sound.
Each containing part specifies, by setting a value in its embedded part's
frame, the view type it prefers the embedded part to have. Your container parts
can specify the view type for each embedded frame they create; when embedded,
your parts should initially display themselves in the view type expected by
their containing parts. See, for example, View Type for more information and
guidelines.
ΓòÉΓòÉΓòÉ 3.3.3.4. Document Windows and Part Windows ΓòÉΓòÉΓòÉ
Compound documents are displayed in windows. A document window is a window that
holds a single OpenDoc document; that document can contain a single part or
many parts. Document windows in OpenDoc are essentially the same as the windows
that display a conventional application's documents.
An architectural cornerstone of OpenDoc is that it provides in-place editing of
all parts in a compound document. Users can manipulate the content of any part,
no matter how deeply embedded, directly within the frame that displays the
part.
Nevertheless, a user might wish to view more of a part than is displayed within
a frame. Even if a frame is resizable and supports scrolling of its contents,
it might be more convenient to view that frame's part separately, in its own
window. OpenDoc supports this by allowing users to open a separate window,
called a part window, which looks similar to a document window. See the
following figure for an example.
Like document windows, part windows can themselves contain embedded parts. But
because your part is the root part in any part windows that your part editor
creates, you may take a more active role in window handling than you do for
your part when it is an embedded part in a document window. See Creating and
Using Windows for information on handling part windows.
ΓòÉΓòÉΓòÉ 3.3.3.5. Frame Negotiation ΓòÉΓòÉΓòÉ
In a compound document with embedded parts, the embedding hierarchy and the
frame locations determine the geometric relationships among parts. Each part
controls the positions, sizes, and shapes of the frames embedded within it. If
an embedded part needs to change the size of its frame or add another frame, it
must negotiate for that change with its containing part. This frame negotiation
allows an embedded part to communicate its needs to its containing part;
however, the containing part has ultimate control over the embedded frames'
sizes and shapes.
The following figure shows a simple example of frame negotiation. A user edits
an embedded part, adding enough data so that the content no longer fits in the
embedded part's current frame size. The embedded part requests a larger frame
from the containing part. The containing part can either grant the request or
return a different frame size from that requested. In this example, the
containing part cannot accommodate the full size of the requested frame and so
returns a frame size to the embedded part that is larger than the previous
frame but not as large as the requested one.
The frame-negotiation process is described in more detail in Frame Negotiation.
ΓòÉΓòÉΓòÉ 3.4. Event Handling ΓòÉΓòÉΓòÉ
Most part editors interact with the user primarily by responding to user
events. User events are messages sent or posted by the operating system in
response to user actions, activation or deactivation of a part or window, or
messages from other event sources. Based on information in a user event, a part
editor might redraw its part, open or close windows, perform editing
operations, transfer data, or perform any sort of menu command or other
operation.
OpenDoc has several built-in event-handling features that help your part editor
function properly within a compound document. For example, instead of polling
for events, as a typical application does, your part editor acts when notified
by OpenDoc that an event has occurred.
This section notes some of OpenDoc's event-handling features; for more
information on user events, see User Events.
ΓòÉΓòÉΓòÉ 3.4.1. The Document Shell and the Dispatcher ΓòÉΓòÉΓòÉ
Part editors respond differently to user events than conventional applications
do. Part editors do not receive events directly; OpenDoc receives them and
dispatches them to the proper part.
Because they are not complete applications, and because they must function
cooperatively, part editors run in an environment that itself handles some of
the tasks that conventional applications typically perform. That environment is
called the OpenDoc document shell; it is an executable that handles certain
application-level tasks and provides an address space for each OpenDoc document
within which part editors manipulate document content.
Whenever an OpenDoc document is opened, OpenDoc creates an instance of the
document shell. The shell creates global objects and uses them to open the
document. OpenDoc then loads the part editors for all parts that appear in the
document window; the part editors read in the data of their own parts. The
shell receives both user events and scripting-related events (see Scripting
Support). The shell uses the OpenDoc dispatcher to dispatch those events to the
proper part editors, based on event location and ownership of shared resources
such as menus.
The document shell is described in The Document Shell. How the dispatcher sends
events to parts is described in How User Events Are Handled.
ΓòÉΓòÉΓòÉ 3.4.2. Handling User Commands ΓòÉΓòÉΓòÉ
As a result of user actions or commands, OpenDoc interacts with part editors to
perform these common application activities:
Part activation
Menu handling
Undo
In general, the document shell receives events and passes the appropriate
information to the proper part.
ΓòÉΓòÉΓòÉ 3.4.2.1. Activation and Selection ΓòÉΓòÉΓòÉ
In response to user actions, individual parts in a document become active and
thus editable. A part is active if it possesses the selection focus; the user
can select and modify its contents in this state. The active part may also
possess the menu and keystroke focus; see Focus Transfer for more information
on foci.
Parts activate themselves in OpenDoc, and the individual parts in a document
must cooperate to transfer the selection focus among each other as appropriate.
Note that when a part is not in the active (editable) state, it need not be
idle; multiple parts within an OpenDoc document can perform different tasks at
the same time.
Switching among individual parts within a document can involve a much less
intrusive context switch than switching from one conventional application to
another. Users are less likely to be irritated, because the wait before they
can edit a newly activated part is not all that perceptible.
The active state is different from the selected state. When a part is selected,
its frame is made available for manipulation. Because embedded frames are
considered to be content elements of their containing part, they can be
selected and then moved, adjusted, cut, or copied just like text, graphic
objects, or any other content elements. Thus, whereas an active part is
manipulated by its own part editor, a selected part is manipulated-as a
frame-by its containing part. The following figure shows the visual differences
among the inactive, selected, and active states of an embedded part.
Note that an inactive part, one that is not being edited, need not have a
visible frame border. A selected part's frame border is drawn by the containing
part; its shape typically corresponds to the frame shape, and its appearance
should follow guidelines for selected frames. An active part's frame border is
drawn by OpenDoc; its shape corresponds to the active shape of the embedded
part's facet, and its appearance is fixed for each platform.
When a part activates, more than its own frame border changes. The following
figure illustrates some of the visual changes to a window caused by part
activation.
In the figure, the text part is active first. OpenDoc has drawn the active
frame border around its frame, it has a highlighted selection, and it displays
its own menus. Then the text part becomes inactive and the graphics part
becomes active. The text part removes its menus and its highlighting. The
graphics part displays its own menus and also displays two palettes (in
separate windows). OpenDoc now draws the active border around the graphics
part's frame.
Activation generally occurs in response to mouse clicks within the area of a
frame (specifically, within the area of a facet's active shape). OpenDoc
follows an inside-out activation model, in which a single mouse click causes
activation of the smallest (actually, the most deeply embedded) enclosing frame
at the pointer location. In the case of a deeply nested embedded frame, as
shown in the following figure, a single click within the frame of the most
deeply embedded part activates that part and allows the user to start editing
immediately.
By contrast, some compound-document architectures use an outside-in activation
mode, in which the user selects the outermost (nonactive) frame with one click
and activates it with the second click. Thus, many clicks might be necessary
for the user to activate a deeply embedded part. In the preceding figure, for
example, the user would have to click four times to start editing.
Despite the advantages of inside-out activation, a containing part may at times
not want an embedded part to be activated when the user clicks within its
frame; it may instead want the part to become selected. For example, when you
want the user to be able to move-but not edit-a part embedded within your part,
you would rather have it selected than activated by a mouse click. OpenDoc
allows you to specify this behavior for an embedded part by placing its frame
in a bundled state. A bundled frame acts like a frame with outside-in
selection, in that a single click selects the frame. (However, unlike with
outside-in selection, subsequent clicks do not then activate the part; you have
to unbundle it before it can be activated).
Your part does not receive window-activation events directly. The OpenDoc
dispatcher notifies your part when its window becomes active or inactive, as
described in Handling Activate Events. It also notifies the containing part,
rather than the embedded part, when an embedded part should become selected.
ΓòÉΓòÉΓòÉ 3.4.2.2. Menus ΓòÉΓòÉΓòÉ
Menu organization and menu handling are different on different platforms.
OpenDoc encapsulates certain aspects of menu behavior in a platform-neutral
menu bar object. That object gives your part editor access to its own and to
OpenDoc's menus.
There are four menus that are available whenever an OpenDoc document is active.
It is recommended that these menus remain unchanged by the part editor, with
the exception of adding more choices. The part is free to put up as many other
menus as desired by the developer.
The four standard menus are:
The Document menu is basic to the OpenDoc user interface. It provides a
replacement for the application-oriented File or equivalent menus. The
contents of the Document menu reflect the OpenDoc document-centered
approach. For example, there is no Quit or Exit command. In OpenDoc, the
user closes a document rather than quits applications.
The Document menu contains choices that affect the top-level part shown
in the window.
The Edit menu is another standard OpenDoc menu that allows the user to
operate on your part or its contents when it is the active part.
This menu contains standard choices on selected parts or selected
intrinsic data. For example, there are options to create a new part of
the same type as the selected part and to undo and redo user actions.
The View menu contains choices that affect the view of the active part.
It also controls the display of related windows, such as tool bars or
palettes. All container parts should support the Icon, Tree, and Details
views.
The Help menu contains choices that provide help on the active part.
In addition to the menu bar, each part has a pop-up menu just like other
objects in the WorkPlace Shell. The only difference between the pop-up menu
and choices in the menu bar menu is that in pop-up menus unavailable choices
are not shown. In menu bar menus, unavailable choices are grayed.
More information on the individual menus is in Menus.
Most of the items in the Document menu are handled by the document shell; see
Handling the Document Menu and Document Menu for more information.
Your part editor can add menus of its own, and it can modify-within strict
limits and according to certain rules-the standard OpenDoc menus.
ΓòÉΓòÉΓòÉ 3.4.2.3. Undo ΓòÉΓòÉΓòÉ
Applications on many platforms provide a form of undo, a capability that allows
a user to reverse the effects of a recently executed command. OpenDoc provides
better support for undo than do most current platforms, in at least two ways:
The undo action can cross document boundaries. This is important because
a single drag-and-drop action can affect more than one part or document.
OpenDoc allows multiple sequential undo actions. The user can undo
multiple sequential commands, rather than only one.
OpenDoc support for undo is described in more detail in Implementing Undo.
ΓòÉΓòÉΓòÉ 3.5. Storage and Data Transfer ΓòÉΓòÉΓòÉ
All the data of all parts in a document, plus all information about frames and
embedding, is stored in a single document file. OpenDoc does not require the
user to manually manage the various file formats that make up a compound
document; OpenDoc manages and holds all the pieces. This makes storage easier
for developers and exchanging documents easier for users.
OpenDoc uses the same data-storage concepts for data transfer (clipboard, drag
and drop, and linking) that it uses for document storage.
ΓòÉΓòÉΓòÉ 3.5.1. Storage Basics ΓòÉΓòÉΓòÉ
The OpenDoc storage system manages persistent storage for parts. It is a
high-level persistent storage mechanism that enables multiple part editors to
share a single document file effectively. The storage system is implemented on
top of the native storage facilities for each platform that supports OpenDoc.
Storage in OpenDoc is based on a system of structured elements, each of which
can contain one or more data streams. The data of each part in a document is
kept in at least one storage unit, distinct from the data of other parts.
Storage units can also include references to other storage units, and OpenDoc
uses chains of such references to store the embedding relationships of the
parts within a document.
Storage units and other elements of structured storage of OpenDoc are described
further in Storage.
ΓòÉΓòÉΓòÉ 3.5.2. Document Drafts ΓòÉΓòÉΓòÉ
OpenDoc documents have a history that can be preserved and inspected through
the mechanism of drafts. A draft is a captured record of the state of a
document at a given time; the user decides when to save the current state of a
document as a new draft and when to delete older drafts. All drafts are stored
together in the same document, with no redundantly stored data.
The OpenDoc draft mechanism helps in the creation of shared documents. When
several users share a document, each in turn can save the current state of the
document as a draft and then make any desired changes. Users can always look
back through the drafts of the document they and others have created. Also, if
translation occurs during the process of sharing documents, the user can
consult an older draft to regain access to formatting information that might
have been lost in translation. The following figure shows an example of a
dialog box through which the user can manipulate drafts.
Parts also interact with their drafts to create the objects needed for
embedding other parts and to create links to data sources. See Drafts for more
information.
ΓòÉΓòÉΓòÉ 3.5.3. Templates ΓòÉΓòÉΓòÉ
OpenDoc gives the user additional aids for constructing compound documents. One
aid, central to the user's ability to create new kinds of parts, is templates.
Templates are specialized parts or documents used in creating other parts. A
template is never opened; when the user attempts to open a template, a copy of
that part is created and opened instead. Users can create templates with
specific formatting and content, to create letterhead, forms, or types of
documents. Template parts can be embedded in documents, or they can exist as
stand-alone documents themselves.
The following figure shows an icon for a template document on the desktop. The
user drags the template icon and drops it onto the document, at which time a
copy of the template part is embedded in the document and opened into a frame,
displaying the part's initial contents.
It is typically through templates that users first gain access to your part
editor. When you develop and ship a part editor, you can also provide one or
more template documents. Part registration creates a default template as well.
To create a part using your part editor, the user does not launch the editor;
instead, the user double-clicks on your template document or drags it into an
open document window.
ΓòÉΓòÉΓòÉ 3.5.4. Data Transfer ΓòÉΓòÉΓòÉ
OpenDoc includes several built-in data-transfer features that allow users to
create and edit OpenDoc documents more easily than is usual with conventional
applications. Users can put any kind of media into a document with simple
commands, and OpenDoc helps your part editor respond to those commands.
In OpenDoc data transfer, the source is the part (or the portion of its
content) that provides the data being transferred, and the destination is the
part (or the location in its content) that receives the transferred data.
When source data in one format is transferred to a destination whose data is
written in another format, translation may be necessary; see Translation.
Translation usually involves loss of data, and is less necessary if part
editors provide standard content formats, as promoted by CI Labs; see
Cross-Platform Consistency and CI Labs.
ΓòÉΓòÉΓòÉ 3.5.4.1. Clipboard ΓòÉΓòÉΓòÉ
Clipboard data transfer allows for easy exchange of information among
documents, using menu commands familiar to most users. OpenDoc supports
clipboard transfer of any kind of data, including multipart compound data, into
any document.
Clipboard transfer is a two-stage process. The user first selects some portion
of the content of the source part (possibly including embedded parts) and
places a copy of that content onto the clipboard buffer by executing the Cut or
Copy command. At any subsequent time, the user can copy the clipboard data to
the destination (back into the same part, into another part in the same
document, or into another document) by executing the Paste command.
As noted in Embedding Versus Incorporating, OpenDoc allows for an intelligent
form of pasting in data transfer, anticipating user expectations about the
result of a pasting operation when the destination holds a different kind of
data from the source. Subject to user override, the destination part can decide
whether to embed the data as a separate part, or incorporate it as content data
into itself.
Clipboard transfer is discussed in detail in Handling Pasted or Dropped Data.
ΓòÉΓòÉΓòÉ 3.5.4.2. Drag and Drop ΓòÉΓòÉΓòÉ
Drag-and-drop data transfer is similar to Clipboard transfer, except that it
involves direct user manipulation of the data being transferred, rather than
the intermediate use of the Clipboard. OpenDoc supports drag and drop within
documents, across documents, and to the desktop. Users can even drop
non-OpenDoc data into OpenDoc parts.
The following figure shows the use of drag and drop to transfer a piece of
information from a spreadsheet part to a text part within a document. As with
Clipboard transfer, OpenDoc uses intelligent pasting in drag and drop; the
spreadsheet part includes a plain-text version of the selection being dragged,
so that the destination part (the text part) can directly incorporate it at the
location of the drop.
Drag and drop works equally well between separate documents. As far as user
experience is concerned, the data-transfer facilities of OpenDoc actually blur
the distinction between a part and a document. If the user drags a closed
document (represented as an icon on the desktop) into an open document window,
a copy of the transferred document either becomes an embedded part in the
window's document or is incorporated into the intrinsic content of the
document's root part. Likewise, if the user selects the frame of an embedded
part in an open document window and drags or otherwise moves that part to the
desktop, it immediately becomes a separate, closed document represented by icon
as shown in the following figure.
Drag and drop is discussed in detail in Drag and Drop.
ΓòÉΓòÉΓòÉ 3.5.4.3. Linking ΓòÉΓòÉΓòÉ
Linking allows the user to view, within a part's frame, data that is a live
(updatable) copy of data in a different location-in the same frame, in a
different frame, in a different part, or even in a different document. When the
data in the source location changes, OpenDoc updates the copy at the
destination, either automatically or manually, depending on user preference.
Linked data can include embedded parts, and the source of a link can be in the
same part as its destination, in a different part in the same document, or in
an entirely different document. The following figure shows a simple example of
linking between two parts in a document. In this example, whenever the user
changes the values in the linked spreadsheet cells, the bar graph adjusts its
display accordingly.
Users create a link when pasting data, either by selecting Paste Link or Paste
As. The Paste As dialog box is used to link the source of the data to its
destination. (See The figure in Handling the Paste As Dialog Box for an example
of the dialog box). This simple user interface to linking makes the use of
links more attractive to users than some other systems do.
Linking is discussed in detail in Linking.
ΓòÉΓòÉΓòÉ 3.6. Extensibility ΓòÉΓòÉΓòÉ
The OpenDoc architecture is designed to be extended. Using built-in features,
you can enhance the capabilities of and communications among your parts in a
compound document, and you can even develop component software that goes well
beyond the standard OpenDoc model of parts and compound documents.
The main point of departure for enhancing OpenDoc is the extension mechanism, a
general method for adding programming interfaces to objects. Additional
mechanisms are the focus-module and dispatch-module interfaces, which allow you
to add new kinds of focus and new kinds of event handling to your part editors.
OpenDoc already includes three instances of extensions: scripting support, the
settings extension, and the view extension. This section introduces the
features of these extensions and summarizes how you can add other extensions
for other purposes.
ΓòÉΓòÉΓòÉ 3.6.1. Scripting Support ΓòÉΓòÉΓòÉ
A major feature available with OpenDoc is the ability to make parts scriptable,
allowing users to customize their applications to user-specific tasks. In a
compound document environment, with scriptable parts, sophisticated users can
use standard document-editing procedures to add application-like functionality
to parts. Likewise, programmers can create complex client applications and
present them in the form of compound documents.
The scripting supported by OpenDoc is called content centered scripting or
scripting based on a content model, which lists the content objects and
operations that the part makes available for scripting. For a part to be
scriptable, its part editor must have a content model. OpenDoc provides a way
to deliver scripting messages, or semantic events, from a scripting system to a
part editor, which responds to events. Object REXX is a scripting language
based on Open Scripting Architecture.
Your part editor can provide increasing levels of scripting support, including
scriptability, recordability, and customizability. They are described in
Semantic Events and Scripting.
When a scripting action involves data transfer, OpenDoc can decide whether to
embed the transferred data as a separate part or translate it and incorporate
it as content data into the destination part. Translation and embedding versus
incorporation are described in Translation.
Scripts can be attached to parts, and a user-interface control such as a button
might consist of nothing more than a part that executes a script when
activated. Script editors also can send semantic events to parts. The following
figure shows an example of a script editor sending a semantic event based on a
scripting command ("Set chartType of part "sales chart" to type3D pie") to a
document. The document shell decodes the object specifier in the event (part
'Sales Chart') and determines the content object that it applies to (in this
case an embedded part; the embedded part then performs the specified operation.
The scripting support in OpenDoc is an enhancement of OpenDoc's basic
capabilities. It is recommended, but not required, that a part editor support
scripting.
ΓòÉΓòÉΓòÉ 3.6.2. Settings Extension ΓòÉΓòÉΓòÉ
The settings extension allows a part to display its properties and settings in
a standard OS/2 notebook. OpenDoc provides Property pages that are added to the
notebook by the part.
ΓòÉΓòÉΓòÉ 3.6.3. View Extension ΓòÉΓòÉΓòÉ
The view extension allows a part to communicate with the Details view of its
container part and supply information for additional data columns in the view
container.
ΓòÉΓòÉΓòÉ 3.6.4. Other Extensions ΓòÉΓòÉΓòÉ
The basic architecture of OpenDoc is primarily concerned with mediating the
geometric interrelationships among parts, their sharing of user events, and
their sharing of storage. Direct communication among parts for purposes other
than frame negotiation is mostly beyond the basic intent of OpenDoc. If
separate parts in a document (or across documents) need to share information,
or if one part needs to manipulate the contents or behavior of another part,
some extension to the capabilities of OpenDoc is needed.
OpenDoc allows you to add these kinds of capabilities to part editors through
its extension mechanism. A part editor can create an associated extension
object that implements an additional interface to that editor's parts. Other
part editors can then access and call that interface.
The OpenDoc support for scripting is an example of an extension interface, as
noted in the previous section. Other extension interfaces can, however, provide
faster interaction and lower-level communication between parts than is possible
by passing semantic events. A word processor, for example, might construct an
extension interface to give related part editors or other tools access to its
text. A developer of a large application package might use extensions to
provide greater integration among its components.
The following figure shows some possible examples of communication through
OpenDoc extensions. It shows a text part and a graphics part that both
communicate with a tool palette (a third part), so that the palette displays
the appropriate tools for the currently active part, and communicates the
user's tool selection back to the active part. A spelling checker provides its
service to all parts in the document and accesses their text through their
extensions. Furthermore, the text part and graphic part communicate directly
through an extension mechanism; the text part updates the graphic part with the
current figure number and caption it has assigned to the figure.
In addition to the scripting extension, OpenDoc includes existing extensions
and other kinds of enhancements that allow you to extend the OpenDoc Properties
notebook, extend the capabilities of the OpenDoc document shell, or provide for
additional kinds of user events or foci. Use of the OpenDoc extension mechanism
is discussed further in Extending OpenDoc.
ΓòÉΓòÉΓòÉ 3.7. Cross-Platform Consistency and CI Labs ΓòÉΓòÉΓòÉ
OpenDoc was designed from the beginning to be a cross-platform architecture.
The programming interface to OpenDoc is general enough that it is readily
implemented on many platforms, adapting well to different user-interface
designs and different run-time models. Although OpenDoc does not provide a
complete programming interface-it does not, for example, replace the graphics
system or drawing commands for any platform-it provides consistent structure
within which such system-specific interfaces can be used. As a result, users on
all platforms obtain a uniform experience in embedding and manipulating all
kinds of media.
Part of this uniformity is built into OpenDoc, and part comes from additional
platform-independent user-interface guidelines that part-editor developers must
follow. Because platform-neutral user-interface guidelines and programming
standards are so important, and because data integrity and data-transfer
protocols are so critical in cross-platform settings, OpenDoc is affiliated
with an organization devoted to solving these issues in a vendor-independent
fashion.
Component Integration Laboratories, Inc. (CI Labs) is a nonprofit association
dedicated to promoting the adoption of OpenDoc as a vendor-neutral industry
standard for software integration and component software. CI Labs is composed
of a number of platform and application vendors with a common interest in
solving OpenDoc issues and promoting interoperability.
Organizations and individuals who want to participate in the move to a
component-software model are invited to join CI Labs. CI Labs supports several
levels of participation through different membership categories. Specific
membership benefits vary by category, but all members influence the future
direction of OpenDoc technology.
We encourage you to add your name to one of our electronic mail information
lists at CILABS.ORG, download files from our server at FTP.CILABS.ORG, or look
up our Web page (http://www.cilabs.org). If for some reason you are unable to
get files from our server, we can send you an information packet. For details
on membership levels and how you can become a member, please provide the
following information:
Name
Company Name
Title
Street Address
City
State/Province
Zip Code/Postal Code/Country
Telephone Number
Fax Number
e-mail address
You can send it through CILABS@CILABS.ORG or use our US mail address:
Component Integration Laboratories, Inc.
PO Box 61747
Sunnyvale, CA 94088-1747
Telephone (408) 864-0300
FAX (408) 864-0380
Internet CILABS@CILABS.ORG
World Wide Web http://www.cilabs.org
CI Labs is responsible for the development and maintenance of a number of
component software packages, including the following:
OpenDoc, the platform-independent class library and compound document
architecture that is described in this book.
Bento, a class library and format for persistent object storage. Bento
allows OpenDoc to store and exchange compound documents and multimedia.
Open Scripting Architecture (OSA), a scripting and program-automation
standard and interface that supports application-independent user
scripting on multiple platforms. OSA is not a scripting language; it is a
messaging specification and library upon which scripting languages are
built.
System Object Model (SOM), an efficient and powerful mechanism and set of
libraries for dynamic linking of objects. SOM underlies OpenDoc's dynamic
linking capability, and its language-neutral interface compiler permits
development in multiple languages, object-oriented as well as procedural,
even within a single software component.
Component Glue, an interface and library that enables seamless
interoperability between OpenDoc and Microsoft Corporation's Object
Linking and Embedding (OLE) technology for interapplication
communication.
CI Labs oversees the development and distribution of these technologies. In
the future, CI Labs also expects to coordinate the following activities for
developers of OpenDoc part editors:
Certification of part editors for consistency with OpenDoc standards and
for safe use in any OpenDoc document
Promotion of standard formats for exporting different kinds of document
content
Coordination of standards for extending the capabilities of OpenDoc, by
defining specific sets of OpenDoc extension interfaces
ΓòÉΓòÉΓòÉ 4. Development Overview ΓòÉΓòÉΓòÉ
Creating OpenDoc software is not difficult, but it represents a shift in
approach from conventional application development. OpenDoc part editors differ
from conventional applications in that:
They are generally smaller than conventional applications.
They do not have direct access to some operating-system services such as
event dispatching.
They do not own their own documents.
They must work in close cooperation with other part editors.
The cross-platform design of OpenDoc means that some aspects of part
development may seem foreign to developers, but it also means that writing
part editors for multiple platforms is far easier than doing so for
conventional applications.
OpenDoc is an object-oriented system, but a part editor can be written in
procedural code and still fit into the OpenDoc class structure. Existing
applications, object-oriented or not, can be retrofitted easily to work in the
OpenDoc environment. OpenDoc is extensible, and many of its components are
replaceable, allowing for innovation by developers at both the system and
application levels.
This chapter introduces the OpenDoc class library, and presents a high-level
summary of several approaches to developing an OpenDoc part editor. For
additional discussions of general development-related issues, see also OpenDoc
Run-Time Features.
ΓòÉΓòÉΓòÉ 4.1. The OpenDoc Class Library ΓòÉΓòÉΓòÉ
OpenDoc is a set of DLLs with a largely platform-independent, object-oriented
programming interface. This section introduces the classes implemented in the
OpenDoc libraries and the design goals behind them.
The interfaces to all of OpenDoc's classes are specified in the Interface
Definition Language (IDL), a programming-language-neutral syntax for creating
interfaces. The interfaces are compiled separately from implementation code
using the compiler for the System Object Model (SOM), a specification for
object binding at runtime. See Developing with SOM and IDL for more
information.
Because OpenDoc uses IDL and SOM, part editors and other OpenDoc objects that
have been created with different compilers or in completely different
programming languages can nevertheless communicate properly with each other.
Furthermore, they can be independently revised and extended and still work
together.
For more complete information on the OpenDoc class library, including detailed
descriptions of all public OpenDoc classes and methods, see the OpenDoc for
OS/2 Programming Reference.
ΓòÉΓòÉΓòÉ 4.1.1. A Set of Classes, Not a Framework ΓòÉΓòÉΓòÉ
OpenDoc is a set of classes whose objects cooperate in the creation and
manipulation of compound documents. It is designed to be as platform-neutral as
possible. The object-oriented library structure, in which object fields are
private, facilitates the replacement of existing code in a modular manner.
Also, by using abstract classes and pure virtual methods, it defines a
structure for part editors while specifying as little as possible about their
internal functioning.
The OpenDoc class library is not an object-oriented application framework in
the full sense of the word. That is, even though the design of OpenDoc allows
you to create a part editor, it does so at a lower level, and without forcing
as much adherence to interface and implementation as is typical for an
application framework.
You can create a part editor just as effectively in either way: directly to the
OpenDoc library interfaces or indirectly through the interfaces of a framework.
The main difference is that if you use a part-editor framework, you have less
code to actually implement yourself, and it is easier to ensure consistency in
the final product.
The principal classes in the OpenDoc class hierarchy are shown in the following
figure. All classes are derived from the superclass ODObject, itself a subclass
of somObject, the fundamental SOM superclass (not shown). Classes whose names
are in bold are abstract superclasses that your part editor is likely to
subclass. The other principal classes implement the basic capabilities of
OpenDoc; classes typically instantiated by your part (if it uses them) are in
italics, and classes instantiated only by OpenDoc are in plain text.
Runtime relationships
For an illustrated discussion of the relationships of the principal OpenDoc
objects in terms of runtime references among them, see Run-Time Object
Relationships.
Classes shown in the following figure are support classes, consisting mostly of
iterators and simple sets. They are all direct subclasses of ODObject. (A
separate set of specialized OpenDoc support classes, used solely for scripting,
is shown in the figure in Scripting-Related Objects).
Compared to some application frameworks, there is little inheritance in the
hierarchy represented in the preceding figures; OpenDoc instead makes extensive
use of object delegation, in which objects unrelated by inheritance cooperate
to perform a task. This inheritance structure preserves the language-neutral
flavor of OpenDoc and improves ease of maintenance and replaceability.
The OpenDoc classes can be divided into these three groups, based on how a part
editor might make use of them:
The class (ODPart) that you must subclass and implement to create your
part editor
The bulk of the implemented OpenDoc classes, instantiated either by your
part editor or by OpenDoc, that your part editor calls to perform its
tasks
The classes you can subclass and implement to extend OpenDoc
The following sections summarize these groups of classes.
ΓòÉΓòÉΓòÉ 4.1.2. Classes you Must Subclass ΓòÉΓòÉΓòÉ
The OpenDoc classes listed in this section are abstract superclasses that you
are intended to subclass if you wish to implement their capabilities.
ΓòÉΓòÉΓòÉ 4.1.2.1. The Class ODPart ΓòÉΓòÉΓòÉ
The class ODPart is central to OpenDoc; it is the one class that you must
subclass to create a part editor. ODPart represents the programming interface
that your part editor presents to OpenDoc and to other parts.
ODPart is an abstract superclass with approximately 65 defined methods. When
you subclass ODPart, you must override all methods, but you need to provide
meaningful implementations only for those methods that represent capabilities
actually supported by your part editor. The rest can be stub implementations.
There is one additional class that you must subclass and implement if your part
is a container part. You must provide an iterator class (a subclass of the
abstract superclass ODEmbeddedFramesIterator) to allow callers to access all of
the frames directly embedded in your part.
ΓòÉΓòÉΓòÉ 4.1.3. Classes for Extending OpenDoc ΓòÉΓòÉΓòÉ
OpenDoc provides these classes specifically for enhancing its features:
The class ODExtension is the abstract superclass from which extensions to
OpenDoc are defined; OpenDoc allows you to add new methods to existing
classes by associating objects of class ODExtension with the specific
class that they extend. The classes ODSemanticInterface and
ODSettingsExtension are examples of currently existing subclasses of
ODExtension.
The class ODSemanticInterface, when subclassed, represents the interface
through which a part receives semantic events, thus allowing it to be
scriptable.
The class ODSettingsExtension, when subclassed, represents an object with
which your part editor can create and display a Settings dialog box for
your part editor.
The class ODViewExtension, when subclassed, represents the interface
through which a part can add columns to a details view.
The class ODDispatchModule is used to dispatch certain types of events
(such as keystroke events) to part editors. You can provide for
dispatching of new types of events or messages to your part editor by
subclassing ODDispatchModule.
The class ODFocusModule is used to describe a particular type of focus
(such as the selection focus). You can provide new types of focus for
your part editor by subclassing ODFocusModule.
The class ODTransform represents a graphical transformation matrix used
for drawing. It is somewhat different from the other classes in this
category, in that it can be used as-is (which is why it also appears in
the next section) and it can also be subclassed to extend the kinds of
transformations it is capable of.
ΓòÉΓòÉΓòÉ 4.1.4. Classes you Can Use ΓòÉΓòÉΓòÉ
The OpenDoc classes listed in this section implement most of the OpenDoc
features that your part editor uses. By using or instantiating objects of these
classes, your part can function within an OpenDoc document and can embed other
parts.
ΓòÉΓòÉΓòÉ 4.1.4.1. Abstract Superclasses ΓòÉΓòÉΓòÉ
These classes are never directly instantiated. The classes ODPart and
ODEmbeddedFramesIterator, mentioned in the previous section, are abstract
superclasses. Other abstract superclasses are mentioned in Classes for
Extending OpenDoc.
The abstract superclasses in the following list are special; they define the
basic inheritance architecture of OpenDoc. Not only would you not instantiate
these classes, but you would probably never directly subclass them; in
developing a part editor, you would use (or further subclass) one of their
existing subclasses.
ODObject. This is the abstract superclass for most of the principal
OpenDoc classes. All subclasses of ODObject define objects that are
extensible.
ODRefCntObject. This is the abstract superclass for reference-counted
objects-objects that maintain a count of how many other objects refer to
them so that OpenDoc can manage memory use efficiently.
ODPersistentObject. This is the abstract superclass for persistent
objects-objects whose state can be stored persistently.
ΓòÉΓòÉΓòÉ 4.1.4.2. Implemented Classes ΓòÉΓòÉΓòÉ
These classes are implemented in ways unique to each platform that supports
OpenDoc. Some are instantiated only by OpenDoc, whereas others may also be
instantiated by your part editor.
The session object. The class ODSession represents the user opening and
accessing a single OpenDoc document.
There is only one ODSession object. Part editors and other OpenDoc
objects cache a reference to the single ODSession object for performance
reasons. The OpenDoc run time calls ODSession to instantiate all of the
OpenDoc library objects (such as ODDispatcher, ODWindowState, and
ODArbitrator) and initialize them. ODSession also keeps internal
references to these OpenDoc objects, so it is a handy thing to use to
access other objects.
The binding object. The class ODBinding represents the object that
performs the binding of part editors to the parts in a document.
Storage classes. The primary classes of objects associated with document
storage are the class ODStorageSystem and the set of classes
(ODContainer, ODDocument, ODDraft, and ODStorageUnit) that constitute a
container suite. The container-suite objects all work closely together
and are implemented differently for each platform or file system.
Data-interchange classes. The classes ODLink, ODLinkSource, ODLinkSpec,
ODDragAndDrop, and ODClipboard all relate to transfer of data from one
location to another. These objects do not represent documents, but they
nevertheless use storage units to hold the data they transfer.
Drawing-related classes. The classes ODCanvas, ODShape, and ODTransform
represent imaging structures in a given graphics system. The classes
ODWindowState and ODWindow represent windows on a given platform. The
classes ODFrame and ODFacet represent the frame and facet structures that
define the layout of embedded parts.
Event-handling classes. The classes ODArbitrator and ODDispatcher control
what kinds of user events are sent to which part editors during
execution. The classes ODMenuBar, ODPopupMenu, ODHelp, ODStatusLine, and
ODUndo give part editors access to the user interface.
Semantic-event classes. Besides the abstract class ODSemanticInterface,
the classes ODNameResolver and ODMessageInterface are associated with
sending or receiving semantic events (scripting messages), and connecting
to a scripting system. The first figure in A Set of Classes, Not a
Framework does not show the entire object hierarchy involved with
semantic events and scripting; see the first figure in A Set of Classes,
Not a Framework for a more complete picture.
ΓòÉΓòÉΓòÉ 4.1.4.3. Service Classes ΓòÉΓòÉΓòÉ
These classes exist mainly as services for other classes to use:
The ODStorageUnitCursor and ODStorageUnitView classes facilitate access
to specific values in specific storage units.
The ODFocusSet class provides convenient grouping of foci (access to
shared resources such as the keyboard or menu bar) for activation and
event handling.
The ODNameSpaceManager and ODNameSpace classes provide convenient storage
for attribute/value pairs. The classes ODObjectNameSpace and
ODValueNameSpace, subclasses of ODNameSpace, provide, respectively, name
spaces for OpenDoc objects and for data.
The ODTranslation class provides platform-specific translation between
data formats.
The object descriptor classes (shown in the figure in Scripting-Related
Objects) provide scripting support in the form of an object-oriented
encapsulation of OSA event descriptor structures.
Many classes have associated iterator classes and list classes that are
used for counting through all related instances of the class.
ΓòÉΓòÉΓòÉ 4.2. Writing OpenDoc Software ΓòÉΓòÉΓòÉ
This section briefly summarizes some high-level aspects of the design and
implementation of a part editor. It discusses:
Issues related to developing with the System Object Model that underlies
all OpenDoc classes
Protocols that your part can participate in to accomplish its tasks
Several development scenarios for creating OpenDoc software
ΓòÉΓòÉΓòÉ 4.2.1. Developing with SOM and IDL ΓòÉΓòÉΓòÉ
OpenDoc is implemented as a shared library consisting of a set of SOM classes.
The interfaces to SOM classes must be written in the SOM Interface Description
Language (IDL) and compiled by the SOM compiler, separately from the
implementations of the classes.
The implementation of OpenDoc objects as SOM objects has several advantages to
its use as a shared library:
SOM objects are dynamically bound. Dynamic binding is essential to the
nature of OpenDoc, in which new parts can be added to existing documents
at any time.
All SOM objects, whether their implementations were compiled under
different compilers in the same programming language or in different
languages, communicate consistently and pass parameters consistently.
Unlike with other object-oriented architectures, the modification and
recompilation of a SOM class do not necessarily require the subsequent
recompilation of all of its subclasses and clients.
Because OpenDoc consists of SOM classes, the class ODPart is naturally a SOM
class. If you want your part editor, which is a subclass of ODPart plus any
other classes that you define, to also consist of SOM classes, then you must
write your interfaces in IDL, separate from your implementations, and you must
compile them with the SOM compiler. The result of the compilation is a set of
header and stub implementation source files, in one of the procedural or
object-oriented programming languages supported by the SOM compiler. You
complete your development by writing your implementations into the stub
implementation files and compiling them, along with the headers, using a
standard compiler for your programming language.
If you write your part-editor interfaces in IDL, you will notice that the IDL
syntax is very similar to that of C and C++. It includes essentially the same
character set, white space rules, comment styles, preprocessing capabilities,
identifier-naming rules, and rules for literals. But there are a few notable
differences in source-code appearance when declaring or calling methods of
SOM-based objects:
In IDL method declarations (function definitions), each parameter
declaration is preceded by a directional attribute ("in", "out", or
"inout") that notes whether the parameter is used as an input, or as a
result, or as both. (For example, void FacetAdded (in ODFacet facet);)
The C++ interface to any method of a SOM object includes an extra initial
parameter, the environment parameter (ev), used by all SOM methods to
pass exceptions. See SOM Exception Handling for more information.
The C interface to any SOM method includes another extra parameter,
before the environment parameter, specifying the object to which the
method call is directed.
Advantages of making all your classes SOM classes include a greater ability to
develop portions of your part editor (or set of editors) using different
programming languages and compilers, and a lesser need for recompilation of
all of your code when you change portions of it. These advantages may not be
compelling, however, unless your libraries are very large, especially since
the advantages must be balanced against the disadvantages of working in both
IDL and a separate programming language.
You are not required to make your part-editor classes SOM classes. If you are
developing in C++, for example, you can use C++ classes instead. The generally
preferred procedure is to create only one SOM class, a subclass of ODPart
whose interface contains nothing but your public methods (overrides of the
methods of ODPart and its superclasses). That SOM class delegates all of its
method calls to a C++ wrapper class, which contains the functionality for the
public methods as well as any private fields and methods. Additional classes
can be subclasses of your C++ wrapper class.
Advantages of developing in C++ with a single wrapper object include a lesser
need to work with interfaces in two languages (IDL and C++), a smaller memory
and calling overhead for your objects, and the availability of C++ features
(such as templates) that are not supported by SOM.
ΓòÉΓòÉΓòÉ 4.2.1.1. SOM Class ID and Editor ID ΓòÉΓòÉΓòÉ
A SOM class ID is an ISO string whose format is "module::className". A part
editor's editor ID is a SOM class ID that uniquely defines the editor; you need
to specify your editor ID in your editor's IDL interfaces. For example, the
editor ID for AcmeGraph 1.0 might be "Acme::AcmeGraph". Editor IDs are used for
binding; see Information Used for Binding for more information.
For a more detailed description of the Interface Definition Language and
instructions on programming with SOM, see, for example, the SOMObjects
Developer Toolkit Users Guide and SOMObjects Developer Toolkit Programmers
Reference Manual.
ΓòÉΓòÉΓòÉ 4.2.1.2. Direct-To-Som (DTS) ΓòÉΓòÉΓòÉ
SOM defines bindings for C and C++ languages. These bindings consist of a
number of macros plus structure or class definitions in header files with the
extensions .h and .ih (for C) and .xh and .xih (for C++). They are generated
for a particular SOM class by running the SOM compiler on the .idl file for
that class interface. The bindings can be used with a wide range of C and C++
compilers and do not require special compiler support.
Direct-To-SOM (DTS) is a new and much more flexible way of using SOM in a C++
program. DTS class definitions resemble regular C++ classes, and you can either
write them directly or use the SOM compiler to generate them into .hh files
from existing IDL. DTS C++ class definitions can only be used with C++
compilers, such as VisualAge C++, that support DTS.
DTS provides the same access to SOM functionality that the C++ bindings do but,
in addition, DTS supports far more of the C++ language. DTS supports:
Member operators
Conversion functions
User-defined new and delete operators
Function overloading
Stack local SOM objects
First-class source debugging support for SOM classes
You can write and subclass your DTS classes directly and may never need to
write any IDL.
For more information about DTS, refer to your compiler programming guide.
ΓòÉΓòÉΓòÉ 4.2.2. OpenDoc Protocols ΓòÉΓòÉΓòÉ
OpenDoc imposes very few restrictions on how your part editor does its job. The
inner workings of your editor's core data engine are of little concern to
OpenDoc. The engine should be able to free storage when requested, it should
adequately handle cases where the part can be only partially read into memory,
and it should handle any multiprocessing or multithreading issues that arise.
Other than that, it simply needs to provide an interface to OpenDoc and use the
OpenDoc interface to accomplish OpenDoc-related tasks.
The programming interfaces that your part editor uses (and provides) to perform
specific tasks are called protocols. The methods of ODPart, for example,
participate in approximately 12 protocols, such as part activation and undo.
This section briefly describes the protocols; and the next table lists the
methods of ODPart that you will have to override to participate in each
protocol.
ΓòÉΓòÉΓòÉ 4.2.2.1. Which Protocols To Participate In ΓòÉΓòÉΓòÉ
To implement the simplest possible part, you need to participate in only some
OpenDoc protocols, and you need to override only some methods of ODPart. As a
minimum, your part editor must be able to:
Draw its part
Retrieve its part's data
Handle events sent to its part
Unless it creates extremely simple parts, your part editor must also provide
some sort of command interface to the user. It must then be able to
Activate its part
Handle menus, pop-ups, status lines, and help
Handle windows, dialog boxes, and palettes
If you wish your parts to be able to contain other parts, your part editor
must be able to
Embed frames and manipulate them
Create facets for visible frames
Store frames
Beyond these capabilities, you may want your parts to have additional
capabilities, such as drawing themselves asynchronously, supporting data
transfer such as drag and drop, supporting scripting, or others. You can add
those capabilities by overriding other methods of ODPart.
ΓòÉΓòÉΓòÉ 4.2.2.2. Overriding the Methods of ODPart ΓòÉΓòÉΓòÉ
Your fundamental programming task in creating an OpenDoc part editor is to
subclass the class ODPart and override its methods. The following list
summarizes the OpenDoc protocols that your part editor can use, and lists the
sections in this book that describe each protocol more fully. To create
full-featured container parts, your editor must support all of these protocols.
The methods of ODPart involved in each protocol are shown in the table that
follows this list.
Layout. The layout and imaging protocols together make up the drawing
process. Your part uses the layout protocol for frame manipulation and
for facet manipulation.
- Frame manipulation includes adding and removing display frames,
setting frame characteristics and order, opening frames into
windows, and performing frame negotiation (modifying embedded-frame
shapes on request). Your part interacts with its containing part and
with its draft object to modify your display frames.
- Facet manipulation is the adding, altering, or removing of facets of
frames that are modified or scrolled into or out of view. You
interact with existing facet objects to add or manipulate embedded
facets. OpenDoc uses the facet hierarchy constructed by the layout
protocol for passing geometric events like mouse clicks to the
appropriate part editor.
All parts that are visible must participate in this protocol. The layout
protocol is described, along with the embedding protocol, in Frame and
Facet Hierarchies.
Imaging. Your part uses the imaging protocol after the layout protocol,
to draw itself appropriately in each of its display frames. Drawing may
occur asynchronously, or in response to update events, or when activation
or deactivation affects highlighting. Drawing can be to a video display
or to a printer. You interact with frames, facets, canvases, transforms,
and shapes during the imaging process.
All parts that are visible must participate in this protocol. The imaging
protocol is described in Canvases, Transforms and Shapes, Drawing, and
Printing.
Activation. Through this protocol your part activates and deactivates
itself (and its frames and facets). Your part interacts with the
arbitrator to change the menu and keystroke focus, and to notify the
parts and frames involved of the changes.
Your part must participate in this protocol if it ever needs to be active
or if it needs any of the other focus types. The activation protocol is
described in Focus Transfer.
User events. OpenDoc uses this protocol to distribute events such as
keystrokes and mouse clicks to the appropriate part editors. The document
shell, the arbitrator, and the dispatcher cooperate to deliver these
events to your part.
Your part must participate in this protocol if it handles events. The
user-events protocol is introduced in About Event Handling in OpenDoc and
is described in more detail in other parts of User Events.
Storage. Your part uses the storage protocol to write its content and its
state information persistently in its document, and subsequently to
retrieve it from the document. You interact with your draft object and
your part's storage unit when reading and writing your part, and you may
also interact with other drafts and storage units when using the
clipboard, drag-and-drop, and linking protocols.
All parts must participate in this protocol. The part-storage protocol is
described in Storage Units, Properties, and Values and Documents, Drafts,
and Parts.
Binding. The OpenDoc document shell controls the runtime binding of your
part editor to the parts in a document that it can edit. Your part's
methods are not called during binding. The binding protocol is discussed
in Binding.
Embedding. Your part uses this protocol to embed other parts within
itself and to interact with those parts.
Your part participates in this protocol if it is a container part-that
is, if it is capable of embedding parts within itself. The embedding
protocol is described in Frame and Facet Hierarchies.
Clipboard. The clipboard protocol allows the user to use menu commands to
move content elements and embedded parts into or out of your part, from
or to a system-maintained buffer. Your part interacts with the clipboard
object both when copying to the clipboard and when pasting from it.
Your part must participate in this protocol if it supports clipboard
transfer. The clipboard protocol is described in Clipboard Transfer.
Drag and drop. The drag-and-drop protocol allows the use of direct
manipulation to move content elements and embedded parts into or out of
your part or within your part. Your part interacts with the drag-and-drop
object.
Your part must participate in this protocol if it supports drag and drop.
The drag-and-drop protocol is described in Drag and Drop.
Linking. Your part can use the linking protocol to export updatable data
to another part or to incorporate or embed an updatable copy of another
part's data into your part. If your part is the source of a link, you
interact with a link-source object; if your part is the destination of a
link, you interact with a link object.
Your part must participate in this protocol if it supports linking. The
linking protocol is described in Linking.
Undo. Your part uses this protocol to give the user the capability of
reversing the effects of recently executed commands. Your part interacts
with the undo object to accomplish this.
Your part must participate in this protocol if it has an undo capability.
The undo protocol is described in Undo.
Extensions. The extension protocol is a very general mechanism for
extending your part's capabilities; it consists of an interface in the
form of a specialized extension object that other part editors can
access.
Your part participates in this protocol if it provides OpenDoc
extensions. The extensions protocol is described in OpenDoc Extension
Interface.
Semantic events. Your part uses the semantic events protocol to make
itself scriptable. It interacts with the semantic interface object when
receiving scripting messages (semantic events); it interacts with the
message interface object when sending scripting messages.
Your part must participate in this protocol if it is scriptable. The
semantic events protocol is described in Scripting and OpenDoc.
Memory management. The OpenDoc document shell manages the memory needed
for the document containing your part.
In general, your part editor does not need to be concerned with memory
management except to make sure that it deletes or releases objects that
it has created or obtained. The memory-management protocol and the use of
reference counting is further discussed in Creating and Releasing
Objects.
the table in OpenDoc Protocols lists the methods of ODPart that you must
override to have a functioning part editor, as well as those that you can
optionally override to participate in specific protocols. Note that some
protocols, such as layout, imaging, and activation, are required of all part
editors, and you therefore must override some or all of the methods associated
with them. Other protocols, such as embedding or undo, are not required, and
you therefore need not override any of their methods if your parts do not
participate. It is, of course, strongly recommended that your parts
participate in all protocols.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéProtocol ΓöéRequired overrides ΓöéOptional overrides Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLayout ΓöéAttachSourceFrame ΓöéAcquireContainingPartProperties* Γöé
Γöé ΓöéContainingPartPropertiesUpdated ΓöéRevealFrame* Γöé
Γöé ΓöéDisplayFrameAdded Γöé Γöé
Γöé ΓöéDisplayFrameClosed Γöé Γöé
Γöé ΓöéDisplayFrameConnected Γöé Γöé
Γöé ΓöéDisplayFrameRemoved FacetAdded Γöé Γöé
Γöé ΓöéFacetRemoved FrameShapeChanged Γöé Γöé
Γöé ΓöéGeometryChanged Open Γöé Γöé
Γöé ΓöéSequenceChanged Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéImaging ΓöéCanvasChanged Draw ΓöéAdjustBorderShape* CanvasUpdated* Γöé
Γöé ΓöéGetPrintResolution HighlightChangedΓöé Γöé
Γöé ΓöéPresentationChanged ViewTypeChangedΓöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéActivation ΓöéAbortRelinquishFocus Γöé Γöé
Γöé ΓöéBeginRelinquishFocus Γöé Γöé
Γöé ΓöéCommitRelinquishFocus FocusAcquiredΓöé Γöé
Γöé ΓöéFocusLost Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéUser events ΓöéAdjustMenus HandleEvent ΓöéCreateRootMenuBar Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéStorage ΓöéCloneInto** ClonePartInfo ΓöésomInit** somUninit** Γöé
Γöé ΓöéExternalize** ExternalizeKinds Γöé Γöé
Γöé ΓöéInitPart InitPartFromStorage Γöé Γöé
Γöé ΓöéReadPartInfo WritePartInfo Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéBinding ΓöéCangeKind Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéMemory ΓöéReleaseAll** ΓöéAcquire** Purge** Release** Γöé
ΓöéManagement Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLinking ΓöéLinkStatusChanged ΓöéBreakLink BreakLinkSource Γöé
Γöé Γöé ΓöéCreateLink EditInLinkAttempted* Γöé
Γöé Γöé ΓöéFulfillPromise LinkBroken Γöé
Γöé Γöé ΓöéLinkUpdated RevealLink ShowLink Γöé
Γöé Γöé ΓöéUpdateFromLinkSource Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéEmbedding Γöé ΓöéCreateEmbeddedFramesIterator* Γöé
Γöé Γöé ΓöéEmbeddedFrameUpdated* Γöé
Γöé Γöé ΓöéRemoveEmbeddedFrame* Γöé
Γöé Γöé ΓöéRequestEmbeddedFrame* Γöé
Γöé Γöé ΓöéRequestFrameShape* Γöé
Γöé Γöé ΓöéUsedShapeChanged* Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéClipboard Γöé ΓöéFulfillPromise Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDrag and Drop Γöé ΓöéDragEnter DragLeave DragWithin DropΓöé
Γöé Γöé ΓöéDropCompleted FulfillPromise Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéUndo Γöé ΓöéDisposeActionState ReadActionState Γöé
Γöé Γöé ΓöéRedoAction UndoAction Γöé
Γöé Γöé ΓöéWriteActionState Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéExtensions Γöé ΓöéAcquireExtension** HasExtension** Γöé
Γöé Γöé ΓöéReleaseExtension** Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéSemantic EventsΓöé ΓöéEmbeddedFrameSpec* Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
* Required of all parts that support embedding
** Defined in a superclass of ODPart
ΓòÉΓòÉΓòÉ 4.2.3. Development Scenarios ΓòÉΓòÉΓòÉ
This section provides a high-level discussion of several possible OpenDoc
development scenarios. Reading the scenarios may help you to decide what kinds
of OpenDoc component software you are most interested in developing.
Specifically, it discusses
Creating a noncontainer part editor
Creating a container part editor
Converting a conventional application into a part editor
Developing OpenDoc components that are not part editors
ΓòÉΓòÉΓòÉ 4.2.3.1. Writing an Editor for NonContainer Parts ΓòÉΓòÉΓòÉ
Writing a part editor that does not support embedding is somewhat simpler than
writing an editor that does. Furthermore, if you are starting from scratch (not
modifying an existing application), you are free to consider all aspects of the
design of your part editor before you implement anything.
Content model. If your parts are to be scriptable (see the Scripting
step), you must create a content model and construct semantic events that
allow a full range of user operations to be performed on the parts'
content objects.
Core engine. Design and implement your core data engine, the set of data
structures and behaviors that manifest the basic purpose of your editor.
Storage. Implement persistent storage in these situations:
- Use the OpenDoc storage system to store your part editor's data.
Your part editor must implement code to initialize a part and to
write it back to storage.
- Implement clipboard and drag-and-drop capabilities to transfer
information between parts, using the same OpenDoc storage concepts
for data transfer as for persistent storage.
- Implement linking support, using a combination of event-handling
code and storage code (similar to support for document storage,
clipboard, and drag and drop).
Drawing. Give your part the capability of drawing its content, properly
transformed and clipped, in whatever combination of facets and frames the
content appears, onscreen or offscreen, and for screen display or for
printing.
Event handling. Give your part editor the capability of responding to
user events such as mouse clicks and keystrokes. It must also respond
properly to activation and deactivation events and must use the OpenDoc
user interface objects such as members, pop-ups, help, and status line.
Scripting. To support scripting, you must first define the content model
of your parts, as noted earlier (in the Content Model step). Then you
must implement accessory functions to resolve object specifiers (external
references to a part's content objects) as well as semantic-event
handlers to perform your part's content operations.
Extensions. If you plan to extend the capabilities of your parts to
communicate with other parts or process information fast, create and
attach an extension interface to your part editor. To obtain existing
public extension interface designs or to propose a new public interface,
contact CI Labs.
Packaging and shipping. Once your part editor is complete, provide
information for the OpenDoc binding process to use, indicating what part
kinds are handled and what semantic events are handled.
You ship your product as one or more part editors, plus an install program and
accompanying documentation. Users install your part editor into their systems
and then, using the templates that get control at part registration time,
create new documents of your part kind or insert new parts with your part kind
into their documents. Rules and conventions for installing part editors and
storing documents vary among platforms. For OS/2 platform rules, see the
recipe Installation of OpenDoc Software.
Users themselves can create additional, customized template documents. Users
should be able to exchange documents, including templates, freely.
ΓòÉΓòÉΓòÉ 4.2.3.2. Writing an Editor for Container Parts ΓòÉΓòÉΓòÉ
If your part editor is to support embedding-that is, if its parts are to be
container parts-you need to include the following additional steps:
Embedding. You need to add embedding support to your content model and to
storage.
- Your content model needs to include a type of content element that
represents an embedded part. If your part editor supports semantic
events, embedded parts must be content objects to which you can pass
events.
- Make sure your parts can store embedded parts, both in their
documents and during data transfer. This is relatively simple to
implement; OpenDoc takes care of most of it.
Layout management. You need to add support for layout management during
event handling and during frame negotiation.
- Your part editor must be able to maintain updated information on
embedded frames and facets and notify embedded parts of such
changes. It must add facets when embedded frames become visible. It
must modify or delete facets when embedded frames move or become no
longer visible.
- Your part editor must include support for layout negotiation,
including updating the shapes and transforms associated with each
visible embedded frame and facet.
For a summary of the issues to consider in creating a part editor for
container parts, see Embedding Checklist.
ΓòÉΓòÉΓòÉ 4.2.3.3. Converting a Conventional Application to a Part Editor ΓòÉΓòÉΓòÉ
Creating a part editor from an existing conventional application involves
maintaining its core features but repackaging them for the OpenDoc environment.
Content model and core data engine. You should have your content model
and core data engine already in place. You may need to separate your core
data engine from other facilities, such as user interface and storage, if
it is not already sufficiently separated.
Storage. You must refit all file I/O and clipboard data transfers into
OpenDoc terms, as described for part editors in the Storage step in
Writing an Editor for NonContainer Parts.
Event handling. Because your part editor will receive its event
information from the document shell, you need to remove your event loop
and event-handling code.
Scripting. You can add scripting support, as described for part editors
in the Scripting step in Writing an Editor for NonContainer Parts.
Extensions. You can extend the capabilities of your parts, as described
for part editors in the Extensions step in Writing an Editor for
NonContainer Parts.
Packaging and shipping. Package and ship your part editor, as described
for part editors in the Packaging and Shipping step in Writing an Editor
for NonContainer Parts.
If your converted application is to be a container part, you need to follow
these additional steps:
Embedding. Add embedding support, as described for part editors in the
Embedding step in Writing an Editor for Container Parts.
Layout management. Add layout-management code, as described for part
editors in the Layout Management step in Writing an Editor for Container
Parts.
ΓòÉΓòÉΓòÉ 4.2.3.4. Writing Other Types of Component Software ΓòÉΓòÉΓòÉ
This book primarily describes how to create a part editor. However, you are not
limited to that alone. Development of container applications is discussed
briefly in the previous section. Using the OpenDoc class libraries, you can
also create other kinds of OpenDoc component software:
Part viewers are simply part editors with their editing and saving
capabilities removed. A part editor should be able to read, display, and
print its parts.
Development issues for part viewers are a subset of those for fully
functional part editors. A part viewer is usually much smaller than its
corresponding editor, and it can be developed from the same code base.
To encourage data interchange, you should always create a part viewer for
every part editor you develop, and you should distribute the viewer
widely and without cost or obligation to the user.
Part extensions are objects with which you can extend the programming
interface of your part editor. Extensions work in conjunction with part
editors, allowing their parts to communicate with and directly manipulate
other parts. Extensions are described in Extending OpenDoc.
Document-shell plug-ins are extensions to the capabilities of the shell.
They are DLLs that you can write and install. Shell plug-ins are
described in Shell Plug-Ins.
Part or document services are OpenDoc components that, instead of editing
and saving parts in a document, provide specialized services to those
parts and documents. Spelling checkers, database-retrieval engines, and
network connection services are all examples.
You develop an OpenDoc service much as you develop a part editor. Like
part editors, services are subclasses of ODPart. However, services
commonly use less of the embedding, layout, and imaging protocols of
OpenDoc, and they usually communicate with the parts they serve through
an extension interface (a subclass of ODExtension). The extension
interface is described in Extending OpenDoc.
ΓòÉΓòÉΓòÉ 5. Layout and Embedding ΓòÉΓòÉΓòÉ
This is the first of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter focuses on the key concepts of how parts are
embedded in documents and how embedded parts communicate with their containing
parts to lay themselves out for display.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. For additional concepts related to
your part editor's runtime environment see OpenDoc Run-Time Features.
This chapter starts with a general discussion of frames and facets, and then
describes:
How your part can function as an embedded part in an OpenDoc document
How your part can negotiate with its containing part for modifications to
its display frames
If your part is a noncontainer part, these are the only sections of this
chapter you need to read. If, however, you are developing a container part,
you need also to read the remainder of this chapter. It summarizes how to
embed a part and describes how to interact with the frames and facets of your
embedded parts.
For a summary of embedding issues for container parts, see also Embedding
Checklist.
ΓòÉΓòÉΓòÉ 5.1. Frame and Facet Hierarchies ΓòÉΓòÉΓòÉ
The object hierarchy of embedding controls how information is passed among the
parts that make up a compound document. Parts and embedded frames make up one
hierarchy; facets make up a separate but essentially parallel hierarchy.
The following figure shows a simple example of these hierarchies for a document
that consists of a graphics root part ("draw part"), in which is embedded a
clock ("clock part") and a text-processing part ("text part"). The text part
has a movie-viewing part ("movie part") embedded within it.
This figure uses the same conventions as the more detailed run-time diagrams
presented in the figure in Run-Time Object Relationships. Individual OpenDoc
objects are represented by labeled boxes, with the references (pointers in C++)
between them drawn as arrows. (Different arrows have different appearances in
the following figure for clarity only; all represent the same kinds of object
references). The embedding structure extends downward, with more deeply
embedded objects shown lower in the diagram.
The window object is at the top of the previous figure, uniting the two
hierarchies. The window object itself is referenced by the window state object,
as shown in the figure in section Window State and Windows. (Some inter-object
references have been omitted from this diagram, as noted in the next section).
ΓòÉΓòÉΓòÉ 5.1.1. Frames and Parts ΓòÉΓòÉΓòÉ
The fundamental structure of embedding in the compound document is represented
by the hierarchy on the left. The draw part is the root part of the document;
it directly references its two embedded frames. Those frames, in turn,
reference their parts: the text part and the clock part. The text part
references its one embedded frame, which in turn references its part, the movie
part. Each part thus references only indirectly its own embedded parts. (In
this case, there is one frame per part, but there could be more than one).
Note also that each part also has a reference back up the hierarchy to its
display frame, and each frame has a direct reference up to its containing
frame. See the figure in Embedding for a more complete picture of the object
structure of embedding.
The embedding relation of parts and frames does not have to be the strict
hierarchy shown in the figure in Frame and Facet Hierarchies. For example, a
single part can have frames embedded at different levels, as in the figure in
section Providing Split-Frame Views.
Parts and frames are stored together in their document. When the document is
closed, the states of all the parts and frames are saved. When the document is
reopened, all the parts and frames are restored by reading in the saved data.
ΓòÉΓòÉΓòÉ 5.1.2. Facets ΓòÉΓòÉΓòÉ
The hierarchy on the right side of the figure in section Frame and Facet
Hierarchies is analogous to the one on the left, but it is simpler and more
direct. The facet hierarchy is designed for fast drawing and event-dispatching.
Each facet corresponds to its equivalent frame, but it directly references its
embedded facets. (In this case, there is one facet per visible frame, but there
could be more than one).
Whereas frames must exist (at least in storage, if not always in memory) for
all embedded parts in a document, facets are needed for only those frames that
are visible at any one moment. Because the facet hierarchy is for drawing and
event dispatching, there is no need to have facets that cannot be drawn and
cannot accept events.
Facets are not stored when a document is saved. Facets are created and deleted
by their frames' containing parts, as the facets' frames become visible or
invisible because of scrolling, resizing of frames or windows, or opening and
closing of windows.
Note from the figure in section Frame and Facet Hierarchies that each frame has
a direct reference to (and from) its equivalent facet. That frame-to-facet
reference is the connection between the two hierarchies. It also means that
each part object references its own facets only indirectly, through its display
frames. (Your part can, of course, keep its own private list of its facets).
ΓòÉΓòÉΓòÉ 5.2. Working with your Display Frames and Facets ΓòÉΓòÉΓòÉ
Your part's display frames are the frames in which you draw your part's
contents. This section discusses how to request or respond to changes and
additions to those frames.
You do not directly create your own part's display frames; your part's
containing part does that. When your part is first created or first read into
memory from storage, the containing part of your part will in most cases have
already provided at least one frame for your part to display itself in.
However, it is possible that your part can be instantiated without already
having a display frame. (see Adding an Embedded Part). Your part should allow
for that possibility.
Your part should also allow for the possibility of multiple display frames.
Your part must keep a list of its display frames, with enough information so
that your part knows how to display itself in each and synchronize their
displays when necessary. OpenDoc does not specify the format of that list. It
is your responsibility to keep the list and to update each display frame that
has been affected by any change to your part.
ΓòÉΓòÉΓòÉ 5.2.1. Responding to Reconnected and Closed Display Frames ΓòÉΓòÉΓòÉ
When the document containing your part opens and your previously stored display
frames are read in and instantiated, OpenDoc calls your part's
DisplayFrameConnected method. Here is its interface (note that this and all
other method prototypes in this book are written in IDL):
void DisplayFrameConnected(in ODFrame frame);
Your DisplayFrameConnected method should update your part's internal list of
display frames and other related structures to reflect the addition of the
frame. It should assign the frame's used shape, if different from the frame
shape itself. It should also check the frame's content extent and update it if
necessary, as described in Content Extent. It might also call the frame's
SetDroppable method to add the frame's ability to accept drops.
Unlike for DisplayFrameAdded (see Responding to an Added Display Frame), your
DisplayFrameConnected method does not normally have to create new part info
data or a new set of embedded frames for the display frame, because they will
have been created earlier.
When your part's document is closed, OpenDoc calls your part's
DisplayFrameClosed method when it closes your display frame:
void DisplayFrameClosed(in ODFrame frame);
Your DisplayFrameClosed method should
Update your part's internal list of display frames and other related
structures to reflect the closing of the frame
Relinquish any foci owned by the frame; see Relinquishing Foci
Call the Close method of all your embedded frames
If this frame is the root frame of your window and you have previously
instructed OpenDoc not to dispose of the platform-specific window structure,
you must dispose of the platform window yourself at this time.
Closing and reconnecting can happen at other times as well. For efficient
memory use, a containing part may not always keep frame objects in memory for
all of its embedded frames. Instead, as described in Reconnecting and
Releasing Embedded Frames, it might release frames as they become invisible
through scrolling, and reinstantiate them as they become visible. Released
frames can then be closed by OpenDoc.
Do not update your persistently stored display frames when your
DisplayFrameConnected or DisplayFrameClosed methods are called; these methods
should have no effect on stored data. In general, you should write your part
content and frames to storage only when your Externalize or ExternalizeKinds
methods are called.
ΓòÉΓòÉΓòÉ 5.2.2. Responding to Added or Removed Facets ΓòÉΓòÉΓòÉ
When your part's containing part has added a facet to one of your part's
display frames, the display frame notifies your part of the addition by calling
your part's FacetAdded method. Here is its interface:
void FacetAdded(in ODFacet facet);
Your FacetAdded method must perform certain actions to handle the addition of
the new facet to one of your frames. Some actions depend on the nature and
implementation of your part itself, but others are standard:
The method should store any private information that it needs as part
info data in the facet that is being added. Although a facet's part info
is not persistent, it can hold information that the facet needs for
display.
The method should assign an active shape to the facet, if needed. If you
do not explicitly assign an active shape, OpenDoc uses the frame shape of
the facet's associated frame for the active shape.
The method should create facets for all embedded frames that are visible
within the area of the added facet. See Adding a Facet.
When a containing part removes a facet from one of your part's display frames,
the frame notifies your part by calling your part's FacetRemoved method:
void FacetRemoved(in ODFacet facet);
Your FacetRemoved method must perform certain actions to handle the removal of
the facet. In general, this method reverses the actions performed by
FacetAdded. Typically, the method should at least:
Remove the facets for all embedded frames that were visible in the area
of the removed facet
Delete any part info data that was referenced in the facet
Facets are intended to be ephemeral objects; don't hold references to them
when they are no longer needed. Your FacetRemoved method should delete all
references to the facets that it removes.
Note: If your part is the root part in a window, it receives a FacetAdded
call from the root frame when the window is opened and a FacetRemoved
call from the root frame when the window is closed.
ΓòÉΓòÉΓòÉ 5.2.3. Resizing a Display Frame ΓòÉΓòÉΓòÉ
Because of editing operations in your part, or because of an undesirable frame
size imposed by your part's containing part, you may wish to change the size or
shape of your display frame. You must negotiate this change with the containing
part. (For an example of frame negotiation, showing the point of view of both
the containing part and the embedded part, see Frame Negotiation).
You start by requesting a new frame size from your part's containing part.
Depending on its current contents and other constraints such as gridding, the
containing part may grant the requested size, return a different size, or
refuse the request by returning a size identical to your current frame size.
To request a new frame size, take these steps:
Call the RequestFrameShape method of your display frame. The frame, in
turn, calls the containing part's RequestFrameShape method to forward the
request.
The containing part may honor the request, or it may decide on a
different (usually smaller) shape. It returns the shape it will let your
display frame have. The frame stores it as its frame shape and returns it
to you.
Use the returned frame shape to update the used shape for that frame. You
can also, at this time, update the active shape for your frame's facet.
If your part does not wish to accept the new shape, it can call the
frame's RequestFrameShape method again, but with a different shape to
avoid endless repetition of these steps. Alternatively, it can request an
additional frame, as described in Requesting an Additional Display Frame
(next).
ΓòÉΓòÉΓòÉ 5.2.4. Requesting an Additional Display Frame ΓòÉΓòÉΓòÉ
Your part may need a display frame in addition to the frame or frames already
created by your part's containing part. For example, you may need an extra
frame to flow content into (as when adding another column or page of text), or
you may need it to display your part with a new presentation.
To request another frame, you can call the RequestEmbeddedFrame method of the
containing part. You must specify one of your current display frames as a base
frame; the new frame will be a sibling of the base frame, meaning that it is
embedded at the same level as the base frame. Also, the new frame will be in
the same group as the base frame. (By convention, the containing part adds the
new frame to the end of the sequence in that group.) You also pass additional
information, such as the view type and presentation for the new frame, and
whether it should overlay, or float over, the content of the containing part.
The shape you request for the new frame is understood to be expressed in the
frame coordinates of the base frame (see Frame Coordinate Space). Thus you can
request a position for the new frame that is relative to the base frame (such
as above, below, to the side, or overlapping), by specifying an origin that is
offset from the base frame's origin. The containing part has ultimate control
over frame positioning, however, and is not required to size or place the new
frame exactly as you request. Furthermore, the frame shape returned by the
containing part is by convention normalized, meaning that the
relative-positioning information has been stripped from it and its origin is at
(0, 0).
The containing part responds to this call as described in Adding an Embedded
Frame on Request. Your part then responds as described in Responding to an
Added Display Frame (next).
ΓòÉΓòÉΓòÉ 5.2.5. Responding to an Added Display Frame ΓòÉΓòÉΓòÉ
When an additional display frame is created, OpenDoc automatically connects it
to the part it displays. This automatic connection ensures that frames are
always valid and usable; the object that creates the new frame need do nothing
beyond creating the frame itself.
To achieve that automatic connection, the part displayed in the frame must
respond to this method call, which informs the part that it has a new frame:
void DisplayFrameAdded(in ODFrame frame);
Your part receives this call when it is first created and has no previously
stored display frame, and also when additional display frames are created for
it. In response to this call, your part's DisplayFrameAdded method performs the
appropriate tasks. Most of them depend on the nature and implementation of your
part itself; however, here are some general actions it should take:
The method should add the new display frame to your part's list of
display frames. This list, like other internal structures, is completely
hidden from OpenDoc. You can represent the list any way you choose.
The method should validate the view type and presentation of the new
frame. Your part should accept any view types that are in the required
set of view types, plus any other view types or presentations that you
support. The DisplayFrameAdded method should correct these values, if
necessary, as described in Defining General Display Characteristics.
The method should assign your part's current content extent to the frame
using the frame's ChangeContentExtent method; see Content Extent.
The method should assign a used shape for the frame. If you do not
specifically assign a used shape, the frame shape is used; a containing
part that calls the frame's AcquireUsedShape method receives the frame
shape in that case.
The method should add any needed part info to the frame, by calling the
frame's SetPartInfo method. See Part Info for more information.
If the frame being added is the root frame of its window, your part may
want to activate itself. Part activation is described in How Part
Activation Happens. (This situation can occur only when your part is
first created and has no previous display frame; it cannot happen when
creating an additional display frame).
If the frame being added can accept dropped data, the method should call
the frame's SetDroppable method, passing it a value of kODTrue.
Otherwise, the frame will not be able to receive data through drag and
drop. See Drag and Drop for more information.
Your part should not perform any layout or imaging tasks as a result of a
display frame being added; specifically, it should not at this point negotiate
with its containing part for a different frame shape. It should wait until its
FacetAdded method is called, by which time the containing part has stored the
frame shape and the frame has become visible.
Note: OpenDoc calls DisplayFrameAdded only when a frame is newly created.
When your part opens and its stored display frame is recreated, OpenDoc
calls your part's DisplayFrameConnected method; see Responding to
Reconnected and Closed Display Frames.
ΓòÉΓòÉΓòÉ 5.2.6. Removing a Display Frame ΓòÉΓòÉΓòÉ
To remove a frame in which your part is displayed, you call the
RemoveEmbeddedFrame method of your part's containing part. You can only remove
frames that you have previously requested through calls to your containing
part's RequestEmbeddedFrame method. Your other display frames can be removed
only by your containing part.
When it receives the RemoveEmbeddedFrame call, the containing part calls the
frame's Remove method to permanently remove all connection between your part
and the frame. This also decrements the reference count of the frame object,
and the draft deletes the frame from storage if the reference count is 0. Just
before removing the frame, OpenDoc calls your part's DisplayFrameRemoved
method:
void DisplayFrameRemoved(in ODFrame frame);
Your part's containing part can also initiate removal of one of your display
frames, as described in Removing an Embedded Frame. OpenDoc calls your
DisplayFrameRemoved method regardless of whether your part or the containing
part initiates the removal.
Your DisplayFrameRemoved method should
Relinquish any foci owned by the frame (see Relinquishing Foci )
Delete any part info that your part had associated with the frame
Update your part's internal list of display frames and other related
structures to reflect the removal of the frame.
Remove in turn any embedded frames that your part had been displaying in
that frame, by calling their Remove method
If this frame is the root frame of your window and you have previously
instructed OpenDoc not to dispose of the platform-specific window structure,
you must dispose of the platform window yourself at this time.
Note: OpenDoc calls DisplayFrameRemoved only when a frame is permanently
removed from your part. When your part closes and OpenDoc stores its
display frame, OpenDoc calls your part's DisplayFrameClosed method; see
Responding to Reconnected and Closed Display Frames.
ΓòÉΓòÉΓòÉ 5.2.7. Grouping Display Frames ΓòÉΓòÉΓòÉ
As an embedded part, your part does not directly control the frames in which it
is displayed. However, if you need to flow text or other content in order
through a sequence of separate display frames of your part (as for page-layout
purposes), you can request that the containing part create and assign you the
needed frames as a sequence in a single frame group. See Creating Frame Groups.
ΓòÉΓòÉΓòÉ 5.2.8. Synchronizing Display Frames ΓòÉΓòÉΓòÉ
Sometimes, views of your part in two or more separate frames must be
synchronized, meaning that any editing or other changes to the contents of one
(the source frame) must force updating of the other. In some cases, as when
opening one of your display frames into a window, you can determine these
dependencies internally. In other cases, you cannot. For example, if your part
is an embedded part and your containing part creates multiple views of your
part, your containing part asks you to synchronize those views by calling your
part's AttachSourceFrame method. This is its interface:
void AttachSourceFrame(in ODFrame frame,
in ODFrame sourceFrame);
Your AttachSourceFrame method should take whatever action is necessary to
synchronize the frames; make sure that the result of any editing in one frame
appears in the other. At a minimum, if the two frames have the same
presentation, the method should duplicate all embedded frames in one frame into
the other (and attach them to their source frames as well). It is the
containing frame that determines when embedded frames must be synchronized. See
Synchronizing Embedded Frames.
ΓòÉΓòÉΓòÉ 5.2.9. Using Subframes to Suppress the Active Frame Border ΓòÉΓòÉΓòÉ
The active frame border (see, for example, The figure in Activation and
Selection) is drawn by OpenDoc around the frame of the currently active part.
(It actually follows the active shape of the frame's facet, as described in
Active Shape. When your part is the active part, OpenDoc automatically draws
the active frame border around your part's display frame (the frame that has
the selection focus).
There may be situations in which you do not want the active frame border around
your part's display frame, even when it is active. For example, your part's
frame may represent a single pane in a split window (see Using Multiple
Facets), in which case the entire window should be considered active, not just
the one pane.
In such a case you can force OpenDoc to draw the active frame border around
your display frame's containing frame instead of your display frame itself. You
can call your display frame's SetSubframe method to assign it as a subframe of
its containing frame. (You can test whether a frame is a subframe of its
containing frame by calling its IsSubframe method).
ΓòÉΓòÉΓòÉ 5.2.10. Adopting Container Properties ΓòÉΓòÉΓòÉ
As described in Transmitting your Container Properties to Embedded Parts, a
containing part can notify its embedded parts of the container properties, the
characteristics of its content that it expects the embedded parts to adopt. For
example, if your part is a text part and the user embeds it into another text
part, the containing part may expect your part to adopt the general text
appearance (font, size, stylistic variation, and so on) of the containing part.
Your part can, of course, adopt only those container properties whose format
and meaning it understands. You obtain the set of container properties that
your containing part makes available for adoption by calling the
AcquireContainingPartProperties method of your containing part.
A containing part calls your part's ContainingPartPropertiesUpdated method when
it changes any of the container properties available for your part to adopt.
This is its interface:
void ContainingPartPropertiesUpdated(in ODFrame frame,
in ODStorageUnit propertyUnit);
Your ContainingPartPropertiesUpdated method should read and adopt any container
properties that it understands from the provided storage unit. Then, it should
in turn call the ContainingPartPropertiesUpdated method of any of your part's
own embedded frames (other than bundled frames) that are displayed within the
frame passed to ContainingPartPropertiesUpdated.
ΓòÉΓòÉΓòÉ 5.3. Frame Negotiation ΓòÉΓòÉΓòÉ
Each part in an OpenDoc document controls the positions, sizes, and shapes of
its embedded frames. At the same time, embedded parts may need to change the
sizes, shapes, or numbers of the frames in which they are displayed. Read this
section if your part expects to negotiate its display frame sizes with its
containing part, or if it is a container part and expects to negotiate with its
embedded parts over the sizes of its embedded frames.
Either party can initiate the negotiation, although the containing part has
unilateral control over the outcome. The following figure shows an example of
frame negotiation, in which an embedded part with a single display frame
requests a larger frame size from its containing part, which has two display
frames.
In this case, the embedded part initiates the frame negotiation. Its frame is
wholly contained within frame 1 (the upper frame) of the containing part:
1. The embedded part asks the containing part for a significantly larger
frame, perhaps to fit material pasted in from the clipboard. The embedded
frame is not concerned with, and does not even know, where or how the
larger frame will fit into the containing part's content.
2. The containing part decides, on the basis of its own content model, that
the requested frame is too large to fit within frame 1. The containing
part instead increases the size of the embedded frame as much as it can,
assigns it a place in its content area, and returns the resulting frame
to the embedded part.
3. The embedded part accepts the frame given to it. If it were to repeat the
first step and ask for the original larger frame again, the containing
part would simply repeat the second step and return the same frame.
But the embedded part still wants more area for its display, so it tries
a different approach; it requests another display frame, this time to be
embedded in frame 2 of the embedded part.
4. The containing part decides that the requested frame will fit in frame 2.
It assigns the frame a place within frame 2 and returns the frame to the
embedded part.
Frame negotiation, from the point of view of the embedded part, is discussed
in Resizing a Display Frame and Requesting an Additional Display Frame. Frame
negotiation, from the point of view of the containing part, is discussed in
Resizing an Embedded Frame and Adding an Embedded Frame on Request.
ΓòÉΓòÉΓòÉ 5.4. Working with Embedded Frames and Facets ΓòÉΓòÉΓòÉ
Read the information in this section if your part can contain embedded parts.
It discusses what information your part needs to maintain for embedded frames
and how it creates those embedded frames and facets.
As a containing part, your part needs to maintain current information on the
shapes and transforms for all its visible embedded frames and facets. If it
makes changes to them, it should not only update its own information but in
some cases also notify the embedded parts of the changes, so that they can
update their own information. Your part must also support frame negotiation
(see Frame Negotiation), to permit embedded parts to request additional frames
or changes to the sizes of their existing frames.
If your part is a container part, the user can embed other parts into your part
in several ways, such as by pasting from the clipboard, using drag and drop, or
even selecting a tool from a palette.
The overall process of embedding a part is summarized in the section Adding an
Embedded Part. The overall process of removing a part is summarized in Removing
an Embedded Part. Both processes make use of the specific tasks described in
this section.
ΓòÉΓòÉΓòÉ 5.4.1. Creating a New Embedded Frame ΓòÉΓòÉΓòÉ
If your part embeds a part that does not already have its own display frame,
you need to create a new embedded frame that will be the embedded part's
display frame. Also, if you create an additional view of an existing embedded
part you need to create and embed a frame to hold the new view.
In these situations, your part-the containing part-initiates the embedding
creation. Your method to create an embedded frame calls the CreateFrame method
of the draft, which returns an initialized frame that has already been assigned
to the embedded part (CreateFrame calls the DisplayFrameAdded method of the
embedded part to do that). This calling sequence ensures that the new frame can
be used as soon as the draft returns it.
When you call CreateFrame, you specify several features of the new frame,
including the following:
Containing frame: the frame (your display frame) that is to contain the
embedded frame.
Frame shape: the shape of the frame to be created. Your part can use a
default shape or it can use information supplied with the data you are
embedding; see Frame Shape or Frame Annotation for more information.
Part: the part (your part) in which the frame is to be embedded.
View type and presentation: the initial view type and presentation the
part displayed in the embedded frame is to have.
Subframe: whether or not the embedded frame is to be a subframe of its
containing frame (your display frame). See Using Subframes to Suppress
the Active Frame Border, and Using Multiple Facets for examples.
Overlay status: whether or not the frame should overlay, or float over,
the containing frame.
Once the reference to the new frame is returned, you store it somewhere in
your content and add it to your part's list of embedded frames. It's up to you
to decide how your part internally stores its content, including its embedded
frames. If the embedded frame is visible within the containing frame, you must
create a facet for it. See Adding a Facet.
For more information on the DisplayFrameAdded method, see Requesting an
Additional Display Frame.
ΓòÉΓòÉΓòÉ 5.4.2. Adding an Embedded Frame on Request ΓòÉΓòÉΓòÉ
As a container part, your part may also need to support creation of an embedded
frame when requested to do so by another part. As described in Requesting an
Additional Display Frame, an embedded part can call your part's
RequestEmbeddedFrame method in order to get an additional frame for its
content. The embedded part passes the necessary information about the requested
frame, as shown in this interface:
ODFrame RequestEmbeddedFrame(in ODFrame containingFrame,
in ODFrame baseFrame,
in ODShape frameShape,
in ODPart embedPart,
in ODTypeToken viewType,
in ODTypeToken presentation,
in ODBoolean isOverlaid);
Your RequestEmbeddedFrame method passes most of this information along to your
draft's CreateFrame method. The base-frame parameter specifies which of the
embedded part's existing display frames is to be the base frame for the new
frame; the new frame will be a sibling of the base frame and will be in the
same frame group (if any) as the base frame.
The frameShape parameter passed to this method expresses the requested frame
shape in the coordinate system of the base frame. By this method, the embedded
part can request, by specifying the origin of the frame shape, a relative
location for the new frame compared to its base frame. Your
RequestEmbeddedFrame method should take this information into account when
granting the frame shape and assigning an external transform to its facet.
Specifically, you should incorporate the positioning information into the
external transform (if appropriate, given the nature and state of your
intrinsic content) and then return a frame shape that has been normalized-that
is, one in which the origin of the frame shape is at (0, 0).
Based on information in the existing frame, your RequestEmbeddedFrame method
should also assign the new frame's group ID and sequence number, by calling its
SetFrameGroup and ChangeSequenceNumber methods. Your part can assign the new
frame any sequence number in the frame group, although by convention you should
add the new frame to the end of the current sequence. Then the
RequestEmbeddedFrame method should add the new frame to your part's list of
embedded frames. The method should also create a facet for the new frame if it
is visible.
You might implement your RequestEmbeddedFrame method in such a way that it
calls a private method to actually create the frame. You could then use that
same private method when creating embedded frames other than by request (as
described in the previous section, Creating a New Embedded Frame).
ΓòÉΓòÉΓòÉ 5.4.3. Resizing an Embedded Frame ΓòÉΓòÉΓòÉ
To change a frame's size, the user typically selects the frame and manipulates
the frame border's resize handles. The containing part is responsible for
drawing the selected frame border, determining what resize handles are
appropriate, and interpreting drag actions on them. For more information on
event-handling related to resizing frames, see Resizing Selected Frames.
If your part is the containing part of a frame that is resized by the user, or
if your part has other reasons to change the size of an embedded frame (for
example, to enforce gridding or in response to editing of your own intrinsic
content surrounding the frame), you need to notify the embedded part that its
frame has changed.
After you change the embedded frame's shape, call the frame's ChangeFrameShape
method and pass it the new shape. (For efficiency, you can first acquire the
embedded frame's existing frame shape, then resize it, then call
ChangeFrameShape, and finally release your reference to the frame). The frame
in turn notifies its part by calling its FrameShapeChanged method. In response,
the embedded part may request a different frame size, as discussed in Resizing
a Display Frame.
Note that resizing may result in your part having to adjust the layout of its
own intrinsic content, such as wrapped text.
ΓòÉΓòÉΓòÉ 5.4.4. Removing an Embedded Frame ΓòÉΓòÉΓòÉ
To remove a frame embedded in your part, the basic procedure is to delete all
its facets, delete it from your private content structures and call its frame's
Remove method. This releases the frame object, which decrements its reference
count and possibly causes the draft to delete it from storage. OpenDoc then
notifies the embedded part of the removal by calling its DisplayFrameRemoved
method, as described in Removing a Display Frame.
Removing an embedded frame should be an undoable action, however. Therefore,
you should add a few steps to the procedure to retain enough information to
reconstruct the frame (and all its embedded frames) if the user chooses to undo
the deletion. You can follow steps such as these:
1. Remove all of the embedded facets from the frame; see Removing a Facet.
2. Set the frame's containing frame to kODNULL, to indicate that the frame
is no longer embedded in any part.
3. Place a reference to the frame in an undo action that you add to the undo
history; see Adding an Action to the Undo History.
4. Remove the frame from your embedded-frames list (with which you allow
callers to iterate through your embedded frames; see Providing an
Embedded-Frames Iterator).
5. Remove the frame from your other private content structures.
If the user subsequently chooses to undo the action that led to the removal of
the frame, you can then
1. Retrieve the reference to the frame from the undo action
2. Reestablish your display frame as the frame's containing frame
3. Recreate any needed facets for the frame
If the undo action history is cleared, your part's DisposeActionState method
is called and at that point you can remove the frame object referenced in your
undo action, by calling its Remove method.
ΓòÉΓòÉΓòÉ 5.4.5. Reconnecting and Releasing Embedded Frames ΓòÉΓòÉΓòÉ
Your part connects and closes its embedded frames when the document containing
your part opens and closes. On opening, when your part calls the draft's
AcquireFrame method for each embedded frame that you want to display the frame
in turn calls the DisplayFrameConnected method of its part. After your part and
its embedded frames have been saved, and before the document closes, you call
the Close method of each of your embedded frames; the frames in turn call the
DisplayFrameClosed methods of their parts.
The DisplayFrameClosed and DisplayFrameConnected methods are described in
Responding to Reconnected and Closed Display Frames.
For efficient memory usage, your part can retrieve and connect only the
embedded frames that are visible when its document opens, and it can also
release and reconnect embedded frames during execution, as the frames become
invisible or visible through scrolling or removal of obscuring content. This
process is described in Lazy Instantiation.
ΓòÉΓòÉΓòÉ 5.4.6. Adding a Facet ΓòÉΓòÉΓòÉ
OpenDoc needs to know what parts are visible in a window and where, so that it
can dispatch events to them properly and make sure they display themselves. But
OpenDoc does not need to know the embedding structure of a document; that is,
OpenDoc is not directly concerned with where embedded frames are located and
what their sizes and shapes are. Because embedded frames are considered to be
content elements of their containing part, each containing part maintains
embedded-frame positions, sizes, and shapes in its own internal data
structures. Therefore, containing parts need to give OpenDoc information about
embedded frames only when they are visible. They do this is by creating a facet
for each location where one of their embedded frames is visible.
An embedded frame in your part may become visible immediately after it is
created, or when your part has scrolled or moved it into view, or when an
obscuring piece of content has been removed. However the frame becomes visible,
your part must ensure that there is a facet to display the frame's contents.
Follow these general steps:
If your own display frame has multiple facets, create an embedded facet
for each of the facets in which the newly visible embedded frame appears.
To do that, iterate through all the facets of your display frame,
creating embedded facets where needed.
For each needed facet, if it does not already exist, make one by asking
the containing facet (your display frame's facet) to create one. Call the
containing facet's CreateEmbeddedFacet method.
- In your call to CreateEmbeddedFacet, assign the embedded facet a
clip shape equal to the embedded frame's frame shape, if the new
facet is to be positioned in front of all sibling facets and other
content in your part. Otherwise, adjust the clip shape as described
in Managing Facet Clip Shapes.
- Assign the embedded facet an external transform, based on your
part's internal data on the location of the embedded frame.
- If you are to be drawing offscreen through this facet, assign the
facet a canvas. See Canvases for an explanation.
If you want to receive events sent to, but not handled by, the part
displayed in this facet, set the event-propagating flag of the facet's
frame. See Propagating Events for an explanation.
After you create the facet, OpenDoc calls the embedded part's FacetAdded
method to notify it that it has a new facet.
ΓòÉΓòÉΓòÉ 5.4.7. Removing a Facet ΓòÉΓòÉΓòÉ
An embedded frame may become invisible when the containing part has deleted it,
scrolled or moved it out of view, or placed an obscuring piece of content over
it. In any of these instances, the containing part can then delete the facet,
because it is no longer needed.
Your part deletes the facet of an embedded frame by calling the containing
facet's RemoveFacet method. (If the frame that is no longer visible has more
than one facet, you need to iterate through all facets, removing each one that
is not visible). OpenDoc in turn calls the embedded part's FacetRemoved method
to notify it that one of its facets has been removed.
You do not have to actually delete the facet from memory the moment it is no
longer needed; you can instead mark it privately as unused and wait for a call
to your Purge method to actually remove it.
ΓòÉΓòÉΓòÉ 5.4.8. Creating Frame Groups ΓòÉΓòÉΓòÉ
A frame group is a set of display frames used in sequence. For example, a
page-layout part uses a frame group to display text that flows from one frame
to another. Each frame in the frame group has a sequence number; the sequence
numbers establish the order of content flow from one frame into the next.
Sequence information is important for a frame group because the embedded part
needs to know the order in which to fill the frames. Also your part (the
containing part) needs to provide sequence information to the user, and it
probably also needs to allow the user to set up or modify the sequence.
Your part creates and maintains the frame groups used by all its embedded
parts. To create a frame group, you call the SetFrameGroup method of each frame
that is to be in the group, passing it a group ID that is meaningful to you.
You also assign each frame a unique sequence number within its group, by
calling its SetSequenceNumber method. You should assign sequence numbers that
increase uniformly from 1, so that the embedded part can recognize the position
of each frame in the group. You can, of course, add and remove frames from the
group and alter their sequence with additional calls to SetFrameGroup and
SetSequenceNumber. The embedded part displayed in the frame group can find out
the group ID or sequence number of any of its frames by calling the frame's
GetFrameGroup and GetSequenceNumber methods.
ΓòÉΓòÉΓòÉ 5.4.9. Synchronizing Embedded Frames ΓòÉΓòÉΓòÉ
If your part wants to create multiple similar views of an embedded part, you
must ask the embedded part to synchronize those views. Then, if the content of
one of the frames is edited, the embedded part will know to invalidate and
redraw the equivalent areas in the other frames.
Frame synchronization is necessary because each display frame of a containing
part represents a separate display hierarchy. For invalidating and redrawing,
OpenDoc itself maintains no direct connection between embedded frames in those
separate hierarchies, even if they are exact duplicates that show the same
embedded-part content. the figure in Synchronizing Embedded Frames shows an
example. Part A (in frame view type) is opened into a part window. Embedded
frame B2, as displayed in the part window, is a duplicate of embedded frame B1
in the original frame. However, unless you synchronize the frames, Part B will
not know to update the display of B1 if the user edits the content of B2.
Your part (the containing part) makes the request to synchronize frames by
calling the embedded part's AttachSourceFrame method. You should call
AttachSourceFrame as soon as you create the frame that needs to be synchronized
with the source frame-that is, before you add any facets to the frame. How an
embedded part responds to AttachSourceFrame is described in Grouping Display
Frames. See Synchronizing Display Frames for a description of how an embedded
part responds to AttachSourceFrame.
ΓòÉΓòÉΓòÉ 5.4.10. Transmitting your Container Properties to Embedded Parts ΓòÉΓòÉΓòÉ
When one part is embedded in another part of the same or similar part category,
the user may prefer that, by default, they share certain visual or behavioral
characteristics. For example, if the user embeds a text part into another text
part, it might be more convenient for the embedded part to adopt the text
appearance (font, size, stylistic variation) of the containing part.
OpenDoc supports the communication necessary for this process by defining the
concept of container properties. The containing part defines whatever
characteristics it wishes to transmit to its embedded parts; embedded parts
that understand those container properties can choose to adopt them. Container
properties are passed from the containing part to its embedded part in a
storage unit; the embedded part reads whatever container properties it
understands from the storage unit, and adopts them for its own display if
appropriate.
Note: Container properties are completely part-defined and may apply to only a
portion of a part's content, and thus are in general unrelated to the
properties of the part's storage unit, as described in Storage-Unit
Organization.
As the containing part, your part can define whatever container properties it
wishes to provide for adoption by embedded parts. Only parts that understand
the formats of your container properties, of course, can adopt them.
An embedded part learns what container properties it might adopt from your
part by calling your part's AcquireContainingPartProperties method. This is
its interface:
ODStorageUnit AcquireContainingPartProperties(in ODFrame frame);
You should return your container properties to the caller in a storage unit.
Whenever your part changes any of the container properties that it expects
embedded parts to adopt, it should notify each embedded part (other than
bundled parts) of the change by calling the embedded part's
ContainingPartPropertiesUpdated method.
ΓòÉΓòÉΓòÉ 5.4.11. Providing an Embedded-Frames Iterator ΓòÉΓòÉΓòÉ
Your part must keep a list of all its embedded frames. OpenDoc does not specify
the format of this list. However, your part must implement an iterator class
(type ODEmbeddedFramesIterator) that gives a caller access to each of the
frames in your list of embedded frames. The caller creates an iterator to
access your embedded frames by calling your part's CreateEmbeddedFramesIterator
method, which has this interface:
ODEmbeddedFramesIterator CreateEmbeddedFramesIterator(in ODFrame frame);
Your implementation of CreateEmbeddedFramesIterator must provide First, Next,
and IsNotComplete methods, as do other OpenDoc iterators. See Accessing Objects
through Iterators for additional discussion.
ΓòÉΓòÉΓòÉ 6. Drawing ΓòÉΓòÉΓòÉ
This is the second of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes how your part draws itself.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. You should also be familiar with the
discussion of frames and facets presented in the previous chapter. For
additional concepts related to your part editor's runtime environment, see
OpenDoc Run-Time Features. The discussion in this chapter also assumes that you
are familiar with the platform-specific graphics system and drawing commands
you need to draw your parts' content. For OS/2, this means the OS/2 Graphic
Programming Interface (GPI).
This chapter shows how your part:
Uses canvases as drawing destinations
Use transforms and shapes to lay out its contents
Draws itself to the screen
Prints itself
For a discussion of windows, the display structures within which all your
drawing typically takes place, see Windows and Menus.
ΓòÉΓòÉΓòÉ 6.1. Canvases ΓòÉΓòÉΓòÉ
Canvases are inherently platform-specific. OpenDoc canvas objects are basically
wrappers for structures that differ across platforms. This section discusses
how to use canvas objects regardless of which platform you are developing for,
with some OS/2-specific information provided where appropriate.
ΓòÉΓòÉΓòÉ 6.1.1. Using Canvases ΓòÉΓòÉΓòÉ
The class ODCanvas is OpenDoc's wrapper for a platform-specific (or
graphics-system-specific) object that represents a drawing environment. A
drawing canvas can refer to anything from a bit map or a structured display
list to a stream of PostScript code. It represents the destination for drawing
commands, the environment for constructing a rendered image; it has a
coordinate system and may retain state information (such as pen color) that
influences how drawing commands are interpreted.
A canvas object holds a reference to a system-specific object called a platform
canvas, and that object is not deleted when the canvas is released. If you
create a canvas, you must create the platform canvas separately, and you are
responsible for deleting it when the canvas is deleted.
On some platforms, the platform canvas is a system-defined type or structure.
On OS/2, it is a SOM class, which you create using the ODFacet method
CreatePlatformCanvas.
ΓòÉΓòÉΓòÉ 6.1.1.1. Canvas Features ΓòÉΓòÉΓòÉ
Canvases can be dynamic or static, meaning that they are used for video display
or printing display, respectively. Your part editor can determine whether it is
drawing to a static or dynamic canvas and can adjust its procedures
accordingly.
Canvases can be onscreen or offscreen. Your part editor can create special
effects or improve performance by drawing a complex image to an offscreen cache
and then quickly transferring the completed image to the screen. For added
convenience, offscreen canvases maintain clipping and updating information that
mirrors their onscreen equivalents.
Canvases are attached to individual facets. In the following figure, for
example, which shows the same document with the same facet hierarchy as in the
figure in Frame and Facet Hierarchies, the document has two attached canvases:
An offscreen canvas is attached to the movie part's facet.
An onscreen canvas is attached to the root part's facet. (A canvas
attached to the root facet of a window is also called a window canvas;
every window has a window canvas, which is assigned to its root facet by
OpenDoc when you first register the window).
If a particular facet in a window's facet hierarchy has an attached canvas,
all of its embedded facets (and their embedded facets, and so on) draw to that
canvas. Thus, for most drawing, only a window's root facet needs a canvas. In
the previous figure, for example, any drawing done to the text facet, draw
facet, or clock facet is rendered on the window canvas.
However, if a particular part needs an offscreen canvas, for itself or for an
embedded part, it can attach a canvas to a facet anywhere within the
hierarchy. Any drawing done to the movie facet in the previous figure, for
example, is rendered on the offscreen canvas.
On OS/2, a window canvas consists of a Presentation Manager (PM) window
hierarchy that parallels the facet hierarchy. There is one PM window for each
facet in the window canvas.
Every canvas has an owner, a part that is responsible for transferring images
drawn on its canvas to the parent of that canvas. In the previous figure, for
example, the movie images drawn to the offscreen canvas must be transferred to
the window canvas in order to be viewed onscreen. The owner decides when and
how to transfer images from a canvas to its parent. The owner of the offscreen
canvas in the previous figure might be the movie part or the text part that
contains the movie part.
ΓòÉΓòÉΓòÉ 6.1.1.2. Adding and Removing Canvases ΓòÉΓòÉΓòÉ
This section describes how to create and delete canvases for your parts. For
specific information on using canvases for offscreen drawing, see Offscreen
Drawing. If you want to create a canvas and attach it to a facet, take these
steps:
1. Create and initialize a GPI presentation space and associate it with a
memory DC.
2. Create an ODPlatformCanvas object using the CreatePlatformCanvas method
of ODFacet.
3. Create a canvas object, using the CreateCanvas method of ODFacet or
ODWindowState. In calling the method, you assign the platform canvas to
the new canvas, and you also define the canvas as static or dynamic and
onscreen or offscreen. (These values cannot change for the lifetime of
the canvas).
4. Designate your part as owner of the canvas by calling the SetOwner method
of the canvas.
5. Assign the canvas to a facet. Because only the owner of a canvas can
remove it from a facet, the timing of assigning the canvas is important:
If your part is a containing part assigning an offscreen canvas to
one of its embedded parts, you should assign the canvas when you
first create the embedded facet that is, when you first call the
CreateEmbeddedFacet method of the embedded part's containing facet.
Otherwise, the embedded part may assign a canvas to the facet,
precluding you from doing so.
If your part is a containing part and one of its embedded parts has
an existing facet with no assigned canvas, you can add one by
calling the ChangeCanvas method of the embedded facet. When you do
so, OpenDoc communicates the change to all embedded parts that use
that facet, by calling their CanvasChanged methods.
Your part can add a canvas to any of its own display frames' facets
at any time, as long as the facet does not already have an assigned
canvas. It is probably best to attach the canvas as soon as the
facet is created, by calling ChangeCanvas from within your part's
FacetAdded method. If the containing part of your part has already
attached a canvas to your new facet, you cannot assign a different
canvas to it.
If your part absolutely needs to attach its own canvas to a facet
that already has an assigned canvas, you can get around this
restriction by creating a subframe of the facet's frame, creating a
facet for that frame, and assigning the canvas to that facet.
To remove a canvas from a facet, take these steps:
1. Call the facet's ChangeCanvas method, passing it a null value for the
canvas reference.
2. Delete the PS and memory DC that the platform canvas had referenced.
3. Delete the canvas object, by calling delete (in C++).
4. Delete the platform canvas object, by calling delete in C++.
ΓòÉΓòÉΓòÉ 6.2. Transforms and Shapes ΓòÉΓòÉΓòÉ
Unlike windows and canvases, transforms and shapes are not platform-specific.
The OpenDoc objects that represent them may encapsulate platform-specific
structures, but they also often give added capabilities. This section discusses
how you use transforms and shapes to position and clip embedded parts for
drawing.
ΓòÉΓòÉΓòÉ 6.2.1. Transforms and Coordinate Spaces ΓòÉΓòÉΓòÉ
Both frames and facets employ transforms. It is useful to discuss them in terms
of the coordinate spaces they define.
In OpenDoc, an object of the class ODTransform is a 3x3 matrix that is used to
modify the locations of points in a systematic manner. OpenDoc transforms
support the full range of two-dimensional transformations shown in the
following figure.
Not all graphics systems support all the features of OpenDoc transforms. Some
graphics systems support only offset, or translation, of points; others support
offset plus scaling; still others support all transformations. Depending on the
graphics system your part editor uses, it may have to do extra work on its own
to support features such as scaling or rotation. In such a case, you can create
a subclass of ODTransform, if desired, that adds those features or even other,
more sophisticated transformational capabilities.
A transform can be thought of as defining a coordinate space for the items that
it is applied to. OpenDoc uses transforms to convert among two kinds of
coordinate space: frame coordinate space and content coordinate space.
ΓòÉΓòÉΓòÉ 6.2.1.1. Frame Coordinate Space ΓòÉΓòÉΓòÉ
The frame coordinate space is the coordinate system defined by the
specification of the frame shape. The frame shape is the basis for the layout
and drawing of embedded parts. Suppose, for example, that a frame shape is
defined as a rectangle with coordinates of (0, 0) and (100, 100). In a
coordinate system with the origin at the lower left as in OS/2, the shape would
appear as shown in the figure below.
All shapes and geometric information passed back and forth between embedded
parts and their containing parts are expressed in terms of the frame coordinate
space.
Note that the shapes describing the facet associated with a frame are described
in the same coordinate space as the frame; that is, they are in frame
coordinates. Thus, in this example, if the facet corresponded exactly to the
frame (which is common), it would have coordinates of (0, 0) and (100, 100).
ΓòÉΓòÉΓòÉ 6.2.1.2. Content Coordinate Space ΓòÉΓòÉΓòÉ
The content coordinate space of a part is the coordinate system that defines
locations in the part's content area. It is the local coordinate space of the
part itself and typically has its origin in the lower-left corner of the part's
content.
The internal transform of a part's display frame defines the scrolled position
(as well as the scaling, rotational, and skew properties) of the part's
contents within the frame. Applying the display frame's internal transform to a
point in content coordinate space converts it to frame coordinate space.
Conversely, applying the inverse of the internal transform to a point in frame
coordinate space converts it to content coordinate space.
For example, suppose that the following figure shows the entire contents of a
part, and that a portion of it is to be displayed in a frame. If the part's
display frame has the dimensions shown previously (the figure in Frame
Coordinate Space), and if the frame's internal transform specifies an offset
value of (50, 50), the frame would appear in relation to its part's content as
shown in the following figure. Only the portion of the part within the area of
frame shape will be displayed when the part is drawn.
Application of the internal transform, in this case, means that a point at (50,
50) in content coordinates-the lower-left corner of the display frame-is at (0,
0) in frame coordinates. Conversely, a point at (50, 50) in frame coordinates
is at (0, 0) in content coordinates.
Transformations other than offsets are applied in the same manner; you can
scale, rotate, or otherwise transform the contents of the part within its frame
by applying the frame's internal transform.
ΓòÉΓòÉΓòÉ 6.2.1.3. Converting to the Coordinates of a Containing Part ΓòÉΓòÉΓòÉ
Just as the internal transform of a frame defines the scrolled position of the
content it displays, the external transform of that frame's facet defines the
position of the frame within its containing part.
Applying the external transform of a frame's facet to a point in frame
coordinate space converts it to the content coordinate space of the containing
part. For example, suppose that the facet and frame of the embedded part in the
previous figure have the same shape, and suppose further that the facet's
external transform specifies an offset of (150, 10). In relation to the
containing part's content area, the embedded part would appear as shown in the
following figure.
In this case, applying the external transform causes a point at (0, 0) in
embedded-frame coordinates-the lower-left corner of the embedded part's
frame-to be at (150, 10) in containing-part content coordinates.
(Conversely, applying the inverse of the embedded facet's external transform to
a point in content coordinate space converts it to the embedded frame's
coordinate space. Thus, in the previous figure, a point at (150, 10) in
embedded-frame coordinates is at (0,0) in the content coordinates of the
containing part).
Transformations other than offsets are applied in the same manner; the embedded
part and its frame can be scaled, rotated, or otherwise transformed within the
containing part by applying the facet's external transform.
To convert from the content coordinates of an embedded part to the content
coordinates of its containing part therefore, you need to apply two transforms:
the internal transform of the embedded part's display frame, followed by the
external transform of that frame's facet. For the example shown in this
section, you can see by inspection that the point (50, 50) in the content
coordinates of the embedded part (the origin of its display frame) becomes the
point (150, 10) in the content coordinates of the containing part. You could
also calculate that the point (0, 0) in the contents of the embedded part (its
lower-left corner) converts, by application of both transforms, to the point
(100, 10) in the contents of the containing part (outside of the embedded frame
and, therefore, not drawn).
You are not usually concerned with your embedded part's location within the
content area of its containing part. You are, however, always interested in
your content's location-and your frame's location-on the canvas or in the
window in which you are drawn, as described next.
ΓòÉΓòÉΓòÉ 6.2.1.4. Canvas Coordinates and Window Coordinates ΓòÉΓòÉΓòÉ
When your part draws its contents, or when it draws adornments to its frame
such as scroll bars, it is your part's responsibility to properly position what
it draws on its canvas. In setting up the canvas' platform-specific drawing
environment, you need to provide information that tells the canvas where, in
terms of its own coordinate space, your drawing will take place. OpenDoc does
not do that for you automatically. It does, however, provide methods that make
it fairly simple.
If you start from your part's content coordinate space and traverse the
embedding hierarchy upward, applying, in turn, your part's internal transform
and external transform, followed by each internal transform and external
transform of all containing frames and facets, up to a facet with an attached
canvas, you arrive at the canvas coordinate space or the window coordinate
space in which your part is drawn.
If there are no offscreen canvases in the facet hierarchy, the canvas
coordinate space is the same as the window coordinate space, and
calculating it involves applying all transforms up through the internal
transform of the root facet. This coordinate space corresponds to the
device coordinates of the window canvas.
If there are one or more offscreen canvases in the facet hierarchy, the
canvas coordinate space corresponds to the frame coordinates of the first
part above yours in the hierarchy whose facet has an attached canvas.
Calculating it involves applying all transforms up through the internal
transform of the frame whose facet has the canvas. The window coordinate
space, in this case, is unaffected by the presence of an offscreen
canvas; it is still defined by the device coordinates of the window
canvas.
The following figure shows a simple example of converting to canvas
coordinates and window coordinates. The containing part and the embedded part
are the same ones as shown in the figure in Converting to the Coordinates of a
Containing Part, and the containing part is, in this case, the root part of
the window. The internal transform of the root frame (the containing part's
display frame) specifies an offset of (50, 50), and the external transform of
the root facet is identity.
Converting content coordinates to window coordinates means concatenating all
the transforms up through the root frame's external transform. If there is no
offscreen canvas in the previous figure, the point (50,50) in content
coordinates (the origin of the embedded part's frame, as shown in the figure
in Content Coordinate Space) becomes the point (100, 10) in window or canvas
coordinates. If there were an offscreen canvas attached to the embedded facet
in the previous figure, the canvas coordinates for the origin of the embedded
part's frame would be (0,0).
Normally, you do all your drawing in canvas coordinate space. That way,
whether or not you are drawing to an offscreen canvas (which you might not
control or even be aware of), your positioning will be correct. However, when
you need to draw directly to the window to provide specific user feedback, as
described in Drawing Directly to the Window, you need to work in window
coordinates.
By concatenating the appropriate internal and external transforms, OpenDoc
calculates four different composite transforms that you can use for
positioning before drawing:
The composite transform from your content coordinates to canvas
coordinates is called your content transform, and you apply it when
drawing your part's contents on any canvas. You obtain it by calling your
facet's AcquireContentTransform method.
The composite transform from your frame coordinates to canvas coordinates
is called your frame transform, and you apply it when drawing any frame
adornment on the canvas. You obtain it by calling your facet's
AcquireFrameTransform method.
The composite transform from your content coordinates to window
coordinates is called your window-content transform, and you apply it
when drawing your part's contents directly to the window. You obtain it
by calling your facet's AcquireWindowContentTransform method.
The composite transform from your frame coordinates to window coordinates
is called your window-frame transform, and you apply it when drawing any
frame adornment directly to the window. You obtain it by calling your
facet's AcquireWindowFrameTransform method.
For a description of how to use these transforms when setting up for drawing,
see Draw Method of your Part Editor.
ΓòÉΓòÉΓòÉ 6.2.1.4.1. Transforms and Hit-Testing ΓòÉΓòÉΓòÉ
Hit-testing is, in a sense, the inverse of drawing; it involves a conversion
from window coordinates to your part's content coordinates. In most
circumstances, OpenDoc takes care of this for you. See Hit-Testing for more
information.
ΓòÉΓòÉΓòÉ 6.2.1.5. Coordinate Bias and Platform-Normal Coordinates ΓòÉΓòÉΓòÉ
On each platform, OpenDoc uses the platform's native coordinate system, called
platform-normal coordinates, for stored information and for internal
calculations. For example, on OS/2, coordinates are measured with the origin at
the lower-left (of the screen, of a shape, or of a page, and so on), with
increasing values to the right and upward. Some other platforms use coordinates
with an origin at the upper-left, with values increasing to the right and
downward. The following figure shows how these two coordinate systems would
apply to measurements on a text part's page.
OpenDoc functions consistently on any platform, regardless of the platform's
coordinate system, without need for coordinate conversion. However, a part
editor that assumes a particular coordinate system will not function correctly
with OpenDoc on a platform with a different coordinate system, unless it first
accounts for the coordinate bias, or difference between its coordinate system
and platform-normal coordinates.
ΓòÉΓòÉΓòÉ 6.2.1.5.1. Bias Transforms ΓòÉΓòÉΓòÉ
Coordinate bias usually takes the form of an offset in the origin, a change in
the polarity of one or more axes, and possibly a change in scale. A
transformation matrix, called a bias transform, is applied to measurements in a
part's coordinate system to change them into platform-normal values.
When your part editor's assumed coordinate system is different from that of
OpenDoc or other parts on the current platform, your part can still communicate
properly with them if you install an offscreen canvas on your part's facets and
assign the proper bias transform to it. You draw to that canvas using your own
coordinates and then transfer the (automatically transformed) results to the
onscreen, platform-normal, parent canvas.
ΓòÉΓòÉΓòÉ 6.2.1.5.2. Content Extent ΓòÉΓòÉΓòÉ
To convert locations in your part's content between coordinate systems whose
origins are offset, the vertical extent of your content (in essence, the height
of your part's page) represents the offset between the two coordinate origins.
See the figure in Coordinate Bias and Platform-Normal Coordinates for an
example. This content extent is the offset needed in the bias transform that
performs the conversion.
Because your part may be drawn at any time on a canvas that has a bias
transform attached, you should always make the value of your content extent
available. When a frame is added to your part, and whenever your part's content
extent changes (such as when you add a new page), you should call your display
frame's ChangeContentExtent method so that the frame always stores the proper
value. A caller constructing a bias transform can obtain the current content
extent of your part by calling the GetContentExtent method of your part's
frame.
ΓòÉΓòÉΓòÉ 6.2.1.5.3. The biasCanvas Parameter ΓòÉΓòÉΓòÉ
The classes ODFrame and ODFacet include several methods, such as
ChangeInternalTransform and ContainsPoint, that specify shapes or calculate
positions on a canvas. Because these calculations necessarily assume a
coordinate system, the methods include a parameter, biasCanvas, that allows you
to specify a canvas whose attached bias transform is to be used to convert
between your part editor's coordinates and platform-normal coordinates. Thus,
after you set up your offscreen canvas for drawing in your own coordinate
system, you can also use it to make sure that all point, frame, and facet
geometry is properly converted for you.
ΓòÉΓòÉΓòÉ 6.2.2. Using Transforms ΓòÉΓòÉΓòÉ
The sections that follow cover some of the basic ways you can use transforms to
position and modify the content of your part and embedded parts.
ΓòÉΓòÉΓòÉ 6.2.2.1. Scrolling your Part in a Frame ΓòÉΓòÉΓòÉ
If your part's content area is greater than its frame area, only a portion of
the content can be displayed in the frame. The user needs to be able to choose
which portion of a part shows through its frame by moving the part's
contents-as a unit-in relation to the frame position.
The standard OpenDoc method for supporting this ability involves the Show Frame
Outline menu command, available in the View menu when the user opens your
part's frame into its own part window. In this mode, the user can drag an
outline of the frame, positioning it as desired in your part's content area.
Another standard method employs a mode that uses a hand-shaped cursor to drag
the part within its frame. It is also possible to use arrow keys or other
keyboard input to cause scrolling.
Providing scroll bars is another way to support scrolling. If your part is the
root part of a document window or part window, scroll bars are the standard
method. If your part is an embedded part displayed in a frame, scroll bars may
or may not be appropriate, depending on the size of your frame and the nature
of its contents.
Programmatically, changing the portion of a part displayed in a frame involves
modifying the offset (translational setting) of the internal transform of the
part's frame and then redrawing. If you do not have scroll bars within your
frame, these are the basic steps:
1. Obtain a copy of your frame's internal transform, by calling the frame's
AcquireInternalTransform method.
2. Call the MoveBy method of the transform, passing it the negative of the
amount by which you want the frame to scroll.
3. Reassign the changed transform to your frame, by calling your frame's
ChangeInternalTransform method.
4. Redraw the scrolled frame, as shown in the following figure and discussed
in Drawing when a Part Is Scrolled.
For more information on redrawing frames that include scroll bars or other
adornments, see Drawing with Scroll Bars. For more information on handling
scroll bars, interpreting events in them, and redrawing frames that include
scroll bars or other adornments, see Scrolling.
ΓòÉΓòÉΓòÉ 6.2.2.2. Transforming the Image of your Part ΓòÉΓòÉΓòÉ
If the graphics system used by your part editor supports all transformations,
you can simply modify the internal transform of your display frame to achieve
the kinds of special effects shown in the figure in Transforms and Coordinate
Spaces. By modifying the internal transform appropriately, you not only can
position your part's content in its display frames, but you can also scale,
rotate, skew, and apply perspective to it. The OS/2 GPI supports all types of
transforms except perspective (see the figure in Transforms and Coordinate
Spaces).
For example, you could change the scale of your part's content-to support
either higher precision in positioning or larger total content area than would
otherwise be possible-by including a scale factor in your display frames'
internal transforms. Then, to leave embedded parts' displays unaffected by the
scaling, you could apply the inverse scaling factor to the external transforms
of all embedded facets.
Even if your graphics system does not support features such as rotation or
skew, you can still "pre-transform" your images by first applying the internal
transform to all drawing coordinates, before passing them to the drawing
commands.
ΓòÉΓòÉΓòÉ 6.2.2.3. Positioning an Embedded Frame ΓòÉΓòÉΓòÉ
In general, your containing part can store information on the shapes and
positions of embedded frames in any format that is convenient for you. When
frames become visible, however, OpenDoc needs that positioning information to
allow it to dispatch events correctly and to correctly place the drawn images
of each embedded part.
Thus, when you create a facet for a visible frame embedded in your part, you
assign it (when calling the CreateEmbeddedFacet method) an external transform
whose offset reflects the position of the embedded frame in the coordinate
system of your part content. If you later reposition that frame, you need to
modify its facet's external transform, if the frame is visible. You can change
a facet's external transform, as well as its clip shape, by calling its
ChangeGeometry method.
ΓòÉΓòÉΓòÉ 6.2.2.4. Transforming the Image of an Embedded Part ΓòÉΓòÉΓòÉ
You can use the external transform of a facet embedded in your part to achieve
special display effects, regardless of the display intention of the embedded
part. By modifying the external transform appropriately, you not only can
position the frame within your part's displayed content but can also scale,
rotate, skew, and apply perspective to it.
For example, you could make separate embedded frames appear to be the faces of
a cube, giving each the proper skew or perspective necessary to achieve the
effect, regardless of what is being drawn in each frame by its own part editor.
ΓòÉΓòÉΓòÉ 6.2.3. Using Drawing-Related Shapes ΓòÉΓòÉΓòÉ
A shape object is an OpenDoc object that is an instance of the class ODShape.
It is the specification of a two-dimensional shape in a given coordinate space.
Different platforms and different graphics systems define shapes differently.
Objects of the class ODShape are partly wrappers for system-specific shape
structures, but they also have the ability to convert among several common
shape structures.
OpenDoc uses shape objects for clipping purposes in drawing and hit-testing.
There are four drawing-related shapes: the frame shape and used shape, attached
to the frame object; and the clip shape and active shape, attached to the facet
object.
ΓòÉΓòÉΓòÉ 6.2.3.1. Frame Shape ΓòÉΓòÉΓòÉ
The frame shape is discussed in many places in this book, including earlier in
this chapter, in Frame and Facet Hierarchies. The frame shape represents the
fundamental contract between embedded part and containing part for display
space. The other drawing-related shapes are variations on the frame shape, and
all are defined in the same coordinate system as the frame shape.
A frame shape is commonly rectangular, as shown in the following figure, but it
can have any kind of outline, even irregular. An embedded part can request a
nonrectangular frame shape if it has a special need for one; for example, a
part that displays a clock face might request a round frame shape. It is the
containing part's right to comply with or deny that request, and it is also the
containing part's responsibility to draw that frame's selected appearance,
including resize handles, if appropriate.
The frame shape is defined by the containing part and is stored in the frame
object. Your part (the containing part) can set an embedded frame's frame shape
by calling the frame's ChangeFrameShape method. Any caller may access the frame
shape by calling the frame's AcquireFrameShape method.
By convention, your part should use the frame shape when drawing the selected
frame border of an embedded part.
Cross-platform representation
The frame shape is stored persistently, and your part may subsequently be
displayed under a different graphics system. Therefore, it is important that a
frame shape have a platform-neutral (that is, polygonal) representation when it
is stored.
ΓòÉΓòÉΓòÉ 6.2.3.2. Used Shape ΓòÉΓòÉΓòÉ
An embedded part may need a nonrectangular shape to draw in, may need to change
frame shape often, or may want to allow the containing part to be able to draw
within portions of its frame. In these cases, the embedded part need not
negotiate for an unusual frame shape or continually renegotiate its frame size.
Instead it can define a used shape to tell its containing part which portions
of its frame shape it is currently using. In the following figure, for example,
the embedded part retains a rectangular frame shape but defines a used shape
that covers only the content elements it draws. The containing part can then
use that information to, perhaps, wrap its content to the used portions of the
embedded part. For example, the third figure in Event Handling shows a
containing part (a text part) that wraps its text closely to the used shape of
an embedded part (a pie-chart part whose frame shape is actually rectangular).
The used shape is defined by the embedded part, specified in frame coordinates,
and stored in the frame object. Any caller may access the used shape by calling
the frame's AcquireUsedShape method. If the embedded part has not stored a used
shape in its frame, AcquireUsedShape returns the frame shape as a default.
Note that it does not make sense for the used shape to extend beyond the edges
of the frame shape, because the clip shape (described on Clip Shape) is based
on the frame shape and no drawing occurs outside of the clip shape.
Your part can set its display frame's used shape by calling the frame's
ChangeUsedShape method. (You can pass a null shape to ChangeUsedShape to make
your used shape identical to your frame shape). When you do, OpenDoc then calls
the UsedShapeChanged method of your containing part:
void UsedShapeChanged(in ODFrame embeddedFrame);
If your part is the containing part in this situation and has wrapped its
content to the embedded part's used shape, you can use this method to adjust
your content to the new used shape.
ΓòÉΓòÉΓòÉ 6.2.3.3. Active Shape ΓòÉΓòÉΓòÉ
A facet's active shape is the area within a frame in which the embedded part is
willing to receive geometry-based (mouse) events. The active shape of a facet
is often identical to either the frame shape or the used shape of its frame, as
shown in the following figure. The active area of a part is likely to coincide
with the area it draws in; events within the frame shape but outside of the
used shape are better sent to the containing part, which may have drawn in that
area.
The active shape is defined by the embedded part, is specified in frame
coordinates, and is stored in the facet object. Your embedded part can set the
active shape of its display frame's facet by calling the facet's
ChangeActiveShape method. Any caller may access the active shape by calling the
facet's AcquireActiveShape method. If the embedded part has not stored an
active shape in its facet, AcquireActiveShape returns the frame's shape.
Note that the effective active shape-the active shape as the user perceives
it-is the intersection of the active shape and the clip shape (described next).
Events within the area of obscuring content or other frames that block the
active shape are not passed to the embedded part.
OpenDoc uses the active shape when drawing the active frame border of an
embedded part.
ΓòÉΓòÉΓòÉ 6.2.3.4. Clip Shape ΓòÉΓòÉΓòÉ
A facet's clip shape defines the portion of a frame that is actually to be
drawn. The clip shape of a facet is commonly identical to the frame shape of
its frame, except that it may be modified to account for obscuring content
elements or overlapping frames in the containing part, as shown in the
following figure.
The clip shape is defined by the containing part, it is specified in frame
coordinates, and it is stored in the facet object. Your part (the containing
part) can set the clip shape of an embedded frame's facet by calling the
facet's ChangeGeometry method. Any caller may access the clip shape by calling
the facet's AcquireClipShape method.
ΓòÉΓòÉΓòÉ 6.2.4. Managing Facet Clip Shapes ΓòÉΓòÉΓòÉ
This section describes how your part adjusts the clip shapes of its embedded
frames' facets before drawing, to account for overlapping relationships and for
the active frame border that OpenDoc draws.
ΓòÉΓòÉΓòÉ 6.2.4.1. Clipping Embedded Facets and Intrinsic Content ΓòÉΓòÉΓòÉ
Sibling frames are frames embedded at the same level within a containing frame.
They may be frames in a frame group (described in Creating Frame Groups), or
they may be unrelated frames belonging to different embedded parts. Because
sibling frames can overlap each other and can overlap (or be overlapped by)
intrinsic content of the containing part, it is the responsibility of the
containing part to ensure that clipping occurs properly. The following figure
shows examples of the overlapping relationships that can occur.
ΓòÉΓòÉΓòÉ 6.2.4.2. List of Embedded Facets ΓòÉΓòÉΓòÉ
If your part contains embedded frames, it must maintain a z-ordered
(front-to-back) list of the embedded frames and content elements, so that you
can reconstruct the overlapping relationships among them. You use that list to
update the clip shapes of the facets of your embedded frames.
You control the z-ordering among sibling frames embedded in your part, and you
can communicate that information to OpenDoc through the MoveBehind and
MoveBefore methods of the containing facet of the sibling frames' facets. The
facet positioning you achieve with MoveBehind and MoveBefore is reflected in
the order in which you or OpenDoc (or any caller) encounters facets when you
use a facet iterator (class ODFacetIterator) to access each of the sibling
facets in turn (as, for example, when calculating a resulting clip shape).
ΓòÉΓòÉΓòÉ 6.2.4.3. Calculating the Clip Shapes ΓòÉΓòÉΓòÉ
If your part contains embedded frames, it performs two related tasks when
managing its embedded facets' clip shapes.
It must clip all of its embedded facets so that sibling embedded parts
don't improperly overwrite each other, the containing part's intrinsic
content, or the active frame border. (Adjusting the Active Frame Border
describes how to account for the active frame border).
It must clip its own intrinsic content so that it doesn't improperly
overwrite embedded parts or the active frame border. This also means
taking drawing responsibility in the area of an embedded frame that is
outside the embedded frame's used shape; because the embedded part does
not draw outside of its used shape, it is the containing part's
responsibility to do so.
In calculating the clip shapes for your embedded parts' facets, remember these
points:
A facet's clip shape is affected only by sibling elements, whether they
be items of intrinsic content or facets of other embedded frames. Your
own display facet's clip shape, and the clip shapes of facets embedded
within your embedded parts, have no effect.
When one embedded facet obscures another, use the used shape of the
obscuring facet's frame to clip the obscured facet.
When intrinsic content obscures an embedded facet, make sure to account
for all of it-including selection handles, the active frame border, and
other adornments.
You need to recalculate your embedded parts' clip shapes whenever a
content element or an embedded frame is added, deleted, moved, adorned
with resize handles, or modified in any way that affects how it is drawn.
However, when a given item changes, you need only to recalculate the
clips of it and the elements behind it; the clipping of elements lower in
z-order is unaffected.
Your routine to recalculate embedded-facet clip shapes could take steps
similar to these:
1. Create a "working clip," a clip shape that at each point in the
calculations represents the total area that is not obscured of your
display facet. Start it as a copy of your own display frame's used shape;
only content elements within that area need to have their clip shapes
recalculated.
2. If the currently active frame is embedded in your part, subtract the
active frame border from your working clip. (See Adjusting the Active
Frame Border).
3. Iterate through all your z-ordered content elements, front to back. For
each element that is an item of intrinsic content, do this:
Calculate a new clip shape, representing the visible portion of the
item, by intersecting the item's shape with the current working
clip. Store the new clip shape according to your own content model.
Calculate a new mask shape that includes both the (clipped) content
item shape and any adornments (such as selection handles) it may
have. Modify the working clip by subtracting that mask shape from
it, so that elements behind this content item will be obscured.
Likewise, for each element that is an embedded facet, do this:
Get the frame shape for the facet's frame and convert it to your own
content coordinates. Intersect it with the current working clip (to
account for obscuring content in front), convert it back to
embedded-frame coordinates, and assign it as the facet's new clip
shape.
Calculate a new mask shape by getting the used shape for the facet's
frame and converting it to your own content coordinates. Modify the
working clip by subtracting that mask shape from it, so that
elements behind this embedded frame will be obscured.
ΓòÉΓòÉΓòÉ 6.2.4.4. Embedded-Part Responsibilities ΓòÉΓòÉΓòÉ
The containing part is responsible for its embedded facets' clip shapes, but
the embedded part has some responsibilities, to ensure that drawing occurs
properly:
The embedded part must always clip its own drawing operations correctly,
using its aggregate clip shape; see Draw Method of your Part Editor.
The embedded part must never draw outside of its used shape. Only a root
part can draw outside of its used shape.
When a containing part changes the clip shape of one of its embedded
frame's facets, OpenDoc notifies the embedded part of the change by
calling the embedded part's GeometryChanged method. If the embedded part
is drawing asynchronously (see Asynchronous Drawing), its next
asynchronous draw must use the new clip shape.
ΓòÉΓòÉΓòÉ 6.2.4.5. Adjusting the Active Frame Border ΓòÉΓòÉΓòÉ
When a partcsq.s frame is active, OpenDoc draws the active frame border (shown
in the following figure) around the frame. Neither the active part itself nor
its containing part need to draw the border.
However, the active frame border occupies an area a few pixels wide in the
content area of the active partcsq.s containing part, and it may overlap or be
overlapped by other content elements (embedded frames or intrinsic content) of
the containing part (see the following figure). Therefore, it is up to the
containing part to make sure that the active frame border is appropriately
clipped by elements in front of the active frame, and that elements behind the
active frame are appropriately clipped so that they do not draw themselves
within the border area.
When a new frame acquires the selection focus, or when the active frame changes
its frame shape, OpenDoc calculates a new active border shape. It then passes
that shape to the active part's containing part, by calling the containing
part's AdjustBorderShape method:
ODShape AdjustBorderShape(in ODFacet embeddedFacet,
in ODShape shape) ;
Your AdjustBorderShape method has two tasks: clip the active border shape to
account for obscuring content, and clip any of your own content that is
obscured by the border.
When the active frame embedded in your part becomes inactive and thus no longer
has the active border around it, OpenDoc notifies your part of the change by
calling your part's AdjustBorderShape method once more, this time passing a
null value for the shape parameter. You can then remove the clipping you had
previously applied to your obscured content.
Note: If your part receives several consecutive calls to its AdjustBorderShape
method, it means that your embedded (active) frame has several facets.
Consider the active border shape to be the union of all the provided
shapes.
In your AdjustBorderShape method, you can take steps such as the following.
These steps assume that you maintain a field in your part that is a copy of
the clipped active frame border, and that your embedded-frame clipping method
(see Managing Facet Clip Shapes) uses that active-border field to clip your
content elements.
1. If the shape parameter is null, you no longer need to maintain an active
frame border. Release the border shape you had maintained, set your
active-border field to null, call your embedded-frame clipping method to
remove the clipping that had been caused by the presence of the active
frame border, and exit.
2. If the shape parameter is non-null, copy the shape, convert it to your
own content coordinates, and intersect it with your own used shape, so
that the active border will not be drawn outside of your used shape.
3. To calculate the resultant clipped border shape, iterate through all your
z-ordered content elements, starting from the facet of the active frame
and working toward the front. (Only items in front of the facet can clip
it).
For each element that is an item of intrinsic content, calculate a
mask shape that includes both the item's shape and any adornments
(such as selection handles) it may have. Subtract that mask shape
from your active-border shape.
For each element that is an embedded facet, calculate a mask shape
by getting the used shape for the facet's frame, converting it to
your own content coordinates, and subtracting it from your
active-border shape.
4. If your active-border field is currently null, place the resulting
active-border shape in it. If it is currently non-null, you are
calculating a composite border shape from several facets of the active
frame; in that case, union the resulting active-border shape with the
shape already in the field.
5. Call your embedded-frame clipping method (see Managing Facet Clip
Shapes), so that it will recalculate the clip shapes of all items behind
the active frame, making sure that they don't overwrite it.
If your AdjustBorderShape method does nothing and simply passes back the shape
it receives, it must increment the shape's reference count before returning
it.
ΓòÉΓòÉΓòÉ 6.3. Drawing ΓòÉΓòÉΓòÉ
Fundamental to OpenDoc is the responsibility of each part in a compound
document to draw itself, within the limits of the frame provided by its
containing part. Drawing typically occurs when your part editor is asked to
draw a particular facet of a particular frame in which its part is displayed.
Your part editor is responsible for examining that facet and frame and
displaying the correct data, with the appropriate representation, properly
transformed and clipped.
Before drawing, your part editor should, if necessary, update the used shapes
of its frames and the active shapes of its facets, so that the containing part
can lay itself out correctly and so that only the proper events are dispatched
to your part by the document shell.
This section begins by discussing how your part defines the general
characteristics of its display and outlining several aspects of the basic
drawing process. The section then discusses:
Drawing with scroll bars
Drawing directly to the window
Asynchronous drawing
Offscreen drawing
Use of multiple frames and facets
Storing cached representation of your frames' content
ΓòÉΓòÉΓòÉ 6.3.1. Defining General Display Characteristics ΓòÉΓòÉΓòÉ
OpenDoc parts can display themselves in different ways in different frames or
in different ways in the same frame at different times. The part is in control
of its display within the borders of its frames, but there are conventions for
other parts to request that the part display itself in a particular way.
There are two kinds of display categories. The view type of a frame indicates
whether the part within it is shown as one of several kinds of icons (standard
icon, small icon, or thumbnail) or whether the part content itself is drawn
within a frame. Some view types are standard values defined by OpenDoc; any
part should be able to draw itself in any of the standard view types. You can
define your own view types also.
The presentation of a frame describes, for parts whose view type is framed, how
the content is to be represented within the frame. Presentations are
part-defined. You define what types of presentations your part supports and you
assign their values. Examples of presentations are top view, side view,
wireframe, full rendering, table, bar chart, pie chart, and tool palette.
View type and presentation are represented as tokenized ISO strings of type
ODTypeToken. If you create a presentation type, you define it first as an ISO
string and then use the session object's Tokenize method to convert it to a
token.
ΓòÉΓòÉΓòÉ 6.3.1.1. View Type ΓòÉΓòÉΓòÉ
You can get and set the view type of your own frames by calling the frame's
GetViewType and SetViewType methods, respectively.
In general, a part is expected to adopt the view type specified by its
containing part. However, another part (such as your part's containing part)
can change your frame's view type at any time, and you can change the view type
of a frame of another part (such as one of your part's embedded parts) at any
time, by calling the frame's ChangeViewType method. ChangeViewType sets the
view type and then notifies the owning part, by calling the ViewTypeChanged
method:
void ViewTypeChanged(in ODFrame frame);
If your part receives this call, it should display itself in the specified
frame according to the indicated view type. Parts must support all standard
view types. If your part does not support the requested view type, call your
display frame's SetViewType method to change it back to a view type that you
support. (Calling SetViewType does not result in a call to your ViewTypeChanged
method).
Note that a change in view type can mean a change in the optimum size for your
display frame. Your ViewTypeChanged method might therefore engage in frame
negotiation with your part's containing part at this point.
ΓòÉΓòÉΓòÉ 6.3.1.2. Presentation ΓòÉΓòÉΓòÉ
You can get and set the presentation of your own frames by calling the frame's
GetPresentation and SetPresentation methods, respectively.
Note that another part can change your frame's presentation, or you can change
the presentation of another part's frame, by calling the frame's
ChangePresentation method. In response, ChangePresentation sets the
presentation and then notifies the owning part, by calling the part's
PresentationChanged method:
void PresentationChanged(in ODFrame frame);
If your part receives this call, and if it supports the indicated presentation,
it should display itself in the specified frame accordingly. If it recognizes
the indicated presentation but does not support it, or if it does not recognize
it, your part should instead pick a close match or a standard default. It
should then call the frame's SetPresentation method to give it the correct
value. (Calling SetPresentation does not result in a call to your
PresentationChanged method).
ΓòÉΓòÉΓòÉ 6.3.1.3. Part Info ΓòÉΓòÉΓòÉ
The part info data of a frame is a convenient place for your part to store
information about that particular view of itself. The information can be
anything from a simple ID to a pointer to a complicated structure or a
reference to a helper object. A frame's part info is stored with the frame, not
the part. Thus, if the part has many frames and if only a few have been read
into memory, only the part info of those frames will be taking up space in
memory.
To assign information to a frame's part info field, use its SetPartInfo method;
to retrieve its part info, use its GetPartInfo method. Writing a frame's part
info to its storage unit and reading it back in are described in Reading and
Writing Part Info.
The facet object also contains a part info field, which can hold any kind of
display-related information that you wish, such as transforms, color
information, pen characteristics, or any other imaging-related data.
To assign information to a facet's part info field, use its SetPartInfo method;
to retrieve its part info, use its GetPartInfo method. (A facet's part info is
not persistently stored, so there are no methods for reading and writing it).
ΓòÉΓòÉΓòÉ 6.3.2. Basic Drawing ΓòÉΓòÉΓòÉ
This section discusses how your part editor performs its drawing tasks in
typical situations.
ΓòÉΓòÉΓòÉ 6.3.2.1. Setting Up ΓòÉΓòÉΓòÉ
To set itself up properly for display, your part should have previously
examined the following information in the given facet and its frame:
The view type of the frame tells your part whether it should display
itself as an icon or whether it should show its contents. Your part
should be ready to display itself in the specified frame according to the
frame's view type.
The presentation of the frame tells your part what kind of presentation
(as defined by your own part) its content is to have, if it has frame
view type. If your part does not support the frame's presentation,
substitute a default presentation and call the frame's SetPresentation
method to give it the correct value.
Your part may be within a selection in its containing part. If so, it may
need to be highlighted appropriately for the selection model of the
containing part; see Drawing Selections for more information. You can
call the GetHighlight method of your facet at any time to see whether you
should draw your content with full highlighting, dim (background)
highlighting, or no highlighting.
Alternatively, when your part's HighlightChanged method is called, it can
record the style of highlighting it has been assigned. It can then in
turn call the ChangeHighlight method of all facets embedded in the facet
whose highlight has changed, to make sure that they reflect the same
highlight style.
Check to see if you need to draw borders around any link sources and
destinations in your part's content, as described in Link Borders.
Both the frame and the facet may have information in their part info
fields, placed there by the part itself, that can provide additional
display-related information or objects. Use the GetPartInfo method to
obtain it.
Your part should also be aware of the kind of canvas on which it is being
drawn. You can call the GetCanvas method of the facet to get a reference
to the drawing canvas.
- If the canvas is dynamic (that is, if the result of calling the
canvas's IsDynamic method is kODTrue), the part is being drawn onto
an interactive device like a video display. Otherwise, it is being
drawn to a static device like a printer (or perhaps a print-preview
image onscreen). Your part may display its content differently in
each case; for instance, it might display scroll bars only on
dynamic canvases, and it might engage in frame negotiation if it is
being drawn on a static canvas. For considerations about static
canvas, see Printing.
Your part can make these adjustments from within its FacetAdded and
CanvasChanged methods. When a facet is added, the static/dynamic
nature of its canvas is fixed and cannot be changed unless the
canvas itself is changed.
- If the canvas is offscreen (that is, if the result of calling the
canvas's IsOffscreen method is kODTrue), your part might handle the
drawing slightly differently. See Offscreen Drawing.
ΓòÉΓòÉΓòÉ 6.3.3. Draw Method of your Part Editor ΓòÉΓòÉΓòÉ
Your part must be able to display its content in response to a call to its Draw
method:
void Draw(in ODFacet facet, in ODShape invalidShape);
When it receives this call, your part editor draws the part content in the
given facet. Only the portion within the update shape (defined by the
invalidShape parameter) needs to be drawn. The update shape is based on
previous Invalidate calls that were made involving this facet or containing
facets, or on updating needed because of part activation or window activation.
The shape is expressed in the coordinate system of the facet's frame.
Your part should take steps such as these to draw itself:
1. Your part must make sure that any required platform-specific graphics
structures are prepared for drawing and that the drawing context for the
facet is set correctly. You must at least do the following:
Obtain a handle to the presentation space to be used for drawing.
You do this by calling the canvas' GetPlatformCanvas method to
obtain the platform canvas object for the canvas and then call the
GetPS method of the platform canvas.
Set the default viewing transform of the presentation space. Call
your facet's AcquireContentTransform method to get the proper
conversion from your part's content coordinates to canvas
coordinates, convert the transform to a MATRIXLF structure and pass
the MATRIXLF structure to the GpiSetDefaultViewingTransform
function.
Set the clip region for drawing your part. Call your facet's
AcquireAggregateClipShape method to transform and intersect your
facet's clip shape with all enclosing clip shapes, resulting in a
composite clip shape in canvas coordinates. Then, transform the
aggregate clip shape to canvas coordinates and call the shape
object's GetRegion method to obtain a GPI region handle. Use this
handle to set the clip region with the GpiSetClipRegion function.
OpenDoc provides a utility library (FocusLib) that facilitates this
step for the OS/2 platform. The source code for FocusLib is provided
with the OpenDoc Programmer's Toolkit.
2. If your part is drawing to an offscreen canvas and has embedded parts,
have the embedded parts draw themselves first; see Updating an Offscreen
Canvas.
3. Your part can then draw its contents, using platform-specific drawing
commands.
Your part can alter the normal order of drawing, perhaps in order to make
overlapping embedded parts and content elements combine to achieve a
specific visual effect. You can force the drawing of a given embedded
part at any point by calling the embedded facet's Draw method. Calling
the embedded facet's DrawChildren method in addition forces the embedded
part's own embedded parts to draw themselves immediately. From within
your parts draw method, you may also call the update method of your
embedded facets, specifying KODNULL for the invalid shape. This will
cause the embedded facet, and any of its embedded facets, and so on, to
be updated as needed based on the current invalid shape.
Note: Never draw outside of the used shape of your display frame. Your part's
containing part may be wrapping its content to the edges of your used
shape.
ΓòÉΓòÉΓòÉ 6.3.3.1. Invalidating and Validating your Content ΓòÉΓòÉΓòÉ
To mark areas of your part's content that need redrawing, you modify the update
shape of the canvas on which your part is imaged. To do so, you call the
Invalidate method of your display frames or their facets, passing a shape
(invalidShape), expressed in your own frame coordinates, that represents the
area that needs to be redrawn. OpenDoc adds that shape to the canvas's update
shape, and when redrawing occurs again, that area is included in the
invalidShape passed to your Draw method.
Likewise, to remove previously invalid areas from the update shape of the
canvas, you call the Validate method of your frame or facet, passing a shape
(validShape) that represents the area that no longer needs to be redrawn.
Validating is not a common task for a part editor to perform; OpenDoc
automatically validates all areas that you draw into with your Draw method, and
when you draw asynchronously, you automatically validate the areas that you
have redrawn when you call the DrawIn method of the facet.
ΓòÉΓòÉΓòÉ 6.3.3.2. Drawing on Opening a Window ΓòÉΓòÉΓòÉ
When a window opens, the facets for all the visible parts of the document are
created when the window's Open method is called. The root part's FacetAdded
method is called; it builds facets for its embedded frames and invalidates
them. OpenDoc, in turn, calls the embedded parts' FacetAdded methods, so that
they can build facets for their own embedded parts, and so on.
Each part editor whose frame is visible in the window (that is, whose frame has
an associated facet) thus receives a call to its Draw method. The method draws
the contents of its frame into the facet, by either using privately cached
presentations or making standard drawing calls, taking into account the frame's
and facet's aggregate transforms and clip shapes.
ΓòÉΓòÉΓòÉ 6.3.3.3. Drawing when a Window Is Scrolled ΓòÉΓòÉΓòÉ
When the entire contents of a window is scrolled, the scrolled position of the
contents is determined by the value of the internal transform of the root
part's frame. If any of its embedded frames are made newly visible or newly
invisible by scrolling, the root part should create or destroy their facets by
calling the root facet's CreateEmbeddedFacet or RemoveFacet method, as
described in Adding a Facet and Removing a Facet.
To force redrawing of those parts of the window that need to be redrawn after
scrolling, the root part marks the area that needs to be redrawn as invalid, by
calling the root facet's Invalidate method, so that it will be redrawn when an
update event occurs.
ΓòÉΓòÉΓòÉ 6.3.3.4. Redrawing a Frame when It Changes ΓòÉΓòÉΓòÉ
If your part changes an embedded frame's shape, you may need to refresh its
presentation as follows:
1. Change the frame's shape, as appropriate, by calling ChangeFrameShape.
(OpenDoc then calls the embedded part's FrameShapeChanged method, passing
it the new frame shape).
2. Change the clip shape-and external transform, if necessary-of the frame's
facet to correspond to the new frame shape.
3. If there is an undrawn area outside of the new facet shape, invalidate
the portions of your own intrinsic content that correspond to the
difference between the old and new frame shapes.
When the next update event is generated, the Draw methods of the embedded part
and your part, as appropriate, are called to draw the invalidated areas.
ΓòÉΓòÉΓòÉ 6.3.3.5. Drawing when a Part Is Scrolled ΓòÉΓòÉΓòÉ
When a user scrolls the contents of an embedded part, the containing part takes
no role in the repositioning or redrawing. The embedded part sets its frame's
internal transform to the appropriate value, as discussed in Scrolling your
Part in a Frame, and calls the facet's Invalidate method to force the redraw.
If you are the part whose contents are to be scrolled, take these steps:
1. Call your frame's SetInternalTransform method, to set the transform's
offset values appropriately. How you obtain the offset values you pass to
SetInternalTransform depends on what kinds of events you interpret as
scrolling commands and how you handle them. For example, handling events
in scroll bars within your display frames is discussed in Scrolling.
2. If any embedded frames become visible or invisible as a result of the
scrolling, create or delete facets for them as described in Adding a
Facet and Removing a Facet.
3. Call your facet's Invalidate method to force a redraw at the next update
event. You should be able to shift the bits of your frame's image by the
scrolled amount and invalidate only the portion of your facet that
represents part content scrolled into view.
Drawing the contents of frames that include scroll bars is more complicated,
for two reasons. First, the content must be clipped so that it won't draw over
the scroll bars. Second, although the content can be scrolled, the scroll bars
themselves should not move. See Drawing with Scroll Bars for more information.
ΓòÉΓòÉΓòÉ 6.3.3.6. Drawing Selections ΓòÉΓòÉΓòÉ
You determine how selecting works and how selections are drawn inside your
parts.
When a part embedded within your part is selected, the appearance you give it
should be appropriate for your own selection model but it should also reflect
whether the part is selected by itself or is part of a larger selection that
includes your own part's intrinsic content:
To select an embedded part alone when it is viewed in a frame, the user
can perform several actions, such as activating the part and then
clicking on its frame border or using a lasso tool or other selection
method supported by the containing part. (Your part should support all
selection methods that are appropriate for your content model. Note that
when a frame is selected, its part is not active; the menus displayed are
those of the containing part).
If the part alone is selected, your part (the containing part) should
draw the frame border with a standard appearance-typically a gray line,
corresponding to the frame shape, with resize handles (usually eight of
them) if you permit the user to resize the frame. If you allow
nonrectangular frames (such as irregular polygons or circles), you are
responsible for putting an appropriate number of resize handles on the
frame border.
To select an embedded part alone when it is viewed as an icon, the user
places the mouse pointer over a part's icon (that is, anywhere within the
active shape of the facet displaying the icon) and clicks the mouse
button. Because the view type of the facet is iconic rather than framed,
OpenDoc sends the mouse event to your part.
You should, in this case, not draw the frame border of the embedded part
at all. Instead you should notify the part that it is highlighted by
calling the ChangeHighlight method of the embedded part's facet,
specifying a highlight value of kODFullHighlight. It is then up to the
embedded part to draw its highlighted appearance, either by using a
highlight color or displaying the selected version of its icon.
When an embedded part is enclosed within a range of your selected
intrinsic content, its appearance should be analogous to that of the
intrinsic content itself. You should draw the frame border with a
selected appearance, if appropriate, and you should call the
ChangeHighlight method of the embedded part's facet, so the part will
know how it should highlight itself.
- If your part highlights selections with a highlight color or inverse
video (as text processors typically do), you should not draw frame
borders around embedded parts within your selection, and you should
set their facets' highlight value to kODFullHighlight.
- If your part highlights selections by drawing frame borders around
individual objects (as object-based drawing programs typically do),
you should draw frame borders with a selected appearance around any
embedded parts, and you should set their facets' highlight value to
kODNoHighlight.
- If your part highlights selections by drawing a dynamic marquee or
lasso border around the exact area of the selection (as
bit-map-based drawing programs typically do), you should not draw
frame borders around embedded parts, and you should set their
facets' highlight value to kODNoHighlight.
Your part should allow for multiple selection, so the user can select more
than one frame at a time, and it should also support Select All on its own
contents. Furthermore, it should allow for selection of hot parts-parts such
as controls that normally perform an action (such as running a script) in
response to a single click-without executing the action.
ΓòÉΓòÉΓòÉ 6.3.4. Drawing with Scroll Bars ΓòÉΓòÉΓòÉ
This section discusses two methods for providing scroll bars for your parts.
Note that these approaches work not only for scroll bars, but also for any
nonscrolling adornments associated with a frame.
ΓòÉΓòÉΓòÉ 6.3.4.1. Placing Scroll Bars within your Frame ΓòÉΓòÉΓòÉ
If you create scroll bars for your content inside the margins of your frame,
remember that your frame shape includes the areas of the scroll bars. To draw
properly, you need to account for the fact that, although your content can
scroll, the scroll bars themselves should remain stationary.
Note: The method discussed in this section does not apply to a root part where
the scroll bars are part of the document shell window frame control and
are not within the root parts display frame.
Clipping and scrolling your content
One approach is to create an extra, private "content shape," as shown in the
following figure.
You can define the content shape in terms of frame coordinates, and you can
store it anywhere, although placing it in your frame's part info data is
reasonable.
When drawing your part's contents-the portion within the area of the content
shape-you take the current scrolled position of your content into account. You
include the frame's internal transform by using the transform returned by
GetContentTransform to set the origin, and you use the content shape as the
facet's clip shape when drawing. If you will draw your own scroll bars, you
ignore the current scrolled position of your content when drawing them. You
use the transform returned by GetFrameTransform to set the default viewing
transform, and you use the frame shape as the facet's clip shape. If you are
using the PM scroll bar control window, you need to position the scroll bar
window using the frame's external transform and set the window clip region, as
described in the recipes Facet Windows and Using Embedded PM Controls within a
Facet. You should call WinUpdateWindow in your Draw method, specifying the
handle of the scroll bar window after drawing your content.
Clipping embedded frames
One complication of this method is that OpenDoc knows nothing of your content
shape and therefore cannot clip the facets of embedded frames accordingly.
OpenDoc clips all embedded facets to the area of your frame's facet, but that
area includes the scroll bars as well. Thus, embedded parts can draw over the
area of your scroll bars.
To avoid this problem, you must intersect the clip shapes of each embedded
facet with your content shape before the facet draws itself.
ΓòÉΓòÉΓòÉ 6.3.4.2. Placing Scroll Bars in a Separate Frame ΓòÉΓòÉΓòÉ
To avoid having to define a separate, private "content shape," you can place
scroll bars or adornments in completely separate frames from your content. This
method, however, requires the overhead of defining more objects, uses more
memory, and may possibly affect performance negatively.
You can request one display frame for your part that encloses both the scroll
bars and the content. This frame has an internal transform of identity; it does
not scroll. If you are drawing your own scroll bars, draw the scroll bars
directly in this frame.
You then request a second display frame for your part, a frame that can scroll
and in which you draw your content. Make the second frame a subframe of the
first; that is, make the nonscrolling frame the containing frame of the
scrolling frame. Because both frames are display frames of the same part
(yours), you must either set the isSubframe parameter to true when you first
call RequestEmbeddedFrame to create the new frame, or you must call the
SetSubframe method of the new frame after creating it. Either method lets
OpenDoc know that the frame is a subframe, so that OpenDoc draws the active
frame border in the correct location-around the facet of the parent frame.
Your part's containing part, as usual, must make separate facets for both
frames. Then, when a mouse event occurs in the nonscrolling frame, your part
can interpret it and set the internal transform of the scrolling frame
accordingly.
With this method, you do not need to take any special care to manage clip
shapes of any embedded facets.
A more common use of subframes may be in creating scrollable split windows. See
Drawing with Multiple Frames or Facets form=normal.
ΓòÉΓòÉΓòÉ 6.3.5. Drawing Directly to the Window ΓòÉΓòÉΓòÉ
If your part editor needs to draw interactively, such as for rubber-banding or
for sweeping out a selection while the mouse button is held down to provide
user feedback, it can draw directly on the window of the document shell.
OpenDoc provides several ODFacet methods, analogous to those used for drawing
to your own canvas, that you can use to draw directly to the window:
The AcquireWindowContentTransform method, analogous to
AcquireContentTransform, converts from your part's content coordinate
space to window-canvas coordinate space.
The AcquireWindowFrameTransform method, analogous to
AcquireFrameTransform, converts from your part's frame coordinate space
to window-canvas coordinate space.
The AcquireWindowAggregateClipShape method, analogous to
AcquireAggregateClipShape, transforms and intersects your part's facet
clip shape with all enclosing clip shapes, resulting in a composite clip
shape in window-canvas coordinate space.
Use the values returned by these methods to set up your drawing environment
when you must draw directly to the window canvas. Note that, in cases where
there is no canvas in the facet hierarchy other than the window canvas, the
results returned by each of these pairs of methods are the same.
ΓòÉΓòÉΓòÉ 6.3.6. Asynchronous Drawing ΓòÉΓòÉΓòÉ
Your part editor may need to display its part asynchronously, rather than in
response to a call to your Draw method. For example, a part that displays a
clock may need to redraw the clock face exactly once every second, regardless
of whether an update event has resulted in a call to redraw. Asynchronous
drawing is very similar to synchronous drawing, except that you should make
these minor modifications:
1. Determine which of your part's frames should be drawn. Your part can have
multiple display frames, and more than one may need updating. Because
your part stores its display frames privately, only you can determine
which frames need to be drawn in.
2. For each frame being displayed, you must draw all facets. The frame's
CreateFrameFacetIterator method returns an iterator with which you can
access all the facets of the frame. (Alternatively, you can keep your own
list of facets). Draw the part's contents in each of these facets, using
the steps listed for synchronous drawing.
3. After drawing in a facet, call its DrawnIn method to tell it that you
have drawn in it asynchronously. If the facet is on an offscreen canvas,
calling DrawnIn allows the drawing to be copied into the window, because
the owning part's CanvasUpdated method gets called.
If your asynchronous drawing needs to apply to embedded parts as well, you can
call your facet's DrawChildrenAlways method, which forces all embedded facets
to redraw themselves, regardless of whether they have been invalidated or not.
ΓòÉΓòÉΓòÉ 6.3.7. Offscreen Drawing ΓòÉΓòÉΓòÉ
There are several situations in which you may want your part to create an
offscreen canvas. For example, you may want to increase performance with
double-buffering, drawing complex images offscreen before transferring them
rapidly to the screen. Alternatively, you may want to perform sophisticated
image manipulation, such as drawing with transparency, tinting, or using
complex transfer modes.
Parts are likely to create offscreen canvases for their own facets for
double-buffering, in order to draw with more efficiency and quality. Parts are
likely to create offscreen canvases for their embedded parts' facets for
graphic manipulation, in order to modify the imaging output of the embedded
part and perhaps combine it with their own output.
Canvases are described in general in Using Canvases;
ΓòÉΓòÉΓòÉ 6.3.7.1. Drawing to an Offscreen Canvas ΓòÉΓòÉΓòÉ
Drawing on an offscreen canvas is essentially the same as drawing on an
onscreen canvas, in these ways:
You set up the environment as usual: you obtain the canvas and its
pointer to the ODPlatformCanvas object, from which you obtain the
presentation space. You use your facet's transform and clip information
as usual.
If you draw asynchronously on your facet's offscreen canvas, you must, as
usual, call the facet's DrawnIn method to ensure proper updating of the
offscreen canvas to the window canvas. If your part's facet is moved to
an offscreen canvas while it is running, OpenDoc calls your part's
CanvasChanged method, so that your part will know to call DrawnIn if it
is drawing asynchronously.
You perform invalidation the same way for onscreen and offscreen
canvases.
Call the Invalidate or Validate methods of your display frames and their
facets, to accumulate the invalid area in the update shape of the offscreen
canvas.
If you want to bypass the offscreen canvas and draw directly to the document
shell window, make sure you obtain the window canvas when setting up the
environment. Then use the appropriate facet calls (such as
AcquireWindowContentTransform instead of AcquireContentTransform) when setting
the default viewing transform and clip region.
ΓòÉΓòÉΓòÉ 6.3.7.2. Updating an Offscreen Canvas ΓòÉΓòÉΓòÉ
The part that owns an offscreen canvas is responsible for transferring its
contents to the parent canvas. Only the part that created the canvas can be
assumed to know how and when to transfer its contents. The canvas may have a
different format from its parent (one may be a bit map and the other a display
list, for example); the owner may want to transform the contents of the canvas
(such as by rotation or tinting) as it transfers, or the owner may want to
accumulate multiple drawing actions before transferring.
When a containing part has placed one of its embedded part's facets on an
offscreen canvas, it should force the embedded part to draw before the
containing part itself draws any of its own contents. This ensures that the
contents of the offscreen canvas are up to date, and can be combined safely
with the containing part's contents when drawn to the onscreen canvas. You can
force your embedded part to draw itself by calling the embedded facet's Draw
method; you can force your embedded part's own embedded parts to draw
themselves by also calling your embedded facet's DrawChildren method.
If an embedded part displays asynchronously and uses Invalidate or Validate
calls to modify the update shape of its offscreen canvas, the offscreen canvas
calls its owning part's CanvasUpdated method to notify it of the change. The
owning part can then transfer the updated content immediately to the parent
canvas, or it can defer the transfer until a later time, for efficiency or for
other reasons.
ΓòÉΓòÉΓòÉ 6.3.8. Drawing with Multiple Frames or Facets ΓòÉΓòÉΓòÉ
OpenDoc has built-in support for multiple display frames for every part and
multiple facets for every frame. This arrangement gives you great flexibility
in designing the presentation of your part and of parts embedded in it. This
section summarizes a few common reasons for implementing multiple frames or
multiple facets.
ΓòÉΓòÉΓòÉ 6.3.8.1. Using Multiple Frames ΓòÉΓòÉΓòÉ
Several uses for multiple frames have already been noted in this book, most of
which involve the need for simultaneous display of different aspects or
portions of a part's content.
A part whose frame is opened into a part window requires a second frame
as the root frame of that part window.
A part that flows text from one frame to another naturally needs more
than one frame.
Parts that allow for multiple different presentations of their content
are likely to display each kind of presentation (top view versus side
view, wire-frame versus rendered, and so on) in a separate frame.
Parts that display scroll bars, adornments, or palettes in addition to
their own content might use separate frames for such items.
In general, the part that is displayed in a set of frames initiates the use of
multiple frames. The containing part does, however, have ultimate control over
the number and size of frames for its embedded parts.
ΓòÉΓòÉΓòÉ 6.3.8.2. Using Multiple Facets ΓòÉΓòÉΓòÉ
The use of multiple facets allows a containing part to duplicate, distort, and
reposition the content displayed in its embedded frames. Normally, even with
multiple facets, all of the facets of a frame display their content within the
area of the frame shape, because the frame represents the basic space agreement
between the embedded part and the containing part. However, the containing part
always controls the facets to be applied to its embedded frames, and so the
containing part can display the facets in any locations it wishes.
ΓòÉΓòÉΓòÉ 6.3.8.2.1. Drawing an Embedded Frame Across Two Display Frames ΓòÉΓòÉΓòÉ
Perhaps the most common reason to use multiple facets for a frame is to show an
embedded frame spanning a visual boundary between separate drawing areas in a
containing frame.
In the following figure, for example, a word-processing part uses a single
display frame and facet for each of its pages. An embedded part's display frame
spans the boundary between page 1 and page 2. The word-processing part then
defines two facets for its embedded frame: one facet in the display for page 1,
and the other in the display for page 2.
ΓòÉΓòÉΓòÉ 6.3.8.2.2. Multiple Views of an Embedded Frame ΓòÉΓòÉΓòÉ
One simple use of multiple facets is for a containing part to provide a
modified view (such as a magnification) in addition to the standard view of an
embedded frame. You can also use multiple facets to tile the area of an
embedded frame with multiple miniature images of the frame's contents.
A similar but more complex example is shown in the following figure. In this
case, multiple facets are used to break up the image in an embedded frame, to
allow the tiled parts to be moved, as in a sliding puzzle.
The clip shapes for the facets in the preceding figure are smaller than the
frame shape and have offset origins so that they cover different portions of
the image in the frame. Initially, as shown on the left, their external
transforms have offsets that reflect the offsets in the shape definitions, and
the facets just fill the area of the frame. Subsequently, as shown on the
right, the containing part can cause any pair of facets to change places in the
frame simply by swapping their external transforms.
ΓòÉΓòÉΓòÉ 6.3.8.2.3. Providing Split-Frame Views ΓòÉΓòÉΓòÉ
One of the most useful implementations of multiple facets may be for the
construction of split-frame views, in which the user can independently view and
scroll two or more portions of a single document. This use of multiple facets
is somewhat more complex than the others presented here, because it also
involves use of subframes, embedded frames that display the same part as their
containing frame. The following figure shows one example of how to use two
subframes to implement a split-frame view.
In the preceding figure, the part to be displayed has one frame (frame 1) and
facet (facet 1) that represent its current overall frame size and display. The
part creates frames 2 and 3 as embedded frames, specifying that the containing
frame for both is frame 1, and also specifying that they are to be subframes of
their containing frame. (Alternatively the part can call the frames'
SetSubframe method to make them subframes). The diagram in the lower right of
shows the object relationships involved, using the object-diagram conventions
described in Run-Time Object Relationships.
The part now needs to negotiate for frames 2 and 3 only with itself. It creates
facets for them, embedded within facet 1. The part can now position the
contents of frame 2 and frame 3 independently, by changing their external
tranforms. It uses the external transforms to place facets 2 and 3 within
facet1 as shown, and the two facets together fill the area of frame 1 and show
different parts of the document.
You can also achieve a split-frame view with a somewhat simpler method that
does not require the use of subframes. In this case, your part must perform
extra work to split its content display. The method relies on multiple facets
only for duplicate display of embedded parts; as the following figure shows,
your part has only a single display frame and facet.
You create two facets (subfacets of your display frame's facet) for each
embedded part that is visible in both halves of the split view. By your own
means, you separate the display of your part's intrinsic content and, at the
same time, you apply the appropriate external transforms to each embedded facet
so that embedded parts appear in their proper locations in each portion of your
split view. (The diagram in the lower right of the preceding figure shows the
object relationships).
ΓòÉΓòÉΓòÉ 6.4. Printing ΓòÉΓòÉΓòÉ
To print a document, the user selects Print from the Document menu, and the
root part displays a print dialog box (see Print document). If the user
confirms the print command, the root part then sets up the environment for
printing and prints the document.
In printing, the printer is represented by a separate canvas object from the
screen display, although part editors in general use normal drawing calls to
print their content.
Because any part can be the root part of a document, all parts should in
general support the printing commands. The root part has greater responsibility
in printing, but all visible embedded parts are asked to draw themselves, and
they can adjust their drawing process for the presence of a static canvas. They
can also directly access the platform-specific printing structure, if
necessary.
Page setup
When your part is the root part, it also handles the Page Setup command.
OpenDoc does not replace a platform's printing interface; OpenDoc objects
provide access to platform-specific structures and provide a context in which
you make platform-specific calls.
This section first discusses the specific printing responsibilities of the root
part, then addresses those of embedded parts, and finally notes aspects of
printing that apply to all parts.
ΓòÉΓòÉΓòÉ 6.4.1. Root-Part Responsibilities ΓòÉΓòÉΓòÉ
If your part is the root part, it defines the basic printing behavior of its
document. It sets things up, makes the platform-specific printing calls, and
performs any frame negotiation that is to occur.
ΓòÉΓòÉΓòÉ 6.4.1.1. Using the Same Frame for Printing as for Drawing ΓòÉΓòÉΓòÉ
If your part's document uses the same page layout for printing as for screen
display, printing is simpler. You use your part's existing display frame (the
window's root frame) as the frame for printing. That way, you need not create
any new frames, including embedded frames. Using your existing display frame
makes most sense in situations where you do not engage in frame negotiation,
because you can reuse the same layout after printing.
ΓòÉΓòÉΓòÉ 6.4.1.2. Using a Separate Printing Frame ΓòÉΓòÉΓòÉ
For greater flexibility, you may want to allow the printed version of your
document to have a somewhat different layout from the onscreen version. For
example, you may want to give embedded parts a chance to remove scroll bars and
resize their frames or otherwise lay themselves out differently. To do that,
and to retain your original layout for post-printing display, you can create a
separate display frame for printing, as described next.
ΓòÉΓòÉΓòÉ 6.4.1.3. Printing the Document ΓòÉΓòÉΓòÉ
After you have followed the steps listed in Print document, and after the user
has confirmed the print dialog, your part is ready to print its document. It is
assumed that you have created a platform-specific printing structure, such as a
presentation space associated with a printer device context on OS/2. You should
then take these general steps:
1. If you do not change layout or support frame negotiation for printing,
skip this step and use your current display frame as the printing frame.
Otherwise, create a separate printing frame, like this:
Call your draft's CreateFrame method to create a new, nonpersistent
frame as a display frame for your part, to be used as the printing
frame.
Also create new frames for each of your part's embedded parts, with
the printing frame as their containing frame.
2. Create an external transform and clip shape for your printing facet. Make
the clip shape equal to your page size.
3. Create a new facet for the printing frame, using the window state
object's CreateFacet method. Assign the transform and clip to the
printing facet.
4. Create a platform canvas object using the facet's CreatePlatformCanvas
method, and specify the printer presentation space.
5. Create a static canvas with the printing facet's CreateCanvas method. Use
the SetPlatformCanvas method to assign the platform canvas object to the
canvas. Create and initialize a PRINTDEST structure, and use the
SetPlatformPrintJob method to assign the printing structure itself to the
canvas.
6. Assign the canvas to the printing facet by calling the facet's
ChangeCanvas method. Notify the printing frame of the presence of the
printing facet by calling its FacetAdded method.
(If you have not created a separate printing frame, each of your embedded
parts with a facet is at this point notified of the presence of a static
canvas and may attempt frame negotiation. As root part, you may permit or
deny the requests).
7. Loop through all pages in your part's content area (or the page range
specified in the job dialog box), and print each one. For each page, do
the following:
Reset your printing facet's clip shape and/or transform so that it
covers this page.
Create facets for your newly visible embedded frames, and release
facets for frames no longer in view. At this point, allow frame
negotiation with your part's embedded parts if you support
negotiation.
Invalidate the printing facet's area and force a redrawing, by
calling the facet's Update method.
8. When finished printing, clean up. Remove the printing facet from the
printing frame and release it. Delete the platform-specific structures
(such as the print job) and the platform canvas object that you have
created. If you have created a printing frame, remove it by calling its
Remove method.
ΓòÉΓòÉΓòÉ 6.4.2. Embedded-Part Responsibilities ΓòÉΓòÉΓòÉ
Embedded parts have these few specific responsibilities in printing.
ΓòÉΓòÉΓòÉ 6.4.2.1. Engaging in Frame Negotiation ΓòÉΓòÉΓòÉ
Your part must handle printing properly when it is an embedded part and is
assigned a new display frame or facet. It should examine the facet's associated
canvas to determine whether it is dynamic or static and perform frame
negotiation if appropriate. On a static canvas, you might engage in a frame
negotiation to make all of your part's content visible, or you might need to
resize your frame to account for elimination of scroll bars or other items that
are appropriate for screen display only.
The best point at which to test for the presence of a static canvas is when
your facet is added or when its canvas changes, not when your Draw method is
called. At drawing time it is too late to engage in frame negotiation.
ΓòÉΓòÉΓòÉ 6.4.2.2. Responding to GetPrintResolution ΓòÉΓòÉΓòÉ
Your part may receive a call to its GetPrintResolution method as a printing job
is being set up. This is its interface:
ODULong GetPrintResolution(in ODFrame frame);
Your GetPrintResolution method should first call the GetPrintResolution method
of any of its own embedded parts that would be visible within the specified
frame. It should then return the highest of all returned values and your own
part's minimum printing resolution (in dots per inch) as the method result.
On OS/2, the print resolution for a print job cannot be set programmatically;
however, a part that is initiating the print job might try to query the
resolution of available printers to find one that is satisfactory.
ΓòÉΓòÉΓòÉ 6.4.3. Issues for All Parts ΓòÉΓòÉΓòÉ
Your part draws to the printer canvas just as it draws to its window canvas.
Your Draw method is called when the facet that displays your part is being
imaged.
When your part draws to a static canvas (use the IsDynamic method of the canvas
to find out), you may not want to draw adornments and interactive features such
as scroll bars that are appropriate for screen display only.
On a static canvas, you may also want to perform color matching to take into
account the characteristics of the device on which you are drawing.
Note that all printing canvases are static, but not all static canvases are for
printing. The presence of a static canvas does not guarantee that a print job
or job object exists. You can call the HasPlatformPrintJob method of the canvas
to find out if a printing structure exists. If it does, it is available through
the GetPlatformPrintJob method of the printing canvas. You might want to access
the print job or job object directly to determine, for example, whether you are
printing on a PostScript printer.
ΓòÉΓòÉΓòÉ 7. User Events ΓòÉΓòÉΓòÉ
This is the third of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes the OpenDoc event-handling
mechanism and discusses how your part editor handles user events to activate
itself and to manipulate its user interface.
It is by handling user events that your part editor interacts with the user and
cooperates with the user in constructing the compound documents that contain
your parts.
OpenDoc distinguishes user events from semantic events, the messages related to
the OpenDoc scripting extension, although user events can be converted into
semantic events. Semantic events are discussed in Semantic Events and
Scripting.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. For additional concepts related to
your part editor's run-time environment, see OpenDoc Run-Time Features.
This chapter starts with a discussion of what user events are, and then shows
how your part can
Handle mouse events to activate itself
Transfer various types of focus to and from itself
Handle display-related events, for purposes such as hit-testing and
scrolling
ΓòÉΓòÉΓòÉ 7.1. About Event Handling in OpenDoc ΓòÉΓòÉΓòÉ
Your OpenDoc part editor is required to respond to a specific set of actions or
messages from OpenDoc that constitute the majority of user interactions with
your parts. In OpenDoc, these messages are called user events.
User events in OpenDoc include mouse clicks and keystrokes, menu commands,
activation and deactivation of windows, and other events available on only some
platforms. This section defines the different types of events and discusses how
your part editor's HandleEvent method handles them.
ΓòÉΓòÉΓòÉ 7.1.1. How User Events Are Handled ΓòÉΓòÉΓòÉ
Certain user events are handled by the dispatcher. The dispatcher accepts the
user events from the underlying operating system through the document shell and
dispatches them to the part editors. The dispatching system is modular and can
be extended to handle new classes of events by adding a dispatch module. Each
dispatch module is responsible for deciding which frame or part should be asked
to handle each event that the module deals with.
When the OpenDoc dispatcher receives an event intended for your part, it
locates a dispatch module for the event. The dispatch module in turn calls
your part's HandleEvent method. (Your part's Draw method can be called
indirectly because of an event, through the Update method of a window or
facet).
Your part becomes the target for a specific type of user event (other than
geometry-based events) by obtaining the focus for that event. The OpenDoc
arbitrator keeps track of which part owns which foci by consulting a focus
module, which tracks, for example, which part is currently active and therefore
should receive keyboard events. Foci and focus modules are described further in
Focus Types.
Geometry-based events, such as mouse clicks, are generally dispatched to the
parts within which they occur regardless of which part currently has the
selection focus-that is, regardless of which part is currently active. This
permits inside-out activation to occur.
Your part receives some of the information about its events in the ODEventData
structure. This structure is described in OpenDoc for OS/2 Programming
Reference.
Other information about the event is passed to your part in an OpenDoc-defined
event-info structure:
For any geometry-based event (mouse event) that is passed to your part, the
coordinates of the event are passed in the where field of the structure. For
events in embedded frames that are passed to your part, the frame and facet
within which the event occurred are passed in the embeddedFrame and
embeddedFacet fields.
ΓòÉΓòÉΓòÉ 7.1.2. Types of User Events ΓòÉΓòÉΓòÉ
Your parts must, in general, handle the following types of user events:
Mouse events
Mouse events in embedded frames
Keyboard events
Menu events
Window events
Activate events
Update events
Null events
Other events
The following sections describe how you handle each of these kinds of events.
ΓòÉΓòÉΓòÉ 7.1.2.1. Mouse Events ΓòÉΓòÉΓòÉ
When the user presses or releases the mouse button while the mouse pointer is
in the content area of an OpenDoc window, the dispatcher finds the correct part
to handle the mouse event by traversing the hierarchy of facets in the window.
The dispatcher searches depth-first (trying the most deeply embedded facets
first) and front-to-back (trying the front-most of sibling facets first). The
dispatcher sends the event to the editor of the first-that is, most deeply
embedded and front-most frame it finds whose active shape contains the pointer
position. In this way, the smallest enclosing frame surrounding the pointer
location receives the mouse event, preserving the OpenDoc inside-out activation
model. None of the part editors of any containing parts in that frame's
embedding hierarchy are involved in handling the event.
When the user presses a mouse button while the pointer is within a facet of
your part, the dispatcher calls your part's HandleEvent method, passing it an
event containing the mouse message.
When the user releases the mouse button while the pointer is within your facet,
the dispatcher again calls your part's HandleEvent method, passing the mouse
message.
The event-dispatching code does not itself activate and deactivate the relevant
parts; it is up to the editor of the part receiving the mouse event to decide
whether to activate itself or not. See Mouse Events, Activation, and Dragging
for information on how your part should handle mouse-down and mouse-up events
within its facets for the purposes of part activation, window activation, and
drag-and-drop.
The dispatcher also tracks pointer position at all times when the mouse button
is not pressed.
When the pointer enters a facet of your part, moves within your facet or
leaves your facet, the dispatcher calls your part's HandleEvent method,
passing a WM_MOUSEMOVE event.
You can use these events to, for example, change the cursor appearance. See
Handling Mouse Events.
In some situations, OpenDoc redirects or changes mouse events:
If the user holds down the Shift key, Alt key or Ctrl key while pressing
or releasing the mouse button, the dispatcher instead sends the event to
the frame with the selection focus. This allows users to extend
selections by Shift-clicking or Command-clicking.
If the mouse event is in the title bar or re-size box of a window, the
dispatcher converts it to a window event.
If the mouse event is in the menu bar, the dispatcher converts it to a
menu event.
If a frame has the modal focus (see Acquiring and Relinquishing the Modal
Focus), mouse events that occur outside of its border are sent to it.
However, mouse events that occur within frames embedded within the frame
with the modal focus are sent to the embedded frames, as expected.
Mouse clicks within controls associated with your part can be handled in a
number of ways, as discussed in Controls.
ΓòÉΓòÉΓòÉ 7.1.2.2. Mouse Events in Embedded Frames ΓòÉΓòÉΓòÉ
If your part contains embedded frames, the dispatcher can also send you special
mouse events that occur within and on the borders of the embedded frames'
facets.
The following events occur when your part's frame is the active frame and the
embedded frame is selected, bundled, or in icon view type, or if the user
Shift-clicks or Ctrl-clicks or Alt-clicks in the embedded frame to extend a
selection:
When the user presses a mouse button while the pointer is within a facet
of the embedded frame, the dispatcher calls your part's HandleEvent
method, passing it an event type of kODEvtMouseDownEmbedded.
When the user releases a mouse button while the pointer is within the
embedded facet, the dispatcher again calls your part's HandleEvent
method, this time passing it an event type of kODEvtMouseUpEmbedded.
Your part does not receive the mouse-up event if the user clicks in a
selected embedded frame. If the user initiates a drag operation, the
mouse-up event is not sent; instead, it is converted to a drop operation.
If the user does not initiate a drag, the mouse-up event is sent to the
embedded frame so that the embedded part can activate itself. See Mouse
Events, Activation, and Dragging for more information, including how to
handle events in a background process.
The following event occurs when the frame embedded within your part is the
active frame:
When the user presses the mouse button while the pointer is within the
active border of a facet of the embedded frame, the dispatcher calls your
part's HandleEvent method, passing it an event type of
kODEvtMouseDownBorder.
These events allow your part to activate itself and select or drag the
embedded part.
ΓòÉΓòÉΓòÉ 7.1.2.3. Keystroke Events ΓòÉΓòÉΓòÉ
When the user presses a key on the keyboard and your part has the keyboard
focus, the dispatcher calls your part's HandleEvent method, passing it an event
type of kODEvtKeyDown. When the user releases the key, the dispatcher again
calls your part's HandleEvent method, this time passing it an event type of
kODEvtKeyUp.
Exceptions to this convention include keystrokes that are keyboard equivalents
to menu commands when the menu has the focus, which go to your part as menu
events, and keystroke events involving the Page Up, Page Down, Home and End
keys, which go to the frame-if any-that has the scrolling focus.
ΓòÉΓòÉΓòÉ 7.1.2.4. Menu Events ΓòÉΓòÉΓòÉ
If the user presses the mouse button when the mouse pointer is within the menu
bar, or if the user enters a keyboard equivalent to that action, the OpenDoc
converts the mouse-down or keystroke event into a menu event of type kODEvtMenu
and calls the HandleEvent method of the part with the menu focus.
On the OS/2 platform, the message field of the event structure passed to
HandleEvent contains WM_COMMAND. Processing of the WM_COMMAND message for menu
events is handled the same as for a PM application window procedure.
For a description of the HandleEvent method, see the OpenDoc for OS/2
Programming Reference. Handling individual menu commands is discussed in Menus.
ΓòÉΓòÉΓòÉ 7.1.2.5. Window Events ΓòÉΓòÉΓòÉ
If the user presses the mouse button while the pointer is within a non-content
position of the window (such as the close icon in the window), or if the user
enters a keyboard equivalent to that action, OpenDoc converts the mouse-down or
keystroke event into an event of type kODEvtWindow and the document shell
handles the event.
How to handle window events when your part is the root part is described in
Handling Window Events.
ΓòÉΓòÉΓòÉ 7.1.2.6. Activate Events ΓòÉΓòÉΓòÉ
On platforms that support activation and deactivation of windows, OpenDoc sends
activate events and deactivate events, of type kODEvtActivate, to each facet in
the window when the window changes state.
If the user clicks in the title bar of an inactive window, OpenDoc activates
the window and brings it to the front. If the user clicks in the content area
of an inactive window, the part at the click location brings the window to the
front. Either way, when an active window becomes inactive because another
window has become active, each facet of each part displayed in the window being
deactivated receives a deactivate event, and each facet of each part displayed
in the window being activated receives an activate event.
Your part's HandleEvent method can use these events to store and retrieve
information with which it can decide whether or not to activate itself when its
window becomes active; see Handling Activate Events for an explanation.
ΓòÉΓòÉΓòÉ 7.1.2.7. Update Events ΓòÉΓòÉΓòÉ
To support redrawing of previously invalidated areas of a window, frame, or
facet, OpenDoc handles update events and calls the Draw methods of the
appropriate parts.
Update events are themselves triggered by the existence of invalid areas,
created through changes to the content of parts, the activation of windows, or
the removal of obscuring objects such as floating windows.
OpenDoc does not pass update events to your HandleEvent method. When an update
event occurs that involves a facet of your part, the dispatcher calls the
Update method of the window, which results in a call to your Draw method. For
more information, see Invalidating and Updating.
ΓòÉΓòÉΓòÉ 7.1.3. Propagating Events ΓòÉΓòÉΓòÉ
A containing part can set a flag in an embedded frame that allows the
containing part to receive events not handled by the embedded frame. If your
part contains embedded frames with that flag set, your HandleEvent method
receives the events originally sent to them. OpenDoc sets the propagated field
in the EventInfo structure to true when it passes a propagated event to your
part.
Whenever you add a facet to a frame, you can check the state of the flag by
calling the frame's DoesPropagateEvents method. You can then set or clear the
flag by calling the frame's SetPropagateEvents method.
This is a specialized feature of OpenDoc, not likely to be used by most part
editors. You might possibly use it for purposes such as manipulating embedded
selections; for example, you could use tab-key events to allow the user to tab
between embedded parts that do not themselves support tabbing.
If you do not set the event-propagating flag for any of your embedded frames,
your HandleEvent method receives only those embedded-frame events described in
Mouse Events in Embedded Frames.
ΓòÉΓòÉΓòÉ 7.1.4. HandleEvent Method of your Part Editor ΓòÉΓòÉΓòÉ
The dispatcher calls your part's HandleEvent method to pass it a user event
meant for your part. This is the interface to the method:
ODBoolean HandleEvent(inout ODEventData event,
in ODFrame frame,
in ODFacet facet),
inout ODEventInfo eventInfo);
Your implementation of HandleEvent might be similar to this:
1. Initialize the result flag to kODFalse.
2. Obtain the part info data from the frame or facet to which the event was
directed, if you have stored relevant information there.
3. Execute a switch statement with one case for each event type. Pass
execution to the appropriate handler for each event type. Set the result
flag to kODTrue if an individual handler handles the event.
4. Return the result flag as the method result.
Your individual event handlers should function as described in other sections
of this chapter. Each should return kODTrue to HandleEvent if it handles an
event, and kODFalse if it does not.
ΓòÉΓòÉΓòÉ 7.2. Mouse Events, Activation, and Dragging ΓòÉΓòÉΓòÉ
This section discusses how your part should respond to mouse-down, mouse-up,
and activate events in order to activate your part or your window, initiate a
dragging operation, or create a single-click selection. Your HandleEvent method
(see HandleEvent Method of your Part Editor) should dispatch events to routines
that perform the actions described here. For other information about creating
and modifying selections, see Mouse-Down Tracking and Selection.
ΓòÉΓòÉΓòÉ 7.2.1. How Part Activation Happens ΓòÉΓòÉΓòÉ
A part's frame activates itself in these situations:
When it receives a mouse event within its content area
When its window first opens and the part has stored information
specifying that the frame should be active on opening
When its window becomes active and the part has stored information
specifying that the frame should be active upon window activation
When data is dropped on the part as a result of a drag-and-drop operation
When it has another reason for becoming active (such as the need to
display a selection, such as a link source, to the user)
The part activates itself by acquiring the selection focus (see Focus Types)
for one of its display frames. The part is then responsible for displaying its
contents appropriately (such as by restoring the highlighting of a selection)
and for providing any menus, controls, palettes, floating windows, or other
auxiliary items that are part of its active state. The part must also obtain
ownership of any other foci that it needs. OpenDoc draws the active frame
border around the frame with the selection focus. the following figure
illustrates some of the visual changes caused by activation.
In the preceding figure, the graphics part is active. OpenDoc draws the active
frame border around its frame, it has a highlighted selection, and it displays
Font, Size, and Style menus.
Typically, one part is deactivated when another is activated. A part is
expected to deactivate itself when it receives a request to relinquish the
selection focus, plus, possibly, other foci. The deactivating part is
responsible for clearing its selections-an active part in the active window
should not maintain a background selection-and for removing any menus and
other items that were part of its active state. OpenDoc removes the active
frame border from around the (now inactive) part and draws it around the part
that currently has the selection focus.
Mouse events that cause part activation can also cause window activation and
can lead to dragging of part content. The following figure illustrates some of
the elements you must consider in handling mouse-down events for these
purposes. As the following sections explain, your part should handle mouse
events based on whether your part is active or inactive, whether it is in an
active or inactive window, and whether the event location corresponds to one
of the elements- such as the active frame border or an embedded part's
icon-that are shown in the following figure.
Note these points from the previous figure:
Embedded parts can be represented by either frames or icons. Frames can
be active or inactive, and inactive frames can be either selected or
unselected. (Not shown in the figure is that inactive frames can also be
bundled).
There can be only one active frame in the active window, and no active
frame in an inactive window (except that a dialog box can appear in front
of a window that contains the active part).
All selected items must be within the active frame.
A selection in the active window becomes a background selection if the
window becomes inactive.
A selection (either foreground or background) can consist of either
intrinsic content or an embedded frame or icon, or a combination of both.
"Click-selectable intrinsic content" in the figure means any item of
intrinsic content that can be selected by a single mouse click, such as a
graphic object in a draw part.
"White space" in the figure means any location in the content area of a
part that is neither click-selectable intrinsic content nor an embedded
part frame or icon. It includes empty space, but also includes content
(such as text or painting) that requires a sweeping gesture, rather than
a single click, for selection.
An inactive window may be in the current process (if, for example, it is
a part window of the active document) or in a background process (if, for
example, it is a window of another document).
The event-handling guidelines given here are designed to provide the user with
maximum consistency and flexibility when activating parts and when dragging
selections.
ΓòÉΓòÉΓòÉ 7.2.2. Handling Mouse Events ΓòÉΓòÉΓòÉ
On the OS/2 platform, mouse events are sent to your part's HandleEvent as
normal PM mouse messages. The flags field of the EventInfo structure contains
information about where the mouse event occurred. It will be either
kODPropagated, kODInBorder or kODInEmbedded.
ΓòÉΓòÉΓòÉ 7.2.3. Handling Activate Events ΓòÉΓòÉΓòÉ
As noted in Activate Events, your part receives an activate event for each of
its facets when your window becomes active, and a deactivate event when your
window becomes inactive.
When a window becomes inactive, the part that holds the selection focus
relinquishes that focus but can maintain, as a background selection, any
selection it had been displaying. Conversely, when a window becomes active, the
part that had held the selection focus when the window became inactive normally
regains the selection focus. That part may or may not be the part that actually
activated the window.
This part-activation convention requires that your part respond to activate and
deactivate events by recording or retrieving stored information on its focus
transfers; see On Window Activation for more information.
Activate events in a window go first to the most deeply embedded facets and
last to the root facet. This dispatch order gives the root part the opportunity
to obtain the selection focus if no embedded part has done so. It also allows
the root part to override any activation actions taken by embedded parts.
ΓòÉΓòÉΓòÉ 7.3. Focus Transfer ΓòÉΓòÉΓòÉ
Part activation is the process of making a part and frame ready for editing. As
noted previously, activation typically occurs when the user clicks the mouse
button when the pointer is within a frame that is not currently active, but
also happens when a window opens, when a window is activated, and as a result
of drag-and-drop operations.
In the OpenDoc model of part activation, part editors use the concept of focus
to activate and deactivate themselves (rather than being activated and
deactivated by OpenDoc) and to arbitrate the transfer of several types of
shared resources among themselves.
ΓòÉΓòÉΓòÉ 7.3.1. Focus Types ΓòÉΓòÉΓòÉ
A part makes itself the recipient of a certain type of user event or other
action by obtaining the focus for it. A focus is a designation of ownership of
a given shared resource, feature, or event type; for example, the frame that
owns the keystroke focus receives all keystroke events until it passes
ownership of the keystroke focus to another frame.
Focus types are defined as ISO strings, and the standard set defined by OpenDoc
includes those in the following table.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéConstant ΓöéISO String ΓöéDescription Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODKeyFocus Γöé"Key" ΓöéKeystroke events areΓöé
Γöé Γöé Γöésent to the frame Γöé
Γöé Γöé Γöéwith this focus. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODMenuFocus Γöé"Menu" ΓöéMenu events are sentΓöé
Γöé Γöé Γöéto the frame with Γöé
Γöé Γöé Γöéthis focus. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODSelectionFocus Γöé"Selection" ΓöéShift-click and Γöé
Γöé Γöé ΓöéCommand-click mouse Γöé
Γöé Γöé Γöéevents are sent to Γöé
Γöé Γöé Γöéthe frame with this Γöé
Γöé Γöé Γöéfocus. OpenDoc drawsΓöé
Γöé Γöé Γöéthe active frame Γöé
Γöé Γöé Γöéborder around all Γöé
Γöé Γöé Γöéfacets of this Γöé
Γöé Γöé Γöéframe. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODModalFocus Γöé"Modal" ΓöéThe frame that owns Γöé
Γöé Γöé Γöéthis focus is Γöé
Γöé Γöé Γöénotifying other Γöé
Γöé Γöé Γöéframes that it is Γöé
Γöé Γöé Γöéthe only current Γöé
Γöé Γöé Γöémodal frame. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODScrollingFocus Γöé"Scrolling" ΓöéScrolling-specific Γöé
Γöé Γöé Γöékeystroke events Γöé
Γöé Γöé Γöé(such as Page Up andΓöé
Γöé Γöé ΓöéPage Down) are sent Γöé
Γöé Γöé Γöéto the frame with Γöé
Γöé Γöé Γöéthis focus. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODClipboardFocus Γöé"Clipboard" ΓöéThe frame that owns Γöé
Γöé Γöé Γöéthis focus has Γöé
Γöé Γöé Γöéaccess to the Γöé
Γöé Γöé Γöéclipboard. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODMouseFocus Γöé"Mouse" ΓöéThe frame that owns Γöé
Γöé Γöé Γöéthis focus receives Γöé
Γöé Γöé Γöéall mouse-within Γöé
Γöé Γöé Γöéevents whenever the Γöé
Γöé Γöé Γöépointer moves, Γöé
Γöé Γöé Γöéregardless of which Γöé
Γöé Γöé Γöéfacet the pointer isΓöé
Γöé Γöé Γöéwithin. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODStatusLineFocus Γöé"StatusLine" ΓöéThe frame that owns Γöé
Γöé Γöé Γöéthis focus has Γöé
Γöé Γöé Γöéaccess to the statusΓöé
Γöé Γöé Γöéline window. Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
To obtain event foci, part editors request them by name from the arbitrator.
(You obtain access to the arbitrator by calling the GetArbitrator method of the
session object).
You need to convert focus names into tokens before using them in any method
calls. You call the Tokenize method of the session object to convert ISO
strings to tokens.
Foci may be manipulated singly or in groups called focus sets. A focus set is
an OpenDoc object (of class ODFocusSet) listing a group of foci that a part
editor wants to obtain or release as a group.
Foci are owned by frames. Note that, in general, mouse events anywhere within
the content area of an OpenDoc window always go to the most deeply embedded
frame that encloses the click point; thus there is no "mouse focus.". However,
Shift-click, Ctrl-click or Alt-click events, regardless of their location, are
sent to the frame with the selection focus to allow for extending selections.
OpenDoc does not require that the same frame own the selection focus, keystroke
focus, and menu focus, although this is most often the case. Nor does OpenDoc
require that the selection focus be in an active window, although this is
usually the case.
In most cases, when a frame is activated, the part editor for that frame
requests the selection focus, keystroke focus, and menu focus. A frame with
scroll bars might also request the scrolling focus. Your part editor might
create a focus set ahead of time, perhaps during part initialization, that
includes the tokenized names of the foci that your part expects to request when
it becomes active. You use the arbitrator's CreateFocusSet method to create the
focus set.
A simple part, such as a small text-entry field in a dialog box, might request
only the selection focus and keystroke focus on receiving a mouse-up event
within its frame area. An even simpler part, such as a button, might not even
request the selection focus. It might simply track the mouse until the button
is released and then run a script, never having changed the menu bar, put up
palettes or rulers, or become active.
Your part editor can define additional focus types as needed. You can define
other kinds of focus, perhaps to handle other kinds of user events (such as
input from new kinds of devices). To create a new kind of focus, you need to
create a new kind of focus module, the OpenDoc object that the arbitrator uses
to determine focus ownership. Semantic Events and Scripting describes how to
use focus modules to extend OpenDoc's focus management.
Foci may be exclusive or nonexclusive. All of the standard foci defined by
OpenDoc are exclusive, meaning that only one frame at a time can own a focus.
But if you create a new kind of focus, you can make it nonexclusive, meaning
that several frames could share ownership of it.
ΓòÉΓòÉΓòÉ 7.3.2. Arbitrating Focus Transfers ΓòÉΓòÉΓòÉ
This section discusses how to request or relinquish foci to activate or
deactivate your frames.
ΓòÉΓòÉΓòÉ 7.3.2.1. Requesting Foci ΓòÉΓòÉΓòÉ
A part can request, for one of its frames, ownership of a single focus or a set
of foci. You request a focus by calling the arbitrator's RequestFocus method;
you request a focus set by calling the arbitrator's RequestFocusSet method. If
the request succeeds, your part's frame obtains the focus or focus set.
The arbitrator's RequestFocus and RequestFocusSet methods perform a two-stage
transaction in transferring a focus or focus set:
1. The arbitrator first asks the current owning frame of each focus if it is
willing to relinquish the focus, by calling the BeginRelinquishFocus
method of the frame's part.
2. If any owner of the focus is unwilling to relinquish it, the arbitrator
cancels the request by calling each part's AbortRelinquishFocus method.
In this case, RequestFocus or RequestFocusSet returns false.
3. If all focus owners are willing to relinquish, the arbitrator calls each
part's CommitRelinquishFocus method. In this case, RequestFocus or
RequestFocusSet returns true.
ΓòÉΓòÉΓòÉ 7.3.2.2. Relinquishing Foci ΓòÉΓòÉΓòÉ
A part can relinquish foci either on request or when a change to its state
(such as the closure of its frame or a completion of a method) warrants it. An
active part might unilaterally relinquish certain foci (such as the clipboard
focus) as soon as it is finished handling an event, but it might not relinquish
other foci (such as the selection focus) until another part asks for them.
Nevertheless, most parts willingly relinquish the common foci when asked.
Relinquishing foci on request is a two-step process, because multiple foci
requested as a focus set must all be provided to the requestor simultaneously;
if one is not available, none need be relinquished. Your part editor
participates in the process through calls to its BeginRelinquishFocus,
CommitRelinquishFocus, and AbortRelinquishFocus methods.
1. In your BeginRelinquishFocus method, you need do nothing other than
return kODTrue or kODFalse, based on the type of focus and the identities
of the frames (current and proposed focus owners) passed to you. In most
cases, you can simply return kODTrue, unless your part is displaying a
dialog box and another part is requesting the modal focus. In that case,
because you do not want to yield the modal focus until your dialog box
window closes, you return kODFalse. See Acquiring and Relinquishing the
Modal Focus for more information.
2. Your part's CommitRelinquishFocus method, verifies that you have actually
relinquished the focus type you responded to in BeginRelinquishFocus. The
method should take appropriate action, such as removing menus or
palettes, disabling menu items, removing highlighting, and performing
whatever other tasks are part of losing that type of focus. Remember that
the focus may possibly be moving from one frame to another of your part,
so the exact actions can vary.
3. If, after your part responds with kODTrue to BeginRelinquishFocus, the
focus is actually not transferred from your frame, OpenDoc calls your
part's AbortRelinquishFocus method. If your part has done anything more
than return the Boolean result in BeginRelinquishFocus, it can undo those
effects in the AbortRelinquishFocus method.
4. If your part is one of several focus owners called to relinquish the foci
of a focus set, and if you return kODFalse to BeginRelinquishFocus, your
CommitRelinquishFocus method is not called (because you chose not to give
up the focus). However, your AbortRelinquishFocus method is still called
(because all owners of a focus set are notified if any one refuses to
relinquish the focus).
Your part does not relinquish its focus on request only. For example, in your
part's DisplayFrameClosed and DisplayFrameRemoved methods, you should include
a call to the arbitrator's RelinquishFocus or RelinquishFocusSet method to
unilaterally relinquish any foci owned by the frame that you are closing. When
your part closes, its ReleaseAll method should likewise relinquish all of its
foci. When your part finishes displaying a modal dialog box, it should
relinquish or transfer the modal focus; when your part finishes accessing the
clipboard, it should relinquish the clipboard focus.
ΓòÉΓòÉΓòÉ 7.3.2.3. Transferring Focus without Negotiation ΓòÉΓòÉΓòÉ
There are some situations in which the normal process of requesting and
relinquishing foci is not used. Another piece of software interrupts your
part's execution, and your part loses a focus without being given a chance to
relinquish it, or gains focus without having asked for it. To handle those
situations, your part editor must implement versions of the methods
FocusAcquired and FocusLost. The arbitrator calls these methods when your part
has just acquired, or just lost, a specified focus without having negotiated
the transaction.
For example, a containing part, to support keyboard navigation, might call
FocusAcquired in turn on each of its embedded parts as the user makes
successive keystrokes. Or, if a custom input device with its own focus type
were in use and then became detached, the part using the device might receive a
call to its FocusLost method.
These are the interfaces to FocusAcquired and FocusLost:
void FocusAcquired(in ODTypeToken focus,
in ODFrame ownerFrame);
void FocusLost(in ODTypeToken focus,
in ODFrame ownerFrame);
Your FocusAcquired and FocusLost methods should perform any actions your part
editor deems appropriate in response to having just acquired or lost a focus.
The arbitrator's methods TransferFocus and TransferFocusSet allow you to
initiate a transfer of focus ownership without negotiation. A part can use
these calls to transfer focus among parts and frames that it controls directly.
For example, in a modal dialog box consisting of several parts, these methods
can be used to transfer a focus from the outer part (the dialog box) directly
to an inner part (such as a text field) and back.
When focus is transferred with TransferFocus or TransferFocusSet, the
arbitrator calls the FocusAcquired method of the new frame's part and the
FocusLost method of the previous frame's part-except that, when the frame
performing the transfer (the frame representing the part that calls
TransferFocus) is the frame receiving or losing the focus, its FocusAcquired or
FocusLost method is not called.
ΓòÉΓòÉΓòÉ 7.3.2.3.1. Calling your Own FocusAcquired and FocusLost ΓòÉΓòÉΓòÉ
It might seem natural to call your own FocusAcquired method when your request
for foci succeeds, or to call your own FocusLost method from your own
CommitRelinquishFocus method. A better practice, however, is to have related
methods call a shared private method, so that you maintain a clear separation
between public and private interfaces.
ΓòÉΓòÉΓòÉ 7.3.3. Recording Focus Transfers ΓòÉΓòÉΓòÉ
Different frames may need different sets foci when activated. Selection focus,
keystroke focus, and menu focus are commonly needed together. However, a frame
with scroll bars might also need the scrolling focus, and a frame for a
modeless dialog box might not even want the selection focus.
OpenDoc does not save or restore focus assignments. Therefore, during
deactivation of windows and frames, and during closing of windows, you can
record the state of focus ownership in order to restore it at a later
activation or reopening. Your display frame's part info is an appropriate place
to keep that information. Your part's initialization method might create a
focus set with those foci, to use whenever your display frames become active.
ΓòÉΓòÉΓòÉ 7.3.3.1. On Frame Activation ΓòÉΓòÉΓòÉ
When a previously inactive frame in a window becomes active, the part editors
involved should-besides negotiating the focus transfer-record the gain or loss
of selection focus for the respective frames. If you maintain that information,
your activation and deactivation routines can check the state and exit quickly
if not needed.
If you are activating your part's frame, you might record, in a Boolean
flag with a name such as fHasRequiredFoci in the frame's part info data,
the fact that the frame has the selection focus. You can perform this
action in your FocusAcquired method, after its call to the arbitrator's
RequestFocusSet method succeeds.
If you are deactivating your part's frame, you might set the
fHasRequiredFoci flag in the frame's part info data to false. You can
perform this action in your FocusLost method and/or your
CommitRelinquishFocus method.
ΓòÉΓòÉΓòÉ 7.3.3.2. On Window Activation ΓòÉΓòÉΓòÉ
As mentioned in Activate Events, all parts displayed in a window receive an
activate event when the window becomes active, and a deactivate event when the
window becomes inactive.
When an active facet of a frame of your part becomes inactive through window
deactivation and your part loses the selection focus, your part's HandleEvent
method can-upon receiving the deactivate event-store a flag in the facet's part
info field to note that the facet was active before window deactivation. Your
part then can also maintain, as a background selection, any selection it had
been displaying.
Conversely, when a facet of a frame of your part receives an activate event
because of window activation, your part's HandleEvent method can examine the
state of the flag in the part info field to decide whether or not it was the
active part when the window became inactive. If so, it should request the
selection focus, reset the flag, and convert any background selection it may
have maintained into a foreground selection.
ΓòÉΓòÉΓòÉ 7.3.3.3. On Closing and Reopening Documents ΓòÉΓòÉΓòÉ
Normally, the root part of a newly opened window should activate itself as a
matter of course. However, if an embedded had the selection focus when the
window closed, the root part can-if it chooses to-allow the embedded part to
recapture that focus when the window reopens.
When the state of a window is saved in a document and the document is
subsequently reopened, the root part recreates the window. If you want to
restore the selection-focus state of your part (plus perhaps the selection
itself), you can save the selection and the state of the selection-focus flag
in your frame's part info data when the window is closed, and restore them when
the window is opened (when your part's DisplayFrameConnected method is called).
If your part is the root part in this situation, you can either allow the
embedded part's request for selection focus at this time, or you can acquire
the selection focus yourself, when your own DisplayFrameAdded or
DisplayFrameConnected method is called. (The root part is called last).
ΓòÉΓòÉΓòÉ 7.4. Display-Related Events ΓòÉΓòÉΓòÉ
Your part editor needs to respond to events that select or change the position
or visibility of your intrinsic content, by highlighting or preparing to move
or redraw the content. This section discusses general event-handling issues
related to drawing and highlighting. Display concepts are described more
completely in Layout and Embedding.
Just as with intrinsic content, your part editor is responsible for knowing
what embedded frames look like and when they become visible or invisible. When
update events or events related to scrolling or editing cause an embedded frame
to become visible, your part is responsible for creating facets for those
frames. If events cause an embedded facet to move, your part must modify the
facet's external transform to reflect the move. If the embedded facet moves so
as to become no longer visible, your part is responsible for deleting the
embedded facet from its containing facet (or marking it purge-able).
ΓòÉΓòÉΓòÉ 7.4.1. Hit-Testing ΓòÉΓòÉΓòÉ
Hit-testing is the interpretation of mouse events in terms of content elements
within a frame. For example, when the user clicks the mouse somewhere within
your part's content area to select an item or to set an insertion point, you
must use the click location to decide which item has been selected or which
characters correspond to the insertion point. You can then highlight the proper
item or draw the text caret at the proper location.
Coordinate conversion is necessary to assign content locations to hit-testing
events; in that sense, hit-testing is the inverse of drawing. Coordinate
conversion and the application of transforms during drawing is discussed in
Transforms and Coordinate Spaces.
If a mouse click occurs in a facet of your embedded part's frame, OpenDoc
applies the inverse of all external transforms from the canvas facet to your
facet, passing to you the mouse event in your part's frame coordinates. These
coordinates are stored in the ODPoint field of the EventInfo structure. It is
then your responsibility to apply the inverse of your own internal transform to
the event location to convert it to the content coordinates of your part.
If the entire content of your part's frame is scrolled, the application of your
frame's internal transform yields the correct location for everything within
the frame. If, however, your part has drawn scroll bars or other nonscrolling
items within the frame, it is important not to apply the internal transform to
events over those items. See Scrolling for more information.
ΓòÉΓòÉΓòÉ 7.4.2. Invalidating and Updating ΓòÉΓòÉΓòÉ
When keystroke or mouse events involved with editing have changed the visible
content of your part, you should invalidate the affected areas (by calling your
frame's Invalidate method), so that an update event will be generated to force
a redraw. If the same presentation of your part is displayed in more than one
frame (see Synchronizing Display Frames), you are responsible for invalidating
those frames as well, if necessary.
Sometimes, a portion of a window needs to be redrawn because it has been
invalidated by actions taken by the documents' parts or by the system (as when
a window is uncovered). In this case, the document shell receives notification
of that fact and passes an update event to the dispatcher, which calls the
Update method of the window. The window in turn calls the Update method its
root facet.
The root facet's Update method examines all of the root facet's embedded facets
(and their embedded facets, and so on) and marks each one that intersects the
area that must be updated. Then, each facet that needs to be redrawn calls the
Draw method of its frame's part, passing it the appropriate shape that
represents its portion of the area to be updated.
Your part can, if desired, modify the order in which embedded parts draw their
content, by making explicit calls to the Draw, DrawChildren, and
DrawChildrenAlways methods of an embedded facet. See Draw Method of your Part
Editor and Asynchronous Drawing for more information.
ΓòÉΓòÉΓòÉ 7.4.3. Scrolling ΓòÉΓòÉΓòÉ
The scrolled position of a part's contents within a frame is controlled by the
frame's internal transform. (The transform object can also support scaling,
translation, rotation, and perspective transformations of a frame's contents.
See the figure on transforms in Transforms and Coordinate Spaces for an
example.
In general, the user specifies the amount of scrolling to apply to a frame. The
part editor then modifies the offset specified in the frame's internal
transform. (Depending on the new scrolled position of the part's contents, the
part editor might also have to add or remove facets for embedded frames that
have become visible or obscured by the scrolling).
There are a number of ways for your part editor to support scrolling:
It can create scroll bars for its content, placing them at the margins of
its display frames.
It can create scroll bars, sliders, or other kinds of controls and place
them outside of its frame, as separate frames, or as elements in a
floating window.
It can support auto-scrolling, by tracking the mouse pointer when the
user moves it beyond your part's frame, while holding down the mouse
button.
Each of these methods requires different event handling by your part to
achieve the same result: a modified internal transform for the frame. Your
part then draws the scrolled display as described in Scrolling your Part in a
Frame.
ΓòÉΓòÉΓòÉ 7.4.3.1. Event-Handling in Scroll Bars within your Frame ΓòÉΓòÉΓòÉ
You can create scroll bars for your content inside the margins of your frame.
One approach to handling events in those scroll bars is to create an extra,
private "content shape" within your frame. The content shape is the same as the
frame shape, except that it does not include the areas of the scroll bars.
When interpreting mouse events within your frame, it is important that the
current scrolled position of your content be taken into account for content
editing but not for scroll-bar manipulation. Thus, for points that fall within
your content shape, you apply the inverse of the frame's internal transform to
mouse events, whereas outside of it (in the scroll bar area) you do not. See
the following figure.
Mouse events within the scroll bar area specify how to set the frame's internal
transform; based on the transform's new value, you then redraw your part's
content (and the scroll bar slider). Mouse events within the area of the
content shape specify how to select or edit the appropriately scrolled content.
ΓòÉΓòÉΓòÉ 7.4.3.2. Event Handling in Scroll Bars in Separate Frames ΓòÉΓòÉΓòÉ
If you place scroll bars or adornments in completely separate frames from your
content, (see Placing Scroll Bars in a Separate Frame), you avoid having to
define a separate content shape. Your part has one display frame that encloses
both the scroll bars and the content; you draw the scroll bars directly in this
frame, and you draw the content in a sub-frame. Only the sub-frame's internal
transform changes.
When a mouse event occurs in the nonscrolling frame, your part interprets it
and sets the internal transform of the scrolling sub-frame accordingly.
ΓòÉΓòÉΓòÉ 7.4.4. Mouse-Down Tracking and Selection ΓòÉΓòÉΓòÉ
A selection exists only in the context of a particular part. A selection's
boundaries may not span part boundaries; all of its margins must be in the same
part. However, a selection can include any number of embedded frames (which
themselves can be considered to include any number of more deeply embedded
frames).
Selections are typically created by mouse events, or keyboard-modified mouse
events, within an already active frame or a frame that has been made
potentially active by an initial mouse-down event within its area. When a
mouse-down event occurs, OpenDoc passes the pointer location, in the frame
coordinates of the most deeply imbedded frame enclosing the event location, to
the part displayed in that frame. If a drag-selection occurs (that is, if a
drag-and-drop operation does not occur), the subsequent mouse-up event location
is passed in the same coordinates to the same part. Thus, the active frame is
not changed simply because the cursor passes across a frame boundary while
making a selection.
The selection actions your part takes during mouse-down tracking depend on your
content model. You should provide feedback to the user while the dragging is in
progress.
Your part editor should support extending selections through Shift-clicking,
Ctrl-clicking and Alt-clicking. Note that, because selection is possible only
within one part at a time, a part editor can extend a selection outside of its
display frame boundary only to items within another of its own display frames.
If your part is a containing part and allows the user to drag selected content,
including embedded frames, dragging may lead to frame negotiation. If an
embedded frame is clipped by the edge of a page, for example, you may need to
change its size.
Selected frames should be highlighted appropriately, as described in Drawing
Selections.
ΓòÉΓòÉΓòÉ 7.4.5. Resizing Selected Frames ΓòÉΓòÉΓòÉ
Typically, the user resizes embedded frames within your part is by selecting
them and then dragging their resize handles. It is your part, as the (active)
containing part, that handles resizing of its (selected) embedded frames.
A user can select an embedded frame in several ways, as noted previously. Once
a frame is selected, and if your part allows resizing, you can handle resizing
events like this:
1. A mouse-down event on a resize handle in a selected frame border
initiates a resize operation. You track the mouse until the button is
released, giving the user visual feedback on the potential appearance of
the resized frame. The potential shape of the resized frame can depend on
what kind of resize handle (such as corner or side) is being dragged.
2. When the mouse-up event occurs, you determine if its location is
consistent with your part's content model, in terms of allowing a
resizing to that location. If it is not, you can either terminate the
resize operation or adjust the location to an appropriate point.
3. Change the frame size, as discussed in Resizing an Embedded Frame.
ΓòÉΓòÉΓòÉ 7.4.6. Mouse-Up Tracking ΓòÉΓòÉΓòÉ
OpenDoc calls your part's HandleEvent method whenever the mouse pointer enters,
moves within, or leaves a facet of a display frame of your part and the mouse
button is up (not pressed). Your part can use this method call to display a
custom cursor while the pointer is within your part's frames.
Your part is responsible for providing the appropriate cursor appearances as
defined by OpenDoc.
ΓòÉΓòÉΓòÉ 7.4.7. Mouse-Up Feedback while Drawing ΓòÉΓòÉΓòÉ
In certain modal situations, such as when drawing polygons or connected line
segments, the user typically draws by clicking the mouse button once for each
successive vertex or joint. (The user might complete the polygon and exit the
mode by double-clicking or by clicking in the menu bar or another window).
During the drawing operation, while the mouse button is up, the part editor
must provide feedback to the user, showing a potential line segment extending
from the last vertex to the current mouse position.
Your part can support this kind of drawing feedback by requesting the mouse
focus (kODMouseFocus) when the user clicks to make the first vertex after
selecting the appropriate drawing mode (perhaps from a tool palette that you
maintain). Your part then receives events when the mouse moves, regardless of
which facet the cursor travels over; the facet passed to your HandleEvent
method is the one in which the initial mouse-down event occurred. Receiving
these events allows you to provide visual feedback regardless of where the user
moves the mouse pointer.
Your part also receives all mouse-down and mouse-up events until it
relinquishes the focus. In addition, as long as your part holds the mouse
focus, OpenDoc sends no mouse events to any facet.
When the user completes the drawing operation, relinquish the mouse focus.
ΓòÉΓòÉΓòÉ 8. Windows and Menus ΓòÉΓòÉΓòÉ
This is the fourth of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes how your part editor can present
and manipulate some of the major elements of its user interface.
This chapter is a continuation of the previous chapter: it discusses
event-handling and other programming issues involved with your part's user
interface.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. For additional concepts related to
your part editor's run-time environment, see OpenDoc Run-Time Features.
This chapter discusses the following topics:
Windows
Dialog boxes
Controls
Menus
Undo
ΓòÉΓòÉΓòÉ 8.1. Windows ΓòÉΓòÉΓòÉ
Windows are platform-specific data structures through which documents display
themselves. This section discusses how to use OpenDoc window objects (which are
basically wrappers for those structures) regardless of which platform you are
developing for.
ΓòÉΓòÉΓòÉ 8.1.1. Creating and Using Windows ΓòÉΓòÉΓòÉ
The OpenDoc class ODWindow is a wrapper for a pointer to a platform-specific
window structure. For some operations, your part editor must retrieve the
window pointer from the ODWindow object and use the platform's facilities. In
most cases, however, the interface to ODWindow provides the capability you need
for interacting with your windows.
ΓòÉΓòÉΓòÉ 8.1.1.1. Window State Object ΓòÉΓòÉΓòÉ
There is a single instantiated window state object (class ODWindowState) for
each OpenDoc session. The window state consists of a list of the currently
existing window objects. You can access all open windows in a session through
the window state.
The document shell and dispatcher use the window state to pass events to parts
so that they can activate themselves, handle user input, and adjust their menus
as necessary. A part may be displayed in any number of frames, in any window of
a document. The dispatcher uses the window state to make sure that it passes
events to the correct part, no matter what window encloses the active frame and
how many other frames the part has.
Normally, your part editor calls the window state only when it creates new
windows, when it needs access to a particular window, and when it needs access
to the base menu bar object.
If for some reason your part needs access to all windows, you can create an
ODWindowIterator object, which gives you access to all windows referenced by
the window state.
ΓòÉΓòÉΓòÉ 8.1.1.2. Creating and Registering a Window ΓòÉΓòÉΓòÉ
In order to receive events in a window (except in the case of modal dialog
boxes, to which you can pass an event filter routine), you must create an
OpenDoc window object for it. Windows in OpenDoc are created and maintained
through the window-state object, which you obtain from the session object.
You first create a window with platform-specific calls; you then call the
window state object to create an OpenDoc window object describing the
platform-specific window. You call either of two methods:
You call the RegisterWindow method of ODWindowState when you create a
window that is not a root window or has never yet been written to
storage.
You call the call the RegisterWindowForFrame method of ODWindowState when
you create a root window from a previously stored root frame.
(RegisterWindowForFrame takes fewer parameters than RegisterWindow
because the frame passed in with the method already contains some of the
needed information).
A window has an is-root property. If the property is true, the root part of
the window is the root part of its document; in that case, the window is a
root window, which is the same as a document window. If a window's is-root
property is false, the window is a part window that has been opened from a
source frame within a root window. The document associated with a document
window is kept open as long as the document window is open. (OpenDoc permits
multiple document windows for a single document, as long as the root part
provides a user interface to support it). The document shell closes a document
when its last document window is closed.
Windows also have a should-save property that, if true, specifies that the
state of the window is saved persistently after the window closes. Usually,
only document windows should be marked as should-save.
The creator of a window can specify the view type and presentation of the root
frame, the frame that displays the root part. The view type specifies whether
the root part should draw itself as an icon, and the presentation specifies
what kind of appearance the part content should have if not drawn as an icon.
View type and presentation are suggestions to the part editor that draws
within that frame. View type and presentation are described in more detail in
Defining General Display Characteristics.
OpenDoc assumes that each window has a single canvas, attached to its root
facet, the facet created for the root frame. On the OpenDoc platform, the root
frame in the window has the same shape as the window's content region.
Your part should create windows as invisible and then made visible as
described in Opening a Window.
ΓòÉΓòÉΓòÉ 8.1.1.3. Opening a Window ΓòÉΓòÉΓòÉ
After creating a window, your part editor typically makes calls to these three
methods, in this order:
1. The window's Open method, which creates the root facet
2. The window's Show method, which makes the window visible
3. The window's Select method, which brings the window to the front
ΓòÉΓòÉΓòÉ 8.1.1.4. Window IDs ΓòÉΓòÉΓòÉ
Your part editor should not maintain references to ODWindow objects for
accessing OpenDoc windows, because the document shell or the window state
object can close a window and invalidate the reference. Instead, the window
state assigns window IDs that are valid for the length of a session. Use the
window's GetID method to get the ID of a window when you create it, and then
pass that ID to the window state's AcquireWindow method for subsequent access
to the window.
ΓòÉΓòÉΓòÉ 8.1.1.5. Closing a Window ΓòÉΓòÉΓòÉ
If your part editor needs to close a window programmatically, it calls the
window's CloseAndRemove method. That method closes the window, releases the
window object and disposes of the platform-specific window structure, deletes
the root facet and canvas, and removes the root frame from the document. It
also makes any necessary platform-specific calls to dispose of the window
itself.
ΓòÉΓòÉΓòÉ 8.1.1.6. Storing and Retrieving Window Characteristics ΓòÉΓòÉΓòÉ
Whenever a document is saved, OpenDoc writes certain information into the
storage unit of the window's root frame. The window's bounding rectangle,
title, and other characteristics are saved in a property of type
kODPropWindowProperties in the frame's storage unit.
When you create a root window, you retrieve that information from the stored
frame, and use it to specify the platform-specific window's characteristics.
You can use functions of the WinUtils utility library (provided with OpenDoc)
to extract that information, or you can access the frame's storage unit
directly. The kODPropWindowProperties property contains a persistent reference
to another storage unit, which contains the following properties:
kODPropShouldShowlinks
kODPropSourceFrame
kODPropWindowHasCloseBox
kODPropWindowIsFloating
kODPropWindowIsResizable
kODPropWindowIsRootWindow
kODPropWindowIsVisible
kODPropWindowProcID
kODPropWindowRect
kODPropWindowRefCon
kODPropWindowTitle
Making sure a window is on screen
If your part is a root part that recreates a previously stored document
window, you must make sure that the window is visible onscreen. Your document
may have been moved from one system to another with a different monitor
configuration or size. You may need to move or resize the window to fit its
new environment.
ΓòÉΓòÉΓòÉ 8.1.1.7. Open Method of your Part Editor ΓòÉΓòÉΓòÉ
Opening your part means creating a window for it and displaying it in the
window.
Your part itself initiates the opening of a window when the user selects the
Open as window command from the View menu (see View Menu), and when it creates
its own dialog boxes. Otherwise, your part opens a window only when your part's
Open method is called. This is the interface to the Open method:
ODID Open(in ODFrame frame);
The Open method is called in these circumstances.
When your part is initially created from a template-meaning that it has
no previously stored frame or window information-OpenDoc calls your
part's Open method and passes a null value kODNULL for the frame
parameter.
When your part is an embedded part whose frame is selected, and the user
chooses the Open selection command from the Edit menu, your containing
part calls your part's Open method and passes a reference to the selected
frame in the frame parameter.
When your part is the active part, and the user chooses the Open as
window command from the View menu, you call your part's Open method
passing in your frame.
When your part is the root part of a document being opened, OpenDoc calls
your part's Open method and passes a reference to the root frame in the
frame parameter.
In your implementation of Open, you can take steps similar to the following,
depending on the circumstances under which it was called.
1. If you are creating an initial window (frame = null), skip this step and
go to step 2.
If you are opening a frame into a part window (frame = an embedded
frame), check to see if the window already exists. If you have
created the part window previously and saved its window ID, pass
that ID to the AcquireWindow method of the window state object. If
the method returns a valid window, bring the window to the front and
exit. If the window does not yet exist, go to step 2.
If you are opening a stored document into a window (frame = a root
frame), read in the saved window data from the storage unit of the
frame passed to the method (see Storing and Retrieving Window
Characteristics).
2. Create a platform-specific window and register it with the window state
object, as described in Creating and Registering a Window.
If you are opening a stored document into a window, apply the stored
characteristics to the platform-specific window. Call the window
state object's RegisterWindowForFrame method.
If you are opening a frame into a window or creating an initial
window, apply your default characteristics to the platform-specific
window. Call the window state object's RegisterWindow method.
3. Get the window's window ID and save it for future reference.
4. Open and bring the window to the front, as described in Opening a Window.
If you are opening a stored document into a window, skip this step
because there may be more than one window to open and OpenDoc determines
which window is to be in front.
Note: You are not absolutely required to open a window when your Open method
is called. Your part does whatever is appropriate, given its nature and
the conditions under which the method is called. For example, if your
part is a very simple sound player, it might simply play its sound and
never create a window.
ΓòÉΓòÉΓòÉ 8.1.2. Handling Window Events ΓòÉΓòÉΓòÉ
To receive events in its windows that it creates, your part must create an
ODWindow object for each platform-specific window it uses, including dialog
boxes (except for modal dialog boxes; see Modal Dialog Boxes).
The document shell handles most window events outside of the content region of
the window. Nevertheless, the OpenDoc dispatcher first sends a kODEvtWindow
event to the root part. If the part editor of the root part wishes to override
the action the document shell would otherwise take, it can intercept and act on
the event.
If your part handles a window event, its HandleEvent method must return true.
If it does not handle an event, its HandleEvent method must return false so
that the document shell can handle the event.
ΓòÉΓòÉΓòÉ 8.1.2.1. Resizing ΓòÉΓòÉΓòÉ
The document shell usually resizes windows, although the root part can
intercept and handle the event. The document shell relies on default size
limits for windows, so if your part allows-for example-very small window sizes,
it may have to intercept this event and handle the resizing itself (by calling
AdjustWindowShape).
When a window is resized, the active part does not change, but the part editor
for the root frame is informed of the resizing through calls by OpenDoc to its
FrameShapeChanged and GeometryChanged methods. The root part can then do any
necessary invalidation and subsequent redrawing, including creation of new
facets if embedded parts have become visible due to the resizing.
ΓòÉΓòÉΓòÉ 8.1.2.2. Closing ΓòÉΓòÉΓòÉ
The document shell handles a mouse click in the Close icon of a window or user
action of Close or its keyboard equivalent. The document shell closes the
window, after which the window cannot be reopened.
If the window is a document window and is the only one open for that document,
the document shell closes the document.
If your part editor needs to close a window programmatically, it can call the
window's CloseAndRemove method. The window is closed and the window object is
released.
ΓòÉΓòÉΓòÉ 8.1.2.3. Dragging ΓòÉΓòÉΓòÉ
The document shell handles some platform-specific window-moving actions, such
as dragging of a window by its title bar. No event-handling is required of the
window's root part.
Parts in other windows may need to be updated because of the window's move;
they receive update events as appropriate.
ΓòÉΓòÉΓòÉ 8.1.3. Modal Dialog Boxes ΓòÉΓòÉΓòÉ
When your part editor displays a modal dialog box, it does not need to create
an ODWindow object, as with a regular window. However, it should still request
the modal focus (using its own display frame as the modal-focus owner), and it
can still receive events by providing an event filter
In addition, to ensure that floating windows are properly deactivated, your
part must deactivate the front window before displaying a Modal dialog and it
must reactivate the front window after dismissing it.
Your part can create and register its own dialog window, request the modal
focus for the window's root frame, and handle the dialog box.
ΓòÉΓòÉΓòÉ 8.1.3.1. Acquiring and Relinquishing the Modal Focus ΓòÉΓòÉΓòÉ
A frame displaying a modal dialog box should own the modal focus, a focus type
that exists to constrain certain events.
For example, a mouse click outside the frame that has the modal focus still
goes to that frame. If your part's frame has the modal focus and the user
clicks outside the frame, your part's HandleEvent method is called and passed a
facet of kODNULL. The method should check for a null facet in this situation
and either alert the user with a beep or dismiss the dialog box, as
appropriate.
A click in a frame embedded within the frame that has the modal focus goes to
the embedded frame. This behavior facilitates the construction of dialog boxes
and other controls from multiple parts.
Your part obtains and relinquishes the modal focus as it does other foci, as
described in Requesting Foci and Relinquishing Foci.
In general, your part should not be willing to relinquish the modal focus on
request. If your part is displaying a modal dialog, you probably do not want
any other modal dialog to be displayed at the same time. To make sure that your
part holds on to the modal focus, your part editor's BeginRelinquishFocus
method should return kODFalse if the requested focus is kODModalFocus and the
proposed new owner of the focus is not one of your own display frames.
When you have finished displaying a modal dialog box, you can directly transfer
it to its previous owner by calling the arbitrator's TransferFocus method, as
noted in Handling a Simple Modal Dialog Box.
ΓòÉΓòÉΓòÉ 8.1.3.2. Event Filters ΓòÉΓòÉΓòÉ
With modal dialog boxes, your part editor's dialog-box event filter controls
which events you receive while a dialog box or alert box is being displayed. To
pass received null events, update events, and activate events on to OpenDoc or
other windows for handling, your event filter can send them to the OpenDoc
dispatcher by calling its Dispatch method.
Your event filter should not pass other events, such as mouse events, to the
dispatcher.
ΓòÉΓòÉΓòÉ 8.1.3.3. Handling a Simple Modal Dialog Box ΓòÉΓòÉΓòÉ
To display a simple modal dialog box or Alert box, you can take these steps:
1. Get a reference to the frame that currently owns the modal focus by
calling the arbitrator's GetFocusOwner method. Request the modal focus
from the arbitrator, using its RequestFocus method. If you obtain the
focus, proceed.
2. Install your dialog event filter function.
3. Create the dialog box, using a Dialog Manager call such as GetNewDialog.
4. To handle floating windows properly, deactivate the currently active
window by calling DeactivateFrontWindow method of the window state
object.
5. Handle the dialog box with a Dialog Manager call such as ModalDialog. Act
on the results and, when finished, dispose of the dialog box with a
Dialog Manager call such as DisposeDialog.
6. Reactivate the previously active window (to restore floating windows), by
calling the window state's ActivateFrontWindows method.
7. Remove your dialog event filter function.
8. Restore the modal focus to its previous owner by calling the arbitrator's
TransferFocus method.
By always saving and restoring the owner of the modal focus, your part can use
this approach for nested modal dialog boxes, such as a dialog box that is
built from several embedded parts.
ΓòÉΓòÉΓòÉ 8.1.3.4. Handling a Movable Modal Dialog Box ΓòÉΓòÉΓòÉ
In OpenDoc, to implement a full-featured movable modal dialog box-that is, one
that allows process switching-you must create a window object (ODWindow) to
contain it. To display a movable modal dialog box, you can take these steps:
1. Use the appropriate methods to create the structures for the dialog box.
2. Create a window object, using the window state's RegisterWindow method.
Give it properties appropriate for your modal dialog, such as floating,
nonpersistent and floating.
3. Request the modal focus for the root frame of the dialog window.
4. Adjust menus, as necessary, for the presence of the dialog box.
5. Call the Open, Show, and Select methods of the modal dialog window.
6. Handle events in the dialog box through your normal event-handling
mechanism.
To make sure you dismiss the modal dialog box at the right time, you can take
actions such as these when you receive a mouse-down event in the dialog box:
1. Determine whether the event applies to your dialog and, if so, what item
the user selected.
2. If the user chose to close the dialog box, relinquish the modal focus and
call the window's CloseAndRemove method to delete the window and its root
frame.
3. Re-enable any menus or menu items that you disabled for display during of
the dialog box.
Note: It is also possible to create a modal dialog box that is movable,
but does not support process switching, by using a filter function
and other functions in a utility library (DlogUtil) provided with
OpenDoc.
ΓòÉΓòÉΓòÉ 8.1.4. Modeless Dialog Boxes ΓòÉΓòÉΓòÉ
Modeless dialog boxes are more like regular windows than modal dialogs are.
They can be activated and deactivated, and they need not be dismissed for your
part to become active and editable.
ΓòÉΓòÉΓòÉ 8.1.4.1. Showing the Dialog Box ΓòÉΓòÉΓòÉ
To display a modeless dialog box in OpenDoc, you must create a window object
(ODWindow) to contain it. To display the dialog box, you can take steps such as
these:
1. In case the dialog window already exists, try to get a reference to it by
passing its ID (previously stored in your part) to the window state's
AcquireWindow method. If it does not yet exist, create the
platform-specific structures for the dialog box, and create a window
object with the window state's RegisterWindow method. Call the window's
Open method.
2. Call the window's Show and Select methods to make it visible and active.
3. If you do not already have the window ID of the dialog window, get it by
calling the window's GetID method. Save it for use in step 1 the next
time the user chooses the action that brings up the modeless dialog box.
ΓòÉΓòÉΓòÉ 8.1.4.2. Closing the Dialog Box ΓòÉΓòÉΓòÉ
When the user clicks in the close box of a modeless dialog, you may hide the
dialog window, rather than close it, so that it is not destroyed. This is an
optimization that allows you to quickly display the dialog box again.
In your part's HandleEvent method, you can respond in this general way to a
mouse click within a window's close box:
1. From the frame or facet passed to HandleEvent, obtain information that
can identify the window. For example, get a reference to the window
object in which the event occurred (by calling the facet's GetWindow
method), or examine the frame's presentation or part info data for
identifying characteristics.
2. Compare that information to stored information that defines your modeless
dialog box. Get a reference to your modal dialog's window object (by
passing its ID to the window state's AcquireWindow method), for example,
or check a stored value that defines your modeless dialog's presentation.
3. If the two are the same, hide the window instead of closing it.
ΓòÉΓòÉΓòÉ 8.1.4.3. Hiding a Dialog Box when Deactivating a Frame ΓòÉΓòÉΓòÉ
When your part is deactivated, it should hide any of its modeless dialog boxes.
When your part relinquishes the selection focus, it can get a reference to the
dialog window (by passing its ID to the window state's AcquireWindow method),
call the window's IsShown method to see if it is currently being shown, and
then save that shown state and hide the window.
When your part reacquires the selection focus, it can retrieve a reference to
the dialog window by passing its ID to the window state's AcquireWindow method.
Then, if the dialog window had been visible at deactivation, your part can once
again show it.
ΓòÉΓòÉΓòÉ 8.2. Controls ΓòÉΓòÉΓòÉ
This section discusses what kinds of controls you construct them, and how to
handle events within controls It also discusses two specific issues for
palettes: how to share them among instances of your part, and how to use them
to embed parts within your part.
ΓòÉΓòÉΓòÉ 8.2.1. Design Issues for Controls ΓòÉΓòÉΓòÉ
Controls in OpenDoc have the same function as in conventional applications:
they are graphical objects that allow the user to interact with and manipulate
documents in a variety of ways. However, there are some differences:
In a conventional application, controls typically apply to the entire
document. They are always present unless dismissed explicitly. In an
OpenDoc document, however, each part may have its own set of controls,
and thus controls can appear and disappear rapidly as the user edits.
This rapid change can be irritating if not carefully managed.
In a conventional application, finding the space in which to display a
control may be less of a consideration than in OpenDoc. It may be a
challenge for small embedded parts to find sufficient space to display
rulers, scroll bars, and palettes.
In OpenDoc, controls can be constructed as independent parts, assemblages
of parts, different frames of a single part, or content elements of a
part. This can give you more flexibility in constructing user-interface
elements than is possible for conventional applications.
OpenDoc controls can have attached scripts or can communicate with each
other, or with other parts, using the OpenDoc extension mechanism. This
gives OpenDoc controls the ability to be far more integrated and
context-sensitive than standard controls.
Standard types of controls you might wish to include with your parts include:
Push buttons, radio buttons, and checkboxes
Scroll bars and sliders, or other gauges
Pop-up menus
Rulers
Tool bars
Palettes
Rulers usually reflect some settings of the current selection context and
contain controls that allow the user to change these settings. Tool bars are
like rulers but they often trigger actions instead of changing settings.
Status bars display the progress of some long-running operation or suggest
actions to users.
In conventional applications, rulers and tool bars commonly occupy the margins
of the window. In an OpenDoc document, controls can be displayed at the
margins of the window, in separate frames outside of the embedded frame they
apply to, or within the frame they apply to.
A ruler for a text part, for example, can be an additional frame associated
with the part. Events in the ruler are handled by the part's editor.
Conversely, the ruler may be its own part with its own editor. In this case,
the text part editor must maintain a reference to the ruler part and be able
to communicate with it through semantic events or some other extension
mechanism.
A ruler that is a separate part can have its own embedded parts, such as
buttons. The ruler part must then be able to communicate with its embedded
controls as well as with the part that it services.
Palettes often contain editing tools but can also contain choices for object
attributes like color or line width. Palettes commonly float freely beside or
over the document, although they can also be fixed at the window margins.
Palettes might also pop up, pull down, or be torn off of menus.
If all parts in a document can communicate with each other, they can
coordinate the drawing and hiding of palettes or other controls to avoid
irritating the user. For example, all parts in a document can share a single
palette, within which the various parts negotiate for space and in which they
draw only those tools that must change as the active part changes.
ΓòÉΓòÉΓòÉ 8.2.2. Handling Events in Controls ΓòÉΓòÉΓòÉ
How you handle events in a control are handled depends on how you design the
control:
If a control like a ruler is within the active frame, it might not have
its own frame, and the active part editor handles any events in the
ruler.
If a control has its own frame, it might be another display frame of the
active part. In this case also, the active part editor handles the event
in the control.
If a control is a separate part, its own part editor handles the event
and updates the state of the part, which might trigger scripts or calls
to other parts' extension interfaces. Likewise, the part itself can
receive queries from other part editors in the form of semantic events or
calls to its extension interface.
ΓòÉΓòÉΓòÉ 8.2.3. Sharing Palettes and Utility Windows ΓòÉΓòÉΓòÉ
The user-interface guidelines state that you should hide any visible palettes,
modeless dialog boxes, or other utility windows when your part becomes inactive
or its document closes, and restore them-in the same positions-when the part
becomes active once again or the document reopens. In addition, if your part
editor maintains multiple parts in a document and the active state switches
from one to another of them, any visible utility windows that apply to both
parts should remain steadily visible, without any flicker caused by hiding and
immediate restoring.
One way to implement this behavior for a utility window is to follow, in
general, steps such as these:
Make the window globally accessible to your parts by keeping its
reference and its state in an object that each of your parts acquires on
initialization and releases on closing.
Have the global object create the window in a normal manner the first
time it is to be displayed. When the user closes the window, the global
object can simply hide it by calling its Hide method. If the user
subsequently needs to display the window again, the global object can
show it again by calling its Show method.
When your part relinquishes the selection focus, its
CommitRelinquishFocus method can check to see if the part that is
receiving the focus also belongs to your part editor, and has a
presentation that uses the same utility window. There are a number of
ways that you can check this, such as by examining the presentation of
the newly active frame.
When your part acquires the selection focus, it notifies the global
object of that fact. The global object, in turn calls the ChangePart and
SetSourceFrame methods of the utility window's frame to assign the new
part and frame to the utility window. The new part can then adjust the
content of the utility window if needed, and also show or hide other
palettes or dialogs.
ΓòÉΓòÉΓòÉ 8.2.4. Using a Tool Palette to Embed Parts ΓòÉΓòÉΓòÉ
An example of an OpenDoc-specific use for controls is to allow the user to
embed parts by selecting items from a palette. Using a palette in this way,
your part can create embedded parts from scratch, rather than through reading
in existing part data.
Your part can provide a palette, menu, or dialog box from which the user
selects an item that specifies a part kind. As in many conventional
applications, your palette could display a set of tools to the user-drawing
tools, painting tools, text tools, and so on. In this case, however, selecting
an item from the palette actually means that a new part of that kind is to be
embedded in your part.
The items could represent existing template documents in the user's system, or
they could simply represent individual part kinds for which editors exist. If
you are creating parts from scratch, follow steps such as these once the user
has made a selection from your palette:
Create the new part by calling your draft's CreatePart method, passing it
the part kind that the user selected.
Call the new part's Externalize method (see The Externalize Method), so
the part can create and write initial data to the properties in its
storage unit.
Create a new embedded frame for the part, as described in Creating a New
Embedded Frame.
Give the new frame the proper link status, as described in Frame Link
Status.
If the new frame is visible, assign facets to it, as described in Adding
a Facet.
Notify your containing part and your draft that there has been a change
to your part's content; see Making Content Changes Known.
ΓòÉΓòÉΓòÉ 8.3. Menus ΓòÉΓòÉΓòÉ
At any given moment while an OpenDoc document is open, responsibility for the
menu bar is shared among three entities. The operating system provides any
system-wide menus, the OpenDoc document shell creates the Document menu, Edit
menu, View menu and Help menu. Individual part editors can create other menus
as needed. (Part editors can also, with restrictions, add appropriate items to
the Document and Edit menus).
Different platforms have different conventions for enabling and disabling menus
and menu items. In an OpenDoc document, the document shell, the root part, and
the part with the menu focus together control which menu commands are available
to the user. As each part becomes active, the OpenDoc document shell and the
root part update their own menu items, and the active part editor takes care of
the rest.
Basic event handling for menu events is described in Menu Events. When the user
chooses a menu item, the document shell either handles the command itself or
dispatches a menu event to the active part; the part receives the event as a
call to its HandleEvent method.
This section discusses general issues of setting up and working with menus and
then describes how to handle individual menu events for the standard OpenDoc
menus (the Document menu and the Edit menu):
Document
Edit
View
Help
ΓòÉΓòÉΓòÉ 8.3.1. Setting Up Menus ΓòÉΓòÉΓòÉ
This section describes how your part editor can set up and use menus and menu
items.
ΓòÉΓòÉΓòÉ 8.3.1.1. Base Menu Bar ΓòÉΓòÉΓòÉ
When it first opens a document, the document shell creates a menu bar object
(type ODMenuBar) and installs it as the base menu bar by calling the window
state's SetBaseMenuBar method. The base menu bar contains different menu and
items on different platforms, but the Document menu and the Edit menu are
always installed.
ΓòÉΓòÉΓòÉ 8.3.1.2. Base Pop-Up Menu ΓòÉΓòÉΓòÉ
On the OS/2 platform, the document shell also creates a pop-up menu object
(type ODPopup) and installs it as the base pop-up menu by calling the window
state's SetBasePopup method. The base pop-up menu always installs the Open as
properties, Show as, and Help menu items.
ΓòÉΓòÉΓòÉ 8.3.1.3. Adding Part Menus to the Base Menu Bar ΓòÉΓòÉΓòÉ
When your part initializes itself, or when it first obtains the menu focus, it
should create its own menu bar object by:
1. Copying the base menu bar using the window state's CopyBaseMenuBar
method.
2. Adding its own menu structures, by using menu-bar methods such as
AddMenuBefore and AddMenuLast.
If absolutely necessary, your part editor can add items to the end of the
Document and Edit menus, but you should not alter the existing items.
ΓòÉΓòÉΓòÉ 8.3.1.4. Menu IDs ΓòÉΓòÉΓòÉ
You must identify each menu with a menu ID. A menu ID is a positive short;
negative values are reserved by the operating system.
All menus in the menu bar must have unique menu IDs. Therefore the document
shell, the active part, and any shell plugs-in or services that also have menus
must cooperate to ensure that there are no conflicts. Please follow the
conventions listed in the following table list to ensure that your menu IDs do
not conflict with those of others.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéType of Software ΓöéMenu ID Range Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDocument shell Γöé0x0000 - 0x2FFF Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéPart editors (when root) Γöé0x3000 - 0x3FFF Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéShell plug-in/services Γöé0x4000 - 0x4FFF Γöé
Γöé ΓöéNote: These menu IDs must be Γöé
Γöé Γöédynamically assigned. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéPart editors Γöé0x5000 - 0x7FFF Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
A shell plug-in or service may have to adjust its menu ID dynamically at
runtime, because another service or plug-in with that menu ID may already be
installed. The plug-in should choose an ID, look for an installed menu with
that ID, and-if it is found-add 1 to the ID and try again.
ΓòÉΓòÉΓòÉ 8.3.1.5. Obtaining the Menu Focus ΓòÉΓòÉΓòÉ
When your part activates itself, it should request the menu focus (along with
other foci) if it wants to use menus. See Requesting Foci for more information.
ΓòÉΓòÉΓòÉ 8.3.1.6. Enabling and Disabling Menus and Commands ΓòÉΓòÉΓòÉ
When the user clicks in the menu bar, the OpenDoc dispatcher determines which
part has the menu focus and calls that part's AdjustMenus method. It also calls
the root part's AdjustMenus method, if the root part does not have the menu
focus.
Your part's AdjustMenus method can use methods of the menu bar object such as
EnableCommand or EnableAndCheckCommand to change the appearance of your menu
items, or it can make platform-specific calls to directly enable, disable,
mark, or change the text of its menu items.
Your AdjustMenus method typically acquires the clipboard focus and enables the
Cut, Copy, Paste, and Paste as... items in the Edit menu.
ΓòÉΓòÉΓòÉ 8.3.1.7. Menus and Read-Only Documents ΓòÉΓòÉΓòÉ
When your part permissions (see Drafts) specify that your document is
read-only, your part editor needs to disable these menu commands:
Cut, Paste, Paste as..., and Delete items in the Edit menu
any part-specific content-editing commands
This situation can occur when the user views an early draft of a document, or
when a document is stored on read-only media.
Part viewers should disable these items and commands at all times.
ΓòÉΓòÉΓòÉ 8.3.1.8. Menus and the Root Part ΓòÉΓòÉΓòÉ
In all OpenDoc documents, the root part is responsible for printing. The root
part therefore should handle the Print document item from the Document menu,
even if an embedded part has the menu focus. If the root part is a container,
it is also responsible for opening an icon, tree, or details view of itself.
To allow the root part access to these menu events, the dispatcher passes menu
events to the root part if they are not handled by the part with the menu
focus. Also, OpenDoc calls the root part's AdjustMenus method before it calls
the AdjustMenus method of the part with the menu focus, so that the root part
can adjust the state of those menu items.
When your part's AdjustMenus method is called, it should check to see if your
part is the root part and whether it has the menu focus. If it is the root part
but does not have the menu focus, it should adjust only the Open as and Print
document items from the Document menu. If it is an embedded part with the menu
focus, it should not adjust those items.
Likewise, when your part's HandleEvent method is called, it should first check
to see if your part is the root part. If it is the root part, HandleEvent
should return false if it is passed any menu events other than the Print
document item and the Icons, Tree, or Details items of the Open as submenu in
the Document menu. If it is an embedded part, HandleEvent should return false
if it is passed Page setup or Print document.
ΓòÉΓòÉΓòÉ 8.3.2. Document Menu ΓòÉΓòÉΓòÉ
This section describes how your part editor should interact with the Document
menu. The OpenDoc document shell handles most Document menu commands, as
described in Handling the Document Menu. Individual part editors must respond
only to the Open as and Print document commands.
ΓòÉΓòÉΓòÉ 8.3.2.1. Open as ΓòÉΓòÉΓòÉ
The user chooses the Open as (in an OpenDoc document) into its window.
The root part handles this command. It should display one of the user selected
views (icons, tree, or details) using the ODViewExtension class.
Your part does not need to support this command if it is not a container.
ΓòÉΓòÉΓòÉ 8.3.2.2. Print document ΓòÉΓòÉΓòÉ
The root part of the window handles the Print document command. Root parts are
responsible for defining the printing behavior of their documents. When the
user chooses this command and your part is the root part of the active window,
OpenDoc passes the command OD_PRINT to your part's HandleEvent method. If the
root part does not handle the event, the document shell prints the document.
The document shell prints only the visible content of the root frame and its
embeds. If your part supports multiple pages or its content area is greater
that its frame, you need to handle the print command yourself.
Your routine to handle the Print document command should set up for printing.
ΓòÉΓòÉΓòÉ 8.3.3. Edit Menu ΓòÉΓòÉΓòÉ
Most items in the Edit menu are handled by individual part editors. Most apply
to the current selection in the currently active part.
Choices on the Edit menu are as follows:
Undo
Redo
Create
Cut
Copy
Paste
Paste as...
Paste link
Break link
Delete
Select all
Deselect all
Open selection
Selection properties
Show selection as
The following figure shows the Edit menu.
Each item is explained in detail in the following sections.
ΓòÉΓòÉΓòÉ 8.3.3.1. Undo ΓòÉΓòÉΓòÉ
The user selects Undo from the Edit menu in order to reverse recently enacted
user actions, including previous uses of Undo or Redo. Undo reverses the
effects of the last undoable user action and restores all parts to their states
before that action. Not all user actions are undoable. OpenDoc supports
multiple levels of Undo. For example, if the user selects Undo three times in
succession, then the last three undoable actions are undone in order.
The document shell handles the Undo item, passing control to the Undo object.
The Undo object, in turn, calls the UndoAction method of any part editors
involved in the Undo.
Your part should respond to Undo as described in Undo.
ΓòÉΓòÉΓòÉ 8.3.3.2. Redo ΓòÉΓòÉΓòÉ
The user selects Redo from the Edit menu in order to reverse the last use of
Undo. Redo is available only if the last undoable user action was Undo or Redo.
Redo reverses the effects of the last undoable user action, restoring all parts
to their states before the Undo action. OpenDoc supports multiple levels of
Redo. Each time the user selects Redo, the previous Undo is redone. This works
only if the user had invoked Undo multiple times in succession.
The document shell handles the Redo item, passing control to the Redo object.
The Redo object, in turn, calls the RedoAction method of any part editors
involved in the Redo.
Your part should respond to Redo as described in Undo.
ΓòÉΓòÉΓòÉ 8.3.3.3. Create ΓòÉΓòÉΓòÉ
The user selects Create to create a new part of the same type as the selected
part. The part will be created on the clipboard. When Create is selected, the
Paste link menu item and the Paste Link push button in the Paste As... dialog
should be grayed because linking is not possible at this point.
ΓòÉΓòÉΓòÉ 8.3.3.4. Cut, Copy, and Paste ΓòÉΓòÉΓòÉ
The user selects Cut, Copy, or Paste from the Edit menu in order to place data
onto the clipboard or retrieve data from the clipboard.
The Cut, Copy, and Paste choices are familiar mechanisms for copying and moving
content around in the system. In OpenDoc, function has been added for handling
embedded parts. The function added consists of:
Operating on parts in addition to intrinsic content. The parts may be
represented as frames or icons.
Making embed versus incorporate decisions.
Wrapping intrinsic content inside a part in certain circumstances.
Your part should disable the Cut and Paste items when its draft permissions
are read-only; see Menus and Read-Only Documents.
ΓòÉΓòÉΓòÉ 8.3.3.5. Paste as... ΓòÉΓòÉΓòÉ
The user selects Paste as... from the Edit menu in order to specify the manner
in which clipboard data is to be pasted into the active part.
The Paste as... choice displays a dialog box that allows the user to specify
the data format for pasting the clipboard into the destination. The clipboard
content is converted to the user-specified format, and links to it can be
created. Edit Menu lists the kinds of pasting that the user can specify. When
the user selects the command, OpenDoc passes the command information to your
part's HandleEvent method.
Your routine to handle the Paste as... command should prepare to read from the
clipboard, like this:
1. Acquire the clipboard focus and gain access to its content storage unit,
following the initial steps described in Pasting from the Clipboard.
2. Display the Paste As... dialog box, by calling the ShowPasteAsDialog
method of the clipboard object. Pass the function the active frame into
which the paste is to occur.
3. If the method returns kODTrue, the user has pressed the OK button; use
the results of the interaction to determine which kind of pasting action
to take. Then read the appropriate kind of data from the clipboard,
continuing with the procedures shown in Pasting from the Clipboard.
ΓòÉΓòÉΓòÉ 8.3.3.6. Paste link ΓòÉΓòÉΓòÉ
The user chooses the Paste link command from the Edit menu in order to insert
the link from the clipboard into your part.
In your routine to handle paste link, you should:
1. Create an empty "linkspec"
2. Read in the "linkspec" from the clipboard
3. Create a link from the "linkspec"
ΓòÉΓòÉΓòÉ 8.3.3.7. Break link ΓòÉΓòÉΓòÉ
The user selects Break link to copy the source information to the document and
break the link with the source. The action cannot be undone. A confirmation
dialog will be given before the link is broken. The Break link choice is for
destination links only.
ΓòÉΓòÉΓòÉ 8.3.3.8. Delete selection ΓòÉΓòÉΓòÉ
The user selects Delete selection to delete the selected content from the
active part.
Your routine to handle the Delete selection command should remove the items
that make up the selection from your part content. That may involve deleting
embedded parts as well as intrinsic content.
ΓòÉΓòÉΓòÉ 8.3.3.9. Select all ΓòÉΓòÉΓòÉ
The user selects Select all from the Edit menu to make the current selection
encompass all of the content of the active part.
Your routine to handle the Select all command must include all of your part's
content in the selection structure that you maintain and it must highlight the
visible parts of it appropriately.
ΓòÉΓòÉΓòÉ 8.3.3.10. Deselect all ΓòÉΓòÉΓòÉ
The user selects Deselect all from the Edit menu to deselect all the content of
the active part.
Your routine to handle the Deselect all command must include all of your part's
content in the selection structure that you maintain and it must highlight the
visible parts of it appropriately.
ΓòÉΓòÉΓòÉ 8.3.3.11. Selection properties ΓòÉΓòÉΓòÉ
The user chooses the Selection properties command from the Edit menu to display
the Properties notebook containing standard information about the current
selected embedded parts. When the user chooses the command, OpenDoc passes the
command information to your active part's HandleEvent method. Your routine to
handle the Selection properties command should display the Properties notebook
that describes the characteristics of the current selection.
In your routine to handle the Selection properties command, you can take the
following steps,
If the current selection is an embedded frame border, display the
Properties notebook for the part in that frame, using the
ShowPartFrameInfo method of the info object (class ODInfo). You obtain a
reference to the Info object by calling the session object's GetInfo
method. OpenDoc handles all changes made by the user and updates the
information in the storage units for the embedded part and frame,
including performing any requested translation.
OpenDoc determines the information it displays in the Properties notebook
by reading the part's Info properties, the set of properties-separate
from the part's contents-that can be displayed to, and in some cases,
changed by the user. All stored parts include them. If your part creates
an extension to the Properties notebook (see The Settings Extension), it
can define and display additional properties. Your routine should support
both single and multiple selection.
ΓòÉΓòÉΓòÉ 8.3.3.12. Open selection ΓòÉΓòÉΓòÉ
The user chooses the Open selection command from the Edit menu to open the
selected embedded parts within a window. When the user chooses this command,
OpenDoc passes the command to your active part's HandleEvent method. Your
routine to handle Open selection should do the following:
1. From your private data structures, determine which of your embedded
frames is the selected one. (Each embedded part, even if displayed in an
icon view type, has a frame).
2. Call the AcquirePart method of the selected frame, followed by the Open
method of the part returned by the AcquirePart method. The Open method is
described in Open Method of your Part Editor.
Your routine should support both single and multiple selection.
ΓòÉΓòÉΓòÉ 8.3.3.13. Show selection as ΓòÉΓòÉΓòÉ
The user chooses the Show selection as command from the Edit menu to change the
view type of the selected embedded parts into large icons, small icons,
thumbnails, or frames. When the user chooses this command, OpenDoc passes the
command to your active part's HandleEvent method. Your routine to handle Show
selection as should do the following:
1. For each embeded selection frame, obtain a reference to the part that
owns the frame.
2. Call that frame's ChangeViewType method, passing in the view type choosen
by the user.
ΓòÉΓòÉΓòÉ 8.3.4. View Menu ΓòÉΓòÉΓòÉ
The View menu shows the views of the active part or controls the display of
related windows like tool bars or palettes. All container parts should support
Icons, Tree, and Details views.
The View choices are as follows:
Show frame outline
Open as
Properties
Show as
Show links
The following figure shows the View menu.
Each item is explained in detail in the following sections.
ΓòÉΓòÉΓòÉ 8.3.4.1. Show frame outline ΓòÉΓòÉΓòÉ
The user selects this command to reposition your part's content-that is, to
change the portion of it displayed in the frame in the document window. In the
part window, display a 1-pixel wide black-and-white border around the content
currently visible in the display frame in the document window, and allow the
user to drag the frame outline.
This appears in the View menu for parts opened into a window. When it is
selected, an outline of the frame is shown to indicate the portion of a part's
contents that is displayed in its frame. The user can drag this outline to
adjust the visible region of the part. Show frame outline toggles with Hide
frame outline.
Your part should add the Show frame outline command to the View menu when your
part's content area is greater than the area of its display frame, the frame is
opened into a part window, and the part window is active.
ΓòÉΓòÉΓòÉ 8.3.4.2. Open as ΓòÉΓòÉΓòÉ
The user chooses the Open as command from the View menu to display an icon,
tree or details view of your part and its embeds. If your part is a containing
part, you should support these views.
Display the view for your frame by calling the DisplayView method of the
ODViewExtension class, passing in the view type selected by the user.
ΓòÉΓòÉΓòÉ 8.3.4.3. Properties ΓòÉΓòÉΓòÉ
The user chooses the Properties command from the View menu to display a
Properties notebook for your part.
The Properties notebook is displayed for your frame using the ShowPartFrameInfo
method of the ODInfo class. You obtain a reference to the info object by
calling the session object's GetInfo method. OpenDoc handles all changes made
by the user.
ΓòÉΓòÉΓòÉ 8.3.4.4. Show as ΓòÉΓòÉΓòÉ
The user chooses the Show as item from the View menu to change the view type of
your part's frame. Your part should call its SetViewType method, passing in the
view type selected by the user.
ΓòÉΓòÉΓòÉ 8.3.4.5. Show links ΓòÉΓòÉΓòÉ
The user selects this command to show the link border for all links in the
active part. The Show links menu item toggles with Hide links.
ΓòÉΓòÉΓòÉ 8.3.5. Help Menu ΓòÉΓòÉΓòÉ
The Help menu choices are as follows:
Help index
General help
<part> information
The following figure shows the Help menu.
ΓòÉΓòÉΓòÉ 8.3.5.1. Help index ΓòÉΓòÉΓòÉ
Index for the active part. When the part receives this command, it should
display its index by calling the DisplayHelpIndex method of the ODHelp class. A
part obtains a reference to the ODHelp class by calling the GetHelp method of
the ODSession class.
ΓòÉΓòÉΓòÉ 8.3.5.2. General help ΓòÉΓòÉΓòÉ
A general description of the part. When a part receives this command, it should
display its general help by calling the DisplayHelp method of the ODHelp class.
A part obtains a reference to the ODHelp class by calling the GetHelp method of
the ODSession class.
ΓòÉΓòÉΓòÉ 8.3.5.3. <part> information ΓòÉΓòÉΓòÉ
This menu provides information about the active part. This menu is optional and
should be implemented by the part.
ΓòÉΓòÉΓòÉ 8.3.6. Pop-Up Menu ΓòÉΓòÉΓòÉ
The pop-up menu choices are as follows:
Open as
Properties
Show as
Help
Additional actions that a part can perform on the part should also be listed
in the pop-up menu. Related choices should be grouped together. The commands
generated from the pop-up menus are the same as those generated from the menu
bar.
View choices (for example, Open as, Properties, and so forth)
Clipboard/data transfer (for example, Cut, Paste, Delete, and so forth)
Printing
Part actions
Container actions (for example, Align)
Help
ΓòÉΓòÉΓòÉ 8.3.7. Pop-Up Menus and Multiple Selection ΓòÉΓòÉΓòÉ
In the Workplace Shell on OS/2 when you select multiple objects and then bring
up a pop-up menu, you get the intersection of choices you can perform. In the
first release of OpenDoc, limited support for this feature will be provided.
There are three different cases to consider:
When the multiple selection includes intrinsic content only
When multiple embedded parts are selected only
When both intrinsic content and embedded parts are selected
In the first case, the part editor should be able to provide intersection of
choices that apply to all the intrinsic data that is selected.
Part developers are free to add more choices to the pop-up menu. For example,
if the parts are members of a suite of parts, and the part editor can
correctly determine the intersection of choices, then they should do so.
In the third case, at a minimum, any container choices (for example, Align)
that can be performed on parts should be displayed. Part editors can add more
choices as long as they apply to both the part and the selection.
ΓòÉΓòÉΓòÉ 8.3.8. Accelerator Table Sharing ΓòÉΓòÉΓòÉ
OpenDoc creates a default accelerator table containing pre-defined accelerator
keys for OpenDoc. Parts can add to the accelerator table by following these
steps:
1. Fill in an ODACCEL structure with the accelerator information.
2. Call the AddToAccelTable method of the ODMenuBar class.
The AddToAccelTable method can add up to 20 accelerator keys to the
accelerator table with each method call.
ΓòÉΓòÉΓòÉ 8.3.9. Status Line ΓòÉΓòÉΓòÉ
There are two uses for the status line. One is to display help for menu items.
The other is to display information or progress to the user for actions
occuring on within the active part.
To use the status line as help for menu items, a part should call the
SetMenuItemStatusText method of the ODMenuBar class, passing in the menu ID and
the text to be displayed. OpenDoc displays the text when the menu item is
selected by the user.
To use the status line to display information to the user, a part needs to do
the following:
1. Call the session's AcquireExtension method for the status line extension.
2. Call the StatusLineExtension method of the ODArbitrator class.
3. Display the text using the status line extension's SetStatusLineText
method.
4. Release the focus when done.
ΓòÉΓòÉΓòÉ 8.4. Undo ΓòÉΓòÉΓòÉ
The Undo command (and its reverse, Redo) is a common feature on many software
platforms. It is designed to allow users to recover from errors that they
recognize soon after making them. OpenDoc offers a flexible and powerful Undo
capability.
ΓòÉΓòÉΓòÉ 8.4.1. Multilevel, Cross-Document Capability ΓòÉΓòÉΓòÉ
Most systems support only a single-level of Undo; that is, only the most
recently executed command can be reversed. Therefore, in most platforms, Undo
is restricted to a single domain. A complex operation that involves
transferring data from one document to another, for example, cannot be
completely undone.
OpenDoc, by contrast, supports multiple levels of Undo and Redo.; there is no
limit, other than memory constraints on the user's system, to the number of
times in succession that a user can invoke the Undo command. OpenDoc also
allows for inter-document Undo and Redo. Together, these enhancements give
users greater flexibility in recovering from errors than is possible with
simpler Undo capabilities.
The OpenDoc Undo feature does not offer infinite recoverability. Some user
actions clear out the Undo action history, the cumulative set of reversible
actions available at any one time, resetting it to empty. A typical example is
saving a document; the user cannot the document by choosing Undo, and no
actions executed prior to its saving can be undone. As a part developer, you
decide which of your own actions are undoable, which ones are ignored for Undo
purposes, and which ones reset the action history. In general, actions that do
not change the content of a part (such as scrolling, making selections, and
opening and closing windows) are ignorable and do not need to be undoable.
Non-undoable actions that require clearing the action history are few; closing
your part (see Closing your Part) is one of the few.
When the user selects Redo, the effects of the last Undo are reversed. Redo is
available only if the user has previously chosen Undo, and if nothing but
ignorable actions have occurred since the user last chose Undo or Redo. Like
Undo, Redo can be chosen several times in succession, limited only by the
number of times Undo has been chosen in succession. As soon as the user
performs an undoable action (such as an edit) OpenDoc clears the Redo history
and Redo is no longer available until the user chooses Undo once again.
To implement this multilevel Undo that spans different parts and different
documents, OpenDoc maintains a centralized repository of undoable actions,
stored by individual part editors as they perform such actions. Undo history is
stored in the Undo object, an instantiation of the class ODUndo, created by
OpenDoc and accessed through the session object's GetUndo method. Your part
editor needs access to the Undo object to store undoable actions in it or
retrieve them from it.
Importance of Multilevel Undo
Your part must support multiple levels of Undo. If your part supports only a
single-level Undo command, other parts that support multilevel undo will lose
their Undo history when they interact with your part. You should support
multiple levels of Undo if you support Undo at all.
ΓòÉΓòÉΓòÉ 8.4.2. Implementing Undo ΓòÉΓòÉΓòÉ
To implement support for Undo in your part editor, you need to be able to save
information to the Undo object that allows you to recover previous states of
your part, and your part editor needs to implement the following methods:
UndoAction
RedoAction
ReadActionState
WriteActionState
DisposeActionState
The Undo object calls your part's UndoAction and RedoAction methods when the
user selects the Undo and Redo items from the Edit menu, respectively.
The Undo object calls your part's DisposeActionState method when an Undo
action of yours is removed from the Undo action history. At that point, you
can dispose of any storage needed to perform the specified Undo action.
WriteActionState and ReadActionState
Your part's WriteActionState and ReadActionState methods exist to support a
future cross-session Undo capability. OpenDoc can call these methods when it
needs you to store persistently or retrieve your undo-related information.
Version 1.0 of OpenDoc does not support cross-session Undo, however, so you
need not override these methods.
ΓòÉΓòÉΓòÉ 8.4.2.1. Adding an Action to the Undo History ΓòÉΓòÉΓòÉ
If your part performs an undoable action, it should call the Undo object's
AddActionToHistory method. Your part passes an item of action data to the
method; the action data contains enough information to allow your part to
revert to the state it occupied just prior to the undoable action. The action
data can be in any format; OpenDoc adds it to the action history in the Undo
object and passes it back to your part if the user asks your part to perform an
Undo.
The item of data that you pass to AddActionToHistory must be of type
ODActionData, which is a byte array. When you create the item, you can either
copy the data itself into the byte array or you can copy a pointer to the data
into the byte array; either way, you then pass the byte array to
AddActionToHistory.
You also pass two user-visible strings to the AddActionToHistory method. The
strings have the text that you want to appear on the Edit menu, such as "Undo
cut" and "Redo cut". You must also specify the data's action type, which is
described under Creating an Action Subhistory.
In general, you add most editing actions to the action history, unless their
data is so large that you cannot practically recover the pre-action state.
Opening or closing a document, or performing ignorable actions such as
scrolling or selecting, should not cause you to add an action to the action
history.
You decide what constitutes a single action. Entering of an individual text
character is not usually considered an action, although deleting or replacing a
selection usually is. The sum of actions performed between repositionings of
the insertion point is usually considered a single undoable action.
ΓòÉΓòÉΓòÉ 8.4.2.2. Adding Two-Stage Actions ΓòÉΓòÉΓòÉ
Some transactions, such as drag-and-drop, have two stages-a beginning and an
end. To add such a transaction to the Undo history, your part must define which
stage you are adding, by using the action types kODBeginAction and
kODEndAction. (For undoable actions that do not have separate stages, you
specify an action type of kODSingleAction when calling AddActionToHistory).
In the case of drag and drop, the sequence is like this:
The source part calls AddActionToHistory at the beginning of a drag,
specifying an action type of kODBeginAction.
The destination part calls AddActionToHistory when the drop occurs,
adding a single-stage action to the undo history by specifying
kODSingleAction.
The source part once again calls AddActionToHistory after the completion
of the drop (when the drag-and-drop object's StartDrag method returns),
specifying an action type of kODBeginAction.
Similarly, if the user selects the Paste with link checkbox in the Paste As
dialog box (see the figure in Handling the Paste As Dialog Box), the part
receiving the paste and creating the link destination adds the begin action
and end action, whereas the source part adds a single action when it creates
the link source.
As the case of drag and drop demonstrates, parts can add single-stage actions
to the undo history during the time that a two-stage undoable action is in
progress that is, between the times AddActionToHistory is called with
kODBeginAction and with kODEndAction, respectively. These actions become part
of the overall undoable action.
Menu strings and two-stage actions
The strings you can provide when calling AddActionToHistory are ignored if the
action type is kODBeginAction; only the strings passed for the action type
kODEndAction appear in the menu.
Removing a two-stage action
You can remove an incomplete two-stage action from the undo action history
without having to clear the entire history by using the
AbortCurrentTransaction method. See Clearing the Action History.
ΓòÉΓòÉΓòÉ 8.4.2.3. Creating an Action Subhistory ΓòÉΓòÉΓòÉ
In general, the undoable actions a user performs while a modal dialog box is
displayed-as long as the actions do not clear the Undo action history of your
document-should not affect the action history previous to that modal state.
After dismissing the dialog box, the user should be able to undo actions
performed before the modal dialog box was displayed-even though at this point
the user could not undo actions taken while the modal dialog box was open.
To implement this behavior, you can put a mark into the action history that
signifies the beginning of a new action subhistory. To do so, call the
MarkActionHistory method of the Undo object. To clear the actions within a
subhistory, you specify kODRespectMarks when you call the ClearActionHistory
method. To clear the whole action history, specify kODDontRespectMarks instead.
For every time you put a mark into the action history, you must make sure there
is an equivalent call to ClearActionHistory to clear that subhistory.
ΓòÉΓòÉΓòÉ 8.4.2.4. Undoing an Action ΓòÉΓòÉΓòÉ
When the user chooses to Undo an action added to the action history by your
part, OpenDoc calls your part's UndoAction method, passing it the action data
you passed in earlier. Your part should perform any reverse editing necessary
to restore itself to the pre-action state. (When a two-stage transaction
involving your part is undone, OpenDoc calls both your part and the other part
involved, so that the entire compound action is reversed).
ΓòÉΓòÉΓòÉ 8.4.2.5. Redoing an Action ΓòÉΓòÉΓòÉ
When the user chooses to Redo an action of your part, OpenDoc calls your part's
RedoAction method, passing it the same action data that you had passed in
earlier when the original action was performed. In this case, your task is to
re-achieve the state represented by the action data, not reverse it. (When a
two-stage transaction involving your part is redone, OpenDoc calls both your
part and the other part involved, so that the entire compound action is
reversed).
ΓòÉΓòÉΓòÉ 8.4.2.6. Clearing the Action History ΓòÉΓòÉΓòÉ
As soon as your part performs an action that you decide cannot be undone, it
must clear the action history by calling the Undo object's ClearActionHistory
method. Actions that cannot be undone, and for which the action history should
be cleared, include saving a changed document and performing an editing
operation that is too large to be practically saved. Undoable actions such as
normal editing and performing ignorable actions, such as scrolling or
selecting, should not affect the action history.
If your part initiates a two-stage action and, because of an error, must
terminate it before adding the second stage to the action history, it can
remove the incomplete undo action by calling the undo object's
AbortCurrentTransaction method. AbortCurrentTransaction clears the beginning
action and any subsequent single actions form the undo stack, without clearing
the entire action history.
OpenDoc itself clears the action history when it closes a document.
ΓòÉΓòÉΓòÉ 8.4.3. Undo and Embedded Frames ΓòÉΓòÉΓòÉ
Every frame has a private flag, the in-limbo flag, that determines whether any
part currently owns the frame. When a frame is initially created, whether
through CreateFrame or by cloning, its in-limbo flag is cleared.
Whenever your part cuts, pastes, drags, or drops an embedded frame, you must
keep a reference to that frame in an undo action. When you take that action,
and when you perform subsequent actions with the frame, you must set the
frame's in-limbo flag accordingly. OpenDoc requires this information to handle
moved and copied objects properly.
The following table shows how to set the flag properly for each situation, and
how to dispose of the frame referenced in your undo action when you part's
DisposeActionState method is called.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéAction ΓöéSet flag to...ΓöéIf Undone, setΓöéIf Redone, setΓöéWhen DisposeActionState is Γöé
Γöé Γöé Γöéflag to... Γöéflag to... Γöécalled Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéCreating aΓöé(leave as-is) ΓöékODTrue ΓöékODFalse ΓöéIf kODTrue, call Remove; If Γöé
Γöéframe Γöé Γöé Γöé ΓöékODFalse, call Release Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDeleting, ΓöékODTrue ΓöékODFalse ΓöékODTrue ΓöéIf kODTrue, call Remove; If Γöé
Γöécutting, Γöé Γöé Γöé ΓöékODFalse, call Release Γöé
Γöéor Γöé Γöé Γöé Γöé Γöé
Γöéstarting aΓöé Γöé Γöé Γöé Γöé
Γöédrag-move Γöé Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéCopying a Γöé(leave as-is) Γöé(Note1) Γöé(Note1) Γöé(Note1) Γöé
Γöéframe Γöé Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéPasting orΓöékODFalse (but Γöé(restore priorΓöékODFalse (but ΓöéIf kODFalse, call Release; Γöé
Γöédropping aΓöésave prior Γöévalue) Γöésave prior ΓöéIf prior value = kODTrue, Γöé
Γöéframe Γöévalue) Γöé Γöévalue) Γöécall Release; Otherwise, Γöé
Γöé Γöé Γöé Γöé Γöécall Remove Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéWhen ΓöékODFalse (if Γöé(leave as-is) ΓöékODFalse (if Γöé(nothing if drag was a copy;Γöé
ΓöéStartDrag Γöédrag was a Γöé Γöédrag was a Γöéelse as for drag-move) Γöé
Γöéreturns Γöécopy; else Γöé Γöécopy; else Γöé Γöé
Γöé Γöéleave as-is) Γöé Γöéleave as-is) Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Note1: Do not create an undo action for this situation.
You set the value of a frame's in-limbo flag by calling its SetInLimbo method
and passing either kODTrue or kODFalse. You can determine the value of a
frame's in-limbo flag by calling its IsInLimbo method.
ΓòÉΓòÉΓòÉ 9. Storage ΓòÉΓòÉΓòÉ
This is the fifth of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes the external storage facilities
OpenDoc uses and provides. OpenDoc relies upon the native file-storage
facilities of each of the individual OpenDoc platforms. The same fundamental
storage concepts and structures are used consistently for document storage and
for data transfer (clipboard transfer, drag-and-drop, and linking, described in
the next chapter).
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. Additional concepts related to your
part editor's run-time environment are found in OpenDoc Run-Time Features.
This chapter introduces the general architecture of the OpenDoc storage system,
including how parts are stored, and then describes how your OpenDoc part editor
can use that architecture to store and retrieve its own content.
ΓòÉΓòÉΓòÉ 9.1. OpenDoc Storage System ΓòÉΓòÉΓòÉ
The OpenDoc storage system is a high-level mechanism for persistent or
ephemeral storage that enables multiple part editors to effectively share a
single document. The storage system is implemented on top of the native storage
facilities of each platform that supports OpenDoc.
The OpenDoc storage system effectively gives each part its own data stream for
storage and supports reliable references from one stream to another. The system
includes a robust annotation mechanism that allows many pieces of code to
access information about a given part without disturbing its format.
The identity of a part is consistent within a session; it is unique within its
document and testable for equality with other part identities. Parts can
persistently reference other objects in the same draft, including other parts,
which allows run-time object structures to be saved and reconstructed in a
future session.
Parts have more than just their content to store persistently. Properties are
used to store both the content of the part and supplemental information.
OpenDoc defines a standard set of properties for all parts, and you can assign
additional properties to a given part. The name of the preferred part editor is
one example of a standard property that can be stored with a part.
The storage system allows OpenDoc to swap parts out to external storage if
memory is required for other parts. When a part is first needed, its part
editor reads it from external storage into memory. When it is no longer needed,
the draft releases it. If the part is needed again at a later time, the storage
system can either return the in-memory part to the part editor or-if the part
has been deleted because of low memory-bring the part once again into memory
from storage. When it reads or writes its parts, your part editor may not know
whether the data is currently being transferred to or from memory, or to or
from external physical storage.
ΓòÉΓòÉΓòÉ 9.1.1. Storage Units, Properties, and Values ΓòÉΓòÉΓòÉ
The OpenDoc storage system is not an object-oriented database. It is a system
of structured storage, in which each unit of storage can contain many streams.
This design eases the transition for developers working with existing code
bases, which generally assume stream-based I/O.
The controlling storage object is the storage system object, which instantiates
and maintains a list of containers. The containers are objects in the container
suite, a platform-specific storage implementation. Version 1.0 of OpenDoc is
released with the Bento container suite, a container suite that can be used
with or independently of OpenDoc.
Each container object in a container suite can hold one or more document
objects, each of which, in turn, contains one or more draft objects. Each draft
contains a number of storage unit objects, each of which is much like a
directory structure in a typical file system (although multiple storage units
might be physically stored in a single file, or perhaps not stored in files at
all). Storage units hold the streams of stored data.
The run-time relationships of these storage objects are diagrammed in the
following figure. the figure in section Document Storage
ΓòÉΓòÉΓòÉ 9.1.1.1. Storage-Unit Organization ΓòÉΓòÉΓòÉ
You can visualize a storage unit as a set of properties, or categorized groups
of data streams. A property is identified by its property name, an ISO string.
Each property consists of a number of streams called values, each of which
stores a different representation of the same data, in a format identified by a
named type (a value type, also an ISO string). Thus there can be several
properties (named categories) in a single storage unit, and several values
(typed streams) in a single property.
The following figure shows these relationships through a simplified diagram of
the organization of a storage unit.
The following figure is a simplified diagram of a specific storage unit in use.
The storage unit contains a figure caption. The part that owns this storage
unit has created a contents property, which contains the primary data of this
storage unit, as well as a name property, which names the item stored in this
storage unit. This particular storage unit has three representations of the
contents property (the text of the caption) as three different types of values:
its own custom styled text, plain text in a standard (international) text
format, and a bitmap. The storage unit has only one representation of the name
property.
A caller that accesses this storage unit can read or write just the data of
immediate interest. In the storage unit of the previous figure, for example, a
caller could access only the value representing one of the data formats in the
contents property without having to read the rest of the storage unit into
memory. A caller could learn the part kinds of the data stored in the storage
unit without having to read any of the contents into memory, even if the part
editor that stored the data is not present.
Fundamental to the OpenDoc storage system is the concept that all values within
a property are analogous, or equivalent, representations of the same data.
Every value in the contents property in the previous figure, for example, is a
complete representation of the item's contents, (the caption text), expressed
in the type of data that the value holds.
Values in a storage unit can include references to other storage units, as
described in Persistent References. Thus, storage units can be arranged in
ordered structures such as hierarchies; OpenDoc stores the embedding structure
of a document in this way.
ΓòÉΓòÉΓòÉ 9.1.1.2. Standard Properties ΓòÉΓòÉΓòÉ
A basic feature of the OpenDoc storage system is that storage units are
inspectable; that is, one can extract the data (values) of the various
properties of a storage unit without first having to read the entire file or
document that the storage unit is part of, and without having to understand the
internal format of the data in any of the values. For example, if the storage
unit for an embedded part includes a property that holds the part's name, you
can extract that name without having to read in all the data of the part, even
if you do not understand how to read or display the part's contents.
To make the inspectability of storage useful across parts, documents, and
platforms, OpenDoc publicly specifies certain standard properties that all part
editors can recognize. Documents, windows, frames, and parts all have basic
structures, defined by properties, that are accessible from both the stored and
in-memory states. This accessibility allows OpenDoc and part editors to read
only the information that is needed in a given situation and thus increase
performance. The table in What a Draft Contains and the table in Info
Properties list many of the common standard properties that might be found in
storage units.
Using storage-unit methods, you can access the values of any known property of
any storage unit.
ΓòÉΓòÉΓòÉ 9.1.1.3. Creating Properties and Values ΓòÉΓòÉΓòÉ
If you have a reference to a storage unit, you can add a property to it and
then add several values to that property. For the example illustrated in the
previous figure, for example, you might use statements such as these (in which
the storage unit reference is su):
su->AddProperty(ev, kODPropContents);
su->AddValue(ev, kMyKind);
su->AddValue(ev, kKindTEXT);
su->AddValue(ev, kKindPICT);
These statements add the property kODPropContents to the storage unit and give
it one standard (kODIntlText) and two custom (kCustomTextKind and
kMyBitmapKind) value types. The kODPropContents property is a standard property
name; you can add your own custom property and value types to a storage unit,
using statements such as this:
su->AddProperty(ev, kPropAnnotations);
su->AddValue(ev, kMyAnnotationsType);
These statements only set up the storage unit to receive data of a particular
type; you then need to explicitly store the data in the values. To do so, you
must first focus the storage unit, setting it up so that the data you write
will be written to the desired value of the desired property.
ΓòÉΓòÉΓòÉ 9.1.1.4. Focusing a Storage Unit ΓòÉΓòÉΓòÉ
Because any storage unit can contain a number of properties, and any property a
number of values, finding the exact piece of data in an OpenDoc document can be
more complex than finding it in a conventional document. OpenDoc allows you to
focus a storage unit, that is, access the desired data stream (defined by
property name and value type) within it before reading and writing.
You focus on a particular stream by calling the storage unit's Focus method,
and providing some combination of the following three types of specification:
Name: property name or value type
Index: the position of the property in the storage unit or the value in
the property
Position code: a code that allows you to specify, for example, the next
or previous sibling (a sibling is another value in the same property, or
another property in the same storage unit)
If you are not focusing by relative position, you specify a position code of
kODPosUndefined when you call the Focus method. For example, all of the
following calls would focus on the first value of the contents property of the
storage unit shown in the previous figure,
The names of the property and value:
su->Focus(ev,
kODPropContents, // Property name
kODPosUndefined,
kMyKind, // Value type
0,
kODPosUndefined);
The name of the property and the indexed position (1-based), within its
property, of the value:
su->Focus(ev,
kODPropContents, // Property name
kODPosUndefined,
kODNULL,
1, // Value index
kODPosUndefined);
The relative positions, compared to the current focus, of the property
and value:
su->Focus(ev,
kODPropContents // Property type
kODPosUndefined,
kODNULL,
0,
kODPosFirstSib); // Position code
The storage-unit cursor that specifies the property-and-value pair you
want:
su->FocusWithCursor(ev, cursor); // Storage-unit cursor
A storage-unit cursor is an OpenDoc object that may be convenient if you
frequently switch back and forth among specific property-and-value
combinations. You can create a number of objects of class ODStorageUnitCursor,
initialize them with the storage-unit foci you need, and pass a storage-unit
cursor to the Focus method each time you wish to switch focus. You can set up
a storage-unit cursor either by explicitly specifying the property and value
of its focus, or by having it adopt the current focus of an existing storage
unit.
Once you have focused the storage unit, you can read and write its data, as
described next.
ΓòÉΓòÉΓòÉ 9.1.1.5. Manipulating the Data in a Value ΓòÉΓòÉΓòÉ
To read or write the data of a value, remove data from a value, or insert data
into a value, you first focus the storage unit on that particular value.
To write data at a particular position in a value, call the SetOffset
method to set the position of the insertion point in the value's stream,
followed by the SetValue method to write the data.
The SetValue method takes a buffer parameter of type of type ODByteArray;
see Handling Byte Arrays and Other Parameters for more information on
byte arrays. For example, to write information from the buffer pointed to
by dataPtr into the value at the position desiredPosition, you could set
up the byte array myData as shown and then call two methods:
ODByteArray MyData;
myData._length = dataSize;
myData._maximum = dataSize;
myData._buffer = dataPtr;
SetOffset(ev, desiredPosition);
SetValue(ev, &myData);
To read the data at a particular position in a value, call the SetOffset
method, followed by the GetValue method to read the data. To read
information from the position desiredPosition in the value into a buffer
specified by the byte array myData, you could make these calls:
SetOffset(ev, desiredPosition);
GetValue(ev, &myData);
You could then extract the data from the buffer pointed to by
myData._buffer.
To read or write data at the current offset in a value, simply call
GetValue or SetValue without first calling SetOffset. specified. To
insert data at a particular offset, without overwriting any data already
in the value, call the InsertValue method (after having called SetOffset,
if necessary).
To append data at the end of a value, call GetSize to get the size of the
value, then call SetOffset to set the mark to the end of the stream, and
then call SetValue to write the data into the stream.
To remove data of a particular size from a particular position in a
value, call SetOffset to set the mark to the desired point in the stream,
and then call DeleteValue to delete data of a specified length (in bytes)
from the stream.
Note: If you change the data in one value of a property, remember that you
must make appropriate changes to all other values of that property. All
values must be complete and equivalent representations- each according
to its own format-of the information the property represents.
ΓòÉΓòÉΓòÉ 9.1.1.6. Iterating through a Storage Unit ΓòÉΓòÉΓòÉ
To examine each of the properties of a storage unit in turn, access them in
this way:
1. Call the Focus method of the storage unit, passing null values for
property name and value type, and kODPosAll for position code. The null
values "unfocus" the storage unit (that is, focus it on all properties).
2. Get the number of properties in the (unfocused) storage unit by calling
its CountProperties method.
3. Focus on each property in turn, by iterating through them using a
relative-position method of focusing:
su->Focus(ev,
kODNULL,
kODPosNextSib, // Position code
kODNULL,
0,
kODPosUndefined);
To examine in turn each of the values in a property of a storage unit, you
access them in a similar way:
1. Focus the storage unit on the desired property by calling its Focus
method and passing the desired property name and kODPosAll for relative
position of value. Using kODPosAll focuses the storage unit on all values
of that property.
2. Get the number of values in the property, by calling the CountValues
method of the focused storage unit.
3. Focus on each value in turn, by iterating through them using a
relative-position method of focusing for the values, while maintaining
the same property position:
su->Focus(ev,
kODNULL,
kODPosSame, // Position code
kODNULL,
0,
kODPosNextSib); // Position code
ΓòÉΓòÉΓòÉ 9.1.1.7. Removing Properties and Values ΓòÉΓòÉΓòÉ
You can remove a property from a storage unit by:
1. Focusing on the property to be removed; call the Focus method and pass it
a specific property name and a value position of kODPosAll.
2. Removing the property, by calling the Remove method on the focused
storage unit.
You can remove a value from a property in a storage unit by:
1. Focusing on the value to be removed; call the Focus method and pass it a
specific property name and a specific value type.
2. Removing the value; call the Remove method of the focused storage unit.
ΓòÉΓòÉΓòÉ 9.1.1.8. Storage-Unit IDs ΓòÉΓòÉΓòÉ
At run-time, the draft object assigns an identifier to each of its storage
units. A storage-unit ID is a non-persistent designation for a storage unit
that is unique within its draft (storage-unit IDs are not unique across drafts
and do not persist across sessions). You can use the ID to identify storage
units, to compare two storage units for equality at run-time, and to recreate
persistent objects from their storage units.
Just as object references are the basic run-time identifiers for OpenDoc
objects, storage-unit IDs are the basic run-time identifiers for the storage
units of persistent objects (subclasses of ODPersistentObject). Methods that
manipulate storage units often take a storage-unit ID as a parameter;
ODDraft::AcquireStorageUnit and ODDraft::ReleaseStorageUnit are examples.
For purposes in which the storage unit as a whole is passed or copied, an ID is
better than a run-time object reference. For example, storage-unit IDs are used
when cloning persistent objects (see Persistent References and Cloning). Using
a storage-unit ID ensures that the copying occurs even if the storage unit's
object is not in memory at the time. However, OpenDoc first looks for the
object in memory, and uses it rather than the storage unit if its content is
more recent than the storage unit's content.
You generally instantiate a persistent object from storage by passing a
storage-unit ID to the object's factory method (such as ODDraft::AcquireFrame
and ODDraft::AcquirePart). You can also conveniently retrieve persistent
objects that may or may not have been purged (such as frames scrolled out of
and then back into view) by retaining the storage-unit ID for the object when
you release it, and then supplying that ID when you need it again. Note also
that, in discussions that refer to part ID or object ID for any persistent
object, the ID being referred to is a storage-unit ID.
When a part initializes itself, its InitPart or InitPartFromStorage method is
passed a storage-unit ID representing its main storage unit. The part can then
retrieve any of its previously stored auxiliary storage units (see Main and
Auxiliary Storage Units) by passing a storage unit ID to
ODDraft::AcquireStorageUnit.
Storage-unit IDs are not persistent. Therefore, to get the correct storage-unit
ID when instantiating a stored persistent object, a caller must have access to
another, more permanent means of identifying a storage unit. For that purpose,
OpenDoc uses persistent references. OpenDoc provides methods (such as
ODDraft::GetIDFromStorageUnitRef) for obtaining a storage-unit ID from a
persistent reference, and vice versa (such as
ODStorageUnit::GetStrongStorageUnitRef).
ΓòÉΓòÉΓòÉ 9.1.1.9. Persistent References ΓòÉΓòÉΓòÉ
A persistent reference is a number, stored somewhere within a given storage
unit, that refers to another storage unit in the same document (see the
following figure). The reference is preserved across sessions; if a document is
closed and then reopened at another time or even on another machine, the
reference is still valid.
Persistent references allow data to be placed into multiple storage units. The
storage units reflect the run-time objects whose data they store; the
persistent references permit the reconstruction of the run-time relationships
among those objects in subsequent sessions.
ΓòÉΓòÉΓòÉ 9.1.1.9.1. Persistent References in OpenDoc ΓòÉΓòÉΓòÉ
OpenDoc uses persistent references in several situations, including these:
The stored data in a draft includes a list of persistent references to
the root frames of all windows. When a document is opened, the root
frames can then be found and their windows reconstructed.
A stored frame has a persistent reference to its part. When a frame is
read into memory, it can then find the part it displays.
A stored part has persistent references to all of its embedded frames.
When a part is read into memory, it can then find all of the embedded
frames that it contains. Persistent references to embedded frames are
essential to embedding; parts have no access to their embedded parts
except through embedded frames.
A stored part also may have persistent references to all of its display
frames. When a part is read into memory, it can then find all of the
frames that display it.
Parts can also use persistent references for hierarchical storage of
their own content data.
The following figure is a simplified diagram showing the persistent references
among stored objects in an OpenDoc Document. (The difference between the
strong and weak persistent references referred to in the figure is explained
in Persistent References and Cloning).
ΓòÉΓòÉΓòÉ 9.1.1.9.2. Creating Persistent References ΓòÉΓòÉΓòÉ
To create a persistent reference, you first focus the storage unit on the value
whose data stream is to hold the reference and then call the storage unit's
GetStrongStorageUnitRef or GetWeakStorageUnitRef method, passing the ID of the
storage unit that is to be referred to. You then store the returned reference
in the focused value in a format consistent with the type of the value. Such a
reference is then said to be from the value to the referenced storage unit.
A persistent reference is a 32-bit value of type ODStorageUnitRef. You can
create and store a virtually unlimited number of persistent references in a
storage unit value; each persistent reference that you create from a particular
value is guaranteed to be unique. Do not try to inspect or interpret a
persistent reference; the classes ODStorageUnit and ODStorageUnitView provide
methods for manipulating them.
Note: The scope of a persistent reference is limited to the value in which it
was originally stored. Do not store it in a different value; it will
almost certainly no longer refer to the correct storage unit.
Once a persistent reference is no longer needed, you should remove it from the
value in which it was written. Extra persistent references threaten the
robustness and efficiency of execution. A storage unit is aware of all
persistent references that it holds, even those that a part editor may have
stored within its contents property. OpenDoc provides an iterator class,
ODStorageUnitRefIterator, through which a caller can retrieve all persistent
references in a given value.
ΓòÉΓòÉΓòÉ 9.1.1.9.3. Persistent References and Cloning ΓòÉΓòÉΓòÉ
There are two kinds of persistent references: strong persistent references and
weak persistent references. They are treated differently in cloning.
To clone an object is to make a deep copy of it: not only is the object itself
copied, but also all objects that it references (plus the objects that they
reference, and so on). The cloning process is described further in Cloning.
In a clone operation, copies are made of all storage units referenced with a
strong persistent reference in the object being cloned. Storage units
referenced with a weak persistent reference are not copied.
The use of weak persistent references allows you to clone only portions of a
document or other structure of referenced storage units. The following figure,
which shows the persistent references among a stored frame and part plus their
embedded frame and part, illustrates how cloning gives different results,
depending on which references are strong and which are weak.
For example, if you were to clone the containing frame (A) in the previous
figure, all four objects in the figure would be cloned, representing the
containing frame and part plus its embedded frame and part; if, however, you
were to clone the containing part (B), only the part plus its embedded frame
and part (C and D) would be cloned, even though the containing part includes a
persistent reference back to its display frame (A). Likewise, if you were to
clone the embedded frame (C), only the frame and its part (D) would be cloned.
You define a persistent reference as weak or strong by calling either the
GetStrongStorageUnitRef or the GetWeakStorageUnitRef method of the storage unit
that is to hold the reference.
Stored display frames
It is recommended, although not necessary, that you store your part's display
frames persistently. This will ensure that the part can be closed and will look
the same if it is reopened later. If you do store display frames, however, make
sure that your part's persistent references to them are weak references. This
will enable you to clone the part without its display frame(s), so that the
clone can have its own display frame(s).
ΓòÉΓòÉΓòÉ 9.1.1.10. Main and Auxiliary Storage Units ΓòÉΓòÉΓòÉ
Every part (or other persistent object) has a single main storage unit, whose
kODPropContents property stores the content of that object. In most cases, you
can simply stream all of your part's data into that one storage unit. It is
possible, however, to create auxiliary storage units that hold additional data
related to the data in a main storage unit.
The procedure involves creating a strong persistent reference to the auxiliary
storage unit and storing it in your main storage unit. You can subsequently
retrieve the reference and convert it into an object reference to the auxiliary
storage unit. See Creating Additional Storage Units for more information.
ΓòÉΓòÉΓòÉ 9.1.1.11. Prefocused Access with Storage-Unit Views ΓòÉΓòÉΓòÉ
A storage-unit view is an OpenDoc object that represents a prefocused access to
a storage unit. The class ODStorageUnitView has most of the functionality of
the class ODStorageUnit, except that it has no methods for accessing different
properties and values. A storage-unit view does not in itself store data; each
storage-unit view is associated with a specific storage unit that actually does
the storing. Calls you make to access the storage unit view are passed to its
associated storage unit.
Several methods of several OpenDoc classes take a storage-unit view as a
parameter. The ODPart methods FulFillPromise, ReadPartInfo, WritePartInfo,
ReadActionState, and WriteActionState, for example, all make use of
storage-unit views. When one of your methods receives a storage-unit view as a
parameter, the method can read from the storage-unit view or write to it
without first focusing on any property or value or locking the storage unit.
You can, if desired, create storage-unit views to pass prefocused storage units
among your software components, or to any subclasses of OpenDoc objects (such
as ODNameSpace) that you might create, and whose methods take storage-unit
views.
ΓòÉΓòÉΓòÉ 9.1.2. Documents, Drafts, and Parts ΓòÉΓòÉΓòÉ
Compound documents are fundamental to OpenDoc, and each compound document is
represented by a document object. Documents are composed of one or more drafts.
This section discusses the relationship of documents to drafts and to parts,
describes how part data is stored, and discusses the objects that a document
comprises.
The document object is responsible for creating and deleting drafts, for
storing the identity of the current draft, and for collapsing multiple drafts
into one. Your part editor rarely interacts directly with its document object.
However, see Creating a New Document for information on creating a document
object.
ΓòÉΓòÉΓòÉ 9.1.2.1. Drafts ΓòÉΓòÉΓòÉ
A draft is a specific version of an OpenDoc document. Multiple drafts can be
maintained within a single document. There is always at least one draft, the
base draft, within a document. An individual draft can be extracted from a
document and placed in a new document for editing.
Drafts are created by users with the help of the document shell, and maintained
by the container suite. The figure in section Document Drafts shows an example
of the Drafts dialog box, with which the user views and manipulates the drafts
of a document. A user can save the current state of the document as a new draft
at any time. In general, your part editor can ignore the existence of separate
drafts in a document. The data you read or write is always associated with the
currently open draft.
Methods of ODDocument allow access to drafts by draft ID or by object reference
and relative position in the document. Drafts have a specific set of
properties, including creation date, modification date, and user name. Each
draft object privately maintains whatever information it needs to distinguish
itself from its predecessor draft.
The draft object is responsible for creating and tracking frame objects, part
objects, link-related objects, and storage units. It also manages the cloning
process and flags changes to the content of any of its parts. Typically, it is
to perform these tasks-and not to manipulate a draft as a version of a
document-that your part editor interacts with its draft object. Only one user
can edit a draft at a time, and only the most recent draft of a given document
can be edited. (Drafts cannot be changed if other drafts are based on them). To
enforce this, each open draft has an associated set of draft permissions. They
specify the class of read/write access that your part editor has to the draft.
The following table lists the draft permissions recognized by OpenDoc.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéConstant ΓöéExplanation Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékDPExclusiveWrite ΓöéRead access and Γöé
Γöé Γöéexclusive-write access Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékDPReadOnly ΓöéRead-only access Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékDPSharedWrite ΓöéRead access and shared-write Γöé
Γöé Γöéaccess Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékDPTransient ΓöéNavigation-only access Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékDPNone ΓöéNo access Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Note: The Bento container suite supports only the kODReadOnly and
kODExclusiveWrite draft permissions.
When your part initializes itself, or at least before attempting to modify any
of its data, it should get the permissions of its draft (by calling the
draft's GetPermissions method) and behave accordingly. For example, your part
editor should not attempt to make changes to a part when its draft has been
opened read-only. Also, certain menu items should be disabled when the user
views a read-only draft; see Document Menu and Edit Menu for descriptions.
ΓòÉΓòÉΓòÉ 9.1.2.2. Storage Model for Parts ΓòÉΓòÉΓòÉ
Parts, like other persistent objects, have a storage unit in which they can
write their state persistently. At the same time, parts are directly involved
in many other OpenDoc actions (such as binding, data translation, and data
transfer) and thus must satisfy additional storage requirements, so they can
share their data with other objects as needed. Here are some of requirements
your parts must meet:
Your part must be able to store multiple representations of its content.
Each representation must be a full representation of the content; it
cannot contain just that data that differs from the data in another
representation.
A caller must be able to extract at least one representation from your
part's storage unit, without having to understand the format of any of
the representations.
A caller must be able to remove all but one representation from your
part's storage unit, without having to understand the format of any of
the representations.
You must store your part's representations in order of fidelity, which is
the faithfulness of the representation to that of your part editor's
native format. Store them in descending order of fidelity, with your part
editor's preferred format first.
It must be possible to distinguish the fundamental content of your part
from any annotations to it or information about it. OpenDoc-or any caller
with access to your storage unit-can delete these annotations and items
of information from memory at any time, without writing them to storage,
and therefore you cannot use them to store actual content. You can,
however, use them to store non-critical optimizations such as caches.
Because property names are ISO strings, only simple naming conventions,
such as prefixes to establish ownership, are possible. For example, "TC
corporation" might use the prefix "TC:" to identify all ISO strings for
which it controls the format and meaning.
To meet these requirements, your part must create a contents property, defined
by the property name kODPropContents, in your storage unit. In that property,
you need to create-for every representation you wish to store-a value whose
type is the part kind of the representation. (Part kind is described in Part
Kinds Stored in a Part). Into each of these values, you must write one and
only one representation of your part's contents, using the data format
appropriate to that part. Order the values by fidelity.
Do not store any part content outside of the contents property. Make sure that
all other properties in your part's storage unit are for annotations or extra
information, rather than for content. Your part's contents property,
however,can include persistent references to other storage units whose
contents properties contain additional content data.
To help you decide which data you should store as content and which you should
not, consider that content includes any data that the user should be able to
save as well as any data that should persist across different part editors,
different machines, and different platforms.
This storage model offers greater flexibility than is available for
application storage on many platforms' file systems. If your applications use
only a single stream to store all of their contents, your equivalent part can
simply store everything in a single value in the contents property of its
storage unit. If your applications use resources, multifork files, or some
other form of structured storage, then your equivalent part can use multiple
storage units referenced from a value in the contents property of your part's
main storage unit.
Because you can add more storage units at any time and save references to them
in any value, you can construct an arbitrarily complex structure for your
stored data. Each additional storage unit can have multiple properties, and
each property can have multiple values. (Remember that different values within
the same property should only be used for different representations of the
same data).
ΓòÉΓòÉΓòÉ 9.1.2.3. Reading and Writing ΓòÉΓòÉΓòÉ
In OpenDoc, reading (also called internalization) is the process of converting
a part or other persistent object into its in-memory form, by reading its data
from persistent storage. Conversely, writing (also called externalization)
converts an in-memory object to its persistent form by copying its essential
data to external storage. In general, when a document is opened, its objects
are read into memory. As the user edits the document the objects are modified.
When the user saves the document, the modified objects are written back to
storage. (As noted earlier, the container suite controls how data is physically
stored; reading and writing may not necessarily involve an immediate transfer
of data between memory and an external physical storage medium).
In some instances, OpenDoc may read a part or other object but never write it
back to storage. If no changes have been made to a part and it is no longer
needed (for example, if its frame has scrolled out of sight on the screen), or
if its document is being closed without saving changes, OpenDoc simply deletes
the part, (after allowing it to release all of its references to other
objects), causing it to be removed from memory. Releasing is described further
in Reference-Counted Objects.
To maximize performance, OpenDoc requires reading and writing only when
absolutely necessary.
A part is not instantiated and read into memory until its draft's
CreatePart or AcquirePart method has been called-meaning that the part
editor is needed for tasks such as drawing, editing, frame negotiation,
or script execution. When a part is read in, OpenDoc has already bound it
to a part editor according to its part kind (and loaded the part editor
if it is not already in memory).
A part is not written to storage until the user performs a save operation
on its document, causing the part's Externalize method to be called. If
the user does not perform a save operation on a document, no changes to
any of its parts are permanently recorded.
When its document is saved or closed, no writing is required of a part if
it has not been altered since it was read into memory.
Your part editor can follow these policies strictly, or it can deviate from
them as necessary. For example, you can write out your part at any time,
without waiting for the user to save the entire document. Note, however, that,
no matter how many times your part writes its data to storage, none of those
changes will become persistent unless the user performs a save and your part's
Externalize method is called.
How to read and write your part's data is described in Reading and Writing
your Part.
ΓòÉΓòÉΓòÉ 9.1.2.4. What a Draft Contains ΓòÉΓòÉΓòÉ
The following table shows some of the kinds of information that can be stored
persistently as properties of the various objects that make up the storage
units in a draft of an OpenDoc document. Note that some of the storage units
shown in the following table reflect the objects and persistent references
shown in the figure in Persistent References in OpenDoc, as well as the
run-time object references shown in Run-Time Object Relationships. Furthermore,
some properties should not be accessed by part editors. This list is not
complete, nor are all properties shown here required to be present. For more
information on these and other standard properties, see "Types, Constants, and
Errors" in OpenDoc Class Reference for the Macintosh.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéProperty ΓöéDescription Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAny Storage Unit: Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropObjectType ΓöéType of object stored in this Γöé
Γöé Γöéstorage unit (draft, frame, Γöé
Γöé Γöépart, and so on) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéDraft Storage Unit: Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropRootPartSU ΓöéStrong persistent reference toΓöé
Γöé Γöéthe root part of this draft Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéFrame Storage Unit Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropPart ΓöéStrong persistent reference toΓöé
Γöé Γöéthe part displayed in this Γöé
Γöé Γöéframe Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropContainingFrame ΓöéWeak persistent reference to Γöé
Γöé Γöéthe containing frame of this Γöé
Γöé Γöéframe Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropFrameShape ΓöéFrame shape of this frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropPartInfo ΓöéPart info (part-specific data)Γöé
Γöé Γöéassociated with this frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropViewType ΓöéView type of the part Γöé
Γöé Γöédisplayed in this frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropPresentation ΓöéPresentation of the part Γöé
Γöé Γöédisplayed in this frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropInternalTransform ΓöéInternal transform of this Γöé
Γöé Γöéframe Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropFrameGroup ΓöéGroup ID of the frame group Γöé
Γöé Γöéthis frame belongs to Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropSequenceNumber ΓöéSequence number of this frame Γöé
Γöé Γöéin its frame group Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropLinkStatus ΓöéThe link status (in-source, Γöé
Γöé Γöéin-destination, or not in Γöé
Γöé Γöélink) of this frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsRoot ΓöéTrue if this frame is root Γöé
Γöé Γöéframe in window Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsSubframe ΓöéTrue if this frame is a Γöé
Γöé Γöésubframe Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsOverlaid ΓöéTrue if this frame is overlaidΓöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsFrozen ΓöéTrue if this frame is bundled Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropDoesPropagateEvents ΓöéTrue if this frame's part Γöé
Γöé Γöépropagates events Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéPart Storage Unit: Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropContents ΓöéPart content (the actual Γöé
Γöé Γöéstored data of this part) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropDisplayFrames ΓöéWeak persistent references to Γöé
Γöé Γöéthe display frames of this Γöé
Γöé Γöépart Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéClipboard or Drag-and-Drop Γöé Γöé
ΓöéStorage Unit: Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropContents ΓöéThe contents of the Clipboard Γöé
Γöé Γöé(or drag-and-drop object) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropSuggestedFrameShape ΓöéSuggested shape for frame, if Γöé
Γöé Γöécontents are embedded at Γöé
Γöé Γöédestination Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropLinkSpec ΓöéA link specification Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropContentFrame Γöé(Exists if data is a single Γöé
Γöé Γöéembedded frame) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropProxyContents ΓöéSuggested adornments to apply Γöé
Γöé Γöéto frame (if data is a single Γöé
Γöé Γöéembedded frame) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink Storage Unit Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropLinkSource ΓöéWeak persistent reference to Γöé
Γöé Γöéthe link-source object Γöé
Γöé Γöéassociated with this link Γöé
Γöé Γöéobject Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink-Source Storage Unit: Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropLink ΓöéWeak persistent reference to aΓöé
Γöé Γöélink object associated with Γöé
Γöé Γöéthis link-source object Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropSourcePart ΓöéWeak persistent reference to Γöé
Γöé Γöéthe part that contains (or Γöé
Γöé Γöélast contained) the source Γöé
Γöé Γöédata for this link Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropLinkContentSU ΓöéStrong persistent reference toΓöé
Γöé Γöéthe contents storage unit for Γöé
Γöé Γöéthe linked data Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropChangeTime ΓöéThe date and time of this Γöé
Γöé Γöélink's last update Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropUpdateID ΓöéThe update ID for this link's Γöé
Γöé Γöélast update Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropAutoUpdate ΓöéTrue if link is to be updated Γöé
Γöé Γöéautomatically Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
(Other standard part properties are listed in the following table.
Your part editor is responsible for reading and writing only the data that is
stored persistently by your parts; OpenDoc takes care of persistent storage of
the other objects listed in the preceding table. Basically, each of the objects
can read and write itself.
ΓòÉΓòÉΓòÉ 9.1.2.5. Info Properties ΓòÉΓòÉΓòÉ
Some of the standard properties associated with a part are made visible to the
user, either for information purposes only or to allow the user to modify them.
This set of properties, called Info properties, is displayed in the Document
Properties notebook. The last-modified date and time (kODPropModDate) is an
example of an Info property that the user cannot change; the name of the part
(kODPropName) is an example of an Info property that the user can change. The
following table lists the standard Info properties of parts defined for version
1.0 of OpenDoc.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéProperty ΓöéExplanation Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropName ΓöéThe user-assigned name of the Γöé
Γöé Γöépart Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropViewType ΓöéThe view type of the part in Γöé
Γöé Γöéthe currently selected frame Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsFrozen ΓöéTrue if the part is bundled, Γöé
Γöé Γöéfalse if not Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIsStationery ΓöéTrue if the part is Γöé
Γöé Γöéstationery, false if not Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropIconFamily ΓöéThe icons that represent this Γöé
Γöé Γöépart Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropPreferredKind ΓöéThe user-specified part kind Γöé
Γöé Γöéfor the data of this part Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropPreferredEditor ΓöéThe user-specified preferred Γöé
Γöé Γöéeditor for editing this part Γöé
Γöé Γöé(the ID of the editor that Γöé
Γöé Γöélast wrote this part to Γöé
Γöé Γöépersistent storage) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropCreateDate ΓöéThe date and time at which Γöé
Γöé Γöéthis part was originally Γöé
Γöé Γöécreated Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropModDate ΓöéThe date and time at which Γöé
Γöé Γöéthis part was last modified Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropModUser ΓöéThe name of the user that lastΓöé
Γöé Γöémodified this part Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODPropComments ΓöéComments entered by the user Γöé
Γöé Γöéinto this part's Document Γöé
Γöé ΓöéProperties notebook Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Your part editor can define and attach additional user properties to its parts'
storage units. You can then display them to the user in a Settings dialog box,
accessible from the Document Properties notebook. See The Settings Extension
for more information.
ΓòÉΓòÉΓòÉ 9.1.2.6. Container Properties ΓòÉΓòÉΓòÉ
If your part contains embedded parts, you may want the embedded parts to adopt,
by default, some of your current display settings or behavior. For example, if
your part is a text part, it may be appropriate for other text parts embedded
within it to have the same text characteristics (font, size, style, and so on),
unless the user overrides them.
You can define such a set of characteristics as properties and attach them to
your parts' storage units. These container properties then become available to
embedded parts for adoption. See Adopting Container Properties and Transmitting
your Container Properties to Embedded Parts for more information.
ΓòÉΓòÉΓòÉ 9.1.2.7. Creating a New Document ΓòÉΓòÉΓòÉ
Under most circumstances, your part editor never creates a document. Other
parts of OpenDoc, such as the document shell, handle document creation when the
user chooses a menu command such as New or drags a part to the desktop.
If, however, your part editor provides its own document-creation interface to
the user, or if it caches its own data in separate OpenDoc documents, then it
must use methods of the container suite to create those documents.
In your document creation method, you can follow steps such as these:
1. Create a file object, following whatever platform-specific procedures are
required by the file system that is in use.
2. Create an OpenDoc container, document, and draft for the file. Call the
session object's CreateFileContainer method, then the file container's
AcquireDocument method, and then the document's AcquireBaseDraft method.
3. Create the root part. Call the base draft's CreatePart method, then
obtain the root part's storage unit and assign it to the draft. Call the
draft's Externalize method to save the storage unit.
4. Assign the proper file type to the file. This involves giving it a file
type (an OSType) that represents the root part's part kind and assigning
the document a creator type (also an OSType) that is the signature of the
OpenDoc document shell (Open Scripting Architecture Quick
Referenceodtm').
5. Release the objects you have created: the root part, the draft, the
document, and the container. Finally, delete the platform-specific file
object that you created. (This does not delete the file from persistent
storage).
ΓòÉΓòÉΓòÉ 9.2. Reading and Writing your Part ΓòÉΓòÉΓòÉ
This section discusses how your part reads and writes its own content data, as
well as related OpenDoc objects, such as frames, that it uses.
Your part editor must be able to read and write any parts whose data formats
(part kinds) it recognizes. You are not required to read the entire contents of
your part into memory at once, and you need not write it all to storage at
once. However, reading must put your part in a state in which it can accept
events and handle requests, and writing it must ensure that changes made by the
user are not lost.
Your part editor should never change the part kind of any part you handle
except as a result of an explicit user request. Any translations required to
make a part readable, whether performed by OpenDoc or by your part editor, are
first selected by the user. See Binding and Translation for more information.
ΓòÉΓòÉΓòÉ 9.2.1. Initializing and Reading a Part from Storage ΓòÉΓòÉΓòÉ
A part must be able to read itself (reconstruct itself in memory by reading its
stored data). Whenever a document in which your part is visible is opened, or
whenever your part is added to a document, your part must be read into memory.
Note that OpenDoc parts should always be ready to work from an empty storage
unit as well. The two fundamental methods that your part must implement for
initializing itself are InitPart and InitPartFromStorage.
ΓòÉΓòÉΓòÉ 9.2.1.1. The somInit Method ΓòÉΓòÉΓòÉ
When your part object is first created (or recreated from storage), and before
receiving a call to its InitPart or InitPartFromStorage method, your part
receives a call to its SOM object constructor somInit. The somInit method is
inherited from the somObject class of SOM; when you subclass ODPart, you must
override somInit.
Your somInit method should initialize (clear) your part object's instance
variables. You must not perform any tasks in this method that might fail. You
can set pointer variables to null and assign appropriate values to numeric
variables. If you have any initialization code that can potentially fail, your
part's InitPart or InitPartFromStorage method must handle it.
ΓòÉΓòÉΓòÉ 9.2.1.2. The InitPart Method ΓòÉΓòÉΓòÉ
The InitPart method is called only once in your part's lifetime, by your
draft's CreatePart method, when your part is first created and has no
previously stored data to read. (If your part is created from stationery, this
method is never called). This is its interface:
void InitPart(in ODStorageUnit storageUnit,
in ODPart partWrapper);
In the simplest case, you could take these steps: Call your inherited InitPart
method, which in turn calls its inherited InitPersistentObject method to
initialize the information your part needs as a persistent object. (Your
classes' initialization methods should always call their superclasses'
initialization methods).
1. If your part stores its data in multiple part kinds, add a contents
property (type kODPropContents) and a value (of your highest-fidelity
part kind) to the storage unit passed to you, thus preparing your part
for eventual writing of its data. Add values for other part kinds also,
if appropriate. Initialize the values if necessary.
2. If your part stores more than one part kind, add a preferred-kind
property (type kODPropPreferredKind) to the storage unit passed to you,
and write into a value of that property an ISO string representing your
editor's highest-fidelity part kind. Normally, that part kind should
equal the type of the first value in your contents property.
3. Save, in a field (such as fSelf) of your part, the contents of the
partWrapper parameter passed to this method. It represents an object
reference through which OpenDoc interacts with your part. It represents
an object reference (pointer) through which OpenDoc calls back to your
part. See Part-Wrapper Object for more information.
4. Initialize any data pointers or size values that your part maintains. At
this point you might mark your part as clean (unchanged). You could use a
"dirty flag" for this purpose, which you initialize as cleared, and then
set whenever you change your part's content.
ΓòÉΓòÉΓòÉ 9.2.1.3. The InitPartFromStorage Method ΓòÉΓòÉΓòÉ
The InitPartFromStorage is similar to InitPart, except that InitPartFromStorage
also reads in data. Your draft's AcquirePart method calls your
InitPartFromStorage method and supplies a storage unit from which the part
reads itself. Your part retrieves, from the kODPropContents property of the
storage unit, the value (specified by part kind) that represents the data
stream you wish to read. This is its interface:
void InitPartFromStorage(in ODStorageUnit storage unit,
in ODPart partWrapper);
In your InitPartFromStorage method, take steps like these:
1. Call your inherited InitPartFromStorage method, which in turn calls its
inherited InitPersistentObject method to initialize the information your
part needs as a previously stored persistent object.
2. Determine the appropriate part kind of data to read in:
Your editor may be reading in a part that was created or previously
edited by a different editor (see Binding Process). Focus on the
kODPropPreferredKind of the storage unit passed to you, and retrieve
the part's preferred kind (the part kind you should consider to be
the highest-fidelity). This part kind may or may not equal the type
of the first value in the part's contents property.
If the kODPropPreferredKind property does not exist, assume that the
first value within the contents property is the preferred kind. Save
the preferred-kind designation for use later (when you write to
storage).
If this is dropped non-OpenDoc file data that you have just been bound to
(see Accepting Non-OpenDoc Data), you can take these steps:
OpenDoc will have provided, in the contents property, a value type
representing the file type. If the item is a text file, for example,
the value type is "kODKindOS2Text".
Assuming your part can read data of that file type, you can then
obtain the file type and file name from the kODFileTypeEA and
kODFileType values.
Using this information (such as the file specification), you can
then make file-system-specific calls to open the file and process
its contents into a buffer in memory. If you must store it
immediately and the draft permissions allow you to, write it into an
appropriate value or values in your part's contents property.
Otherwise, you can wait until your Externalize method is called
before writing it to your storage unit.
Skip to Step 7.
3. Focus the storage unit on its contents property and on the value
containing the part's preferred kind, if you can read that kind. If you
cannot read the preferred kind, focus on the highest-fidelity part kind
that you can read. (Even if you do not read in the preferred kind at this
stage, do not yet change the value in the kODPropPreferredKind property).
4. Read the data into your part's buffer, using the GetValue method.
5. Read in additional objects as needed, as persistent references within
your main storage unit's kODPropContents property lead you to information
stored elsewhere. See, for example, Creating Additional Storage Units,
Storing and Retrieving Embedded Frames, and Reading Linked Content from
Storage.
6. If your part's display frames have been stored, read them in as discussed
in Storing and Retrieving Display Frames.
7. Call your draft's GetPermissions method and save the results in a field
of your part. Respect the permissions in later attempts to write to the
draft. See Drafts for more information.
8. Save, in a field (such as fSelf) of your part, the contents of the
partWrapper parameter passed to this method. It represents a reference
(pointer) through which OpenDoc calls back to your part. See Part-Wrapper
Object for more information.
9. Initialize any data pointers or size values that your part maintains. At
this time you might, if you maintain a dirty flag, mark your part as
clean (unchanged).
ΓòÉΓòÉΓòÉ 9.2.2. Writing a Part to Storage ΓòÉΓòÉΓòÉ
Your part must be able to write itself (write its data to persistent storage,
typically external storage such as a disk) when instructed to do so. This
instruction may come at any time, not just when the user saves or closes your
document.
This section discusses the Externalize and ExternalizeKinds methods of your
part editor. Another method that involves writing your part data to storage is
the CloneInto method, described in CloneInto Method of your Part Editor.
ΓòÉΓòÉΓòÉ 9.2.2.1. The Externalize Method ΓòÉΓòÉΓòÉ
A caller instructs your part to write its data to storage by calling your
part's override of its inherited Externalize method. Your part then writes its
data into its storage unit.
As a minimum, your part must write one property: kODPropContents. The
kODPropContents property contains your part's intrinsic data. You can write
your part's data in multiple part kinds, representing multiple data formats, in
the kODPropContents property. Each format is a separate stream, defined as a
separate value in the property, and each value must be a complete
representation of your part's contents.
You can add other properties and values as annotations to your part's storage
unit, and you can access them for your own purposes. However, remember that the
OpenDoc binding process looks only at the value types of kODPropContents when
assigning an editor to a part.
The fundamental method that your part must override for writing its data to
storage is the Externalize method, inherited from the class ODPersistentObject.
This is its interface:
void Externalize ();
In a simple Externalize method, you might take these basic steps:
1. Call the inherited Externalize method to make sure that the appropriate
persistent-object information for your part is written to storage.
2. Examine your part to see whether it is dirty-that is, whether the content
has been changed since the last write. If it is clean (unchanged), take
no further action.
3. Get a reference to your main storage unit by calling your part's
inherited GetStorageUnit method.
4. Your editor may be writing a part that was created or previously edited
by a different editor (see Binding Process). If so, you need to prepare
the contents property of the part to match the part kinds and fidelity
order that your part editor uses. (This step is required only the first
time you write a part after having been first bound to it).
Remove any values in the storage unit that represent kinds that your
part editor does not support or does not intend to save.
Add values if necessary, so that values for all part kinds that your
part editor intends to include are present. Make sure the values are
in fidelity order, and make sure that the preferred part kind,
determined when you read the part in, is one of the values that you
write.
5. Focus the storage unit on the contents property and on each of the part
kinds of the data you are writing in turn. Write your data to the storage
unit, using its SetValue method. Write one value for each part kind of
data you store.
If your part is a container part, you write persistent references to your
embedded frames as part of writing your content. See Storing and
Retrieving Embedded Frames.
6. Store references to your display frames, as described in Storing and
Retrieving Display Frames.
7. Mark your part as clean (unchanged).
Typically, in writing your content you might write out just two values: one in
the preferred part kind, and another in a standard, widely recognized part
kind useful for data interchange. If your part editor supports many kinds, you
might allow the user to select a single default kind, to avoid the creation of
very large files full of redundant data.
Note: Do not change the preferred kind from what it was when you read the
part, even if you can easily convert the data to a higher-fidelity part
kind. (You can, however, store a higher-fidelity part kind in addition
to the preferred kind, if you wish). Such a change is implicit
translation, and translation should always be controlled by the user.
Maintain the preferred kind until the user instructs you to change it,
as in the examples described in Binding with Translation.
Your part can write its contents to its storage unit at any time, not just in
response to a call to its Externalize method. However, changes to any parts in
a document are actually made persistent only when the user performs an
explicit save, and thus only when Externalize is called. If your part updates
its storage unit but the user never saves the document, your changes are lost.
ΓòÉΓòÉΓòÉ 9.2.2.2. The ExternalizeKinds Method ΓòÉΓòÉΓòÉ
Your part's ExternalizeKinds method can be called whenever your part is
expected to save its data with a specific set of formats. For example, OpenDoc
calls ExternalizeKinds when the user saves a copy of your part's document in
multiple formats . A document-interchange utility or service might call the
ExternalizeKinds method of all parts in a document to create a version of the
document in which all parts are written in one or more common standard part
kinds.
When your part's ExternalizeKinds method is called, it is passed a list of part
kinds. This is the method's interface:
void ExternalizeKinds (in ODTypeList kindSet);
The method should write as many of the specified part kinds as it supports, as
well as your part's preferred kind. The method should ignore part kinds on the
list that it does not support, and it should remove values (other than the
preferred kind) from its storage unit that are not on the list. Just like
Externalize, the ExternalizeKinds method should make sure to write the part
kinds in proper fidelity order, and not change the preferred kind.
ΓòÉΓòÉΓòÉ 9.2.3. Creating Additional Storage Units ΓòÉΓòÉΓòÉ
Your part can use persistent references to create an auxiliary storage unit for
keeping additional data. The auxiliary unit must be referenced from your part's
main storage unit, using a strong persistent reference. In brief, you can
follow these steps:
1. Call the CreateStorageUnit method of your part's draft to create the
storage unit to hold the data.
2. Focus your part's main storage unit on the value in your contents
property that is to contain the persistent reference. (It is not
necessary to create a separate value to hold the reference).
3. Create the persistent reference, by calling the GetStrongStorageUnitRef
method of your main storage unit.
4. Store the reference anywhere in the value; the only requirement is that
you be able to recognize and retrieve the reference later. Write the
value to persistent storage, by calling the SetValue method of your main
storage unit.
Your part can later retrieve the auxiliary storage unit from the main storage
unit, in this way:
1. Focus the main storage unit on the value that contains the persistent
reference.
2. Read the value, using the GetValue method of your main storage unit.
Retrieve the persistent reference from the value's data.
3. Get the storage unit ID of your auxiliary storage unit from the
persistent reference, by calling the GetIDFromStorageUnitRef method of
your main storage unit.
4. Get the storage unit itself by passing its ID to the AcquireStorageUnit
method of your draft.
How you store the data in your auxiliary storage unit is completely up to you.
You can define your own properties, or you can keep the kinds of properties
(such as kODPropContents) used in your main storage unit. If you define your
own property names, avoid using constants for them that start with kOD; only
OpenDoc-defined constants should use those characters.
ΓòÉΓòÉΓòÉ 9.2.4. Storing and Retrieving Display Frames ΓòÉΓòÉΓòÉ
As noted in Working with your Display Frames and Facets, your part should keep
a private list of its display frames. It is further recommended that your part
persistently store those display frames when it writes itself to storage, and
retrieve them when it reads itself from storage. Your part may need information
on currently nonvisible, uninstantiated display frames in order to perform
frame synchronization (see Synchronizing Display Frames) or frame negotiation
(see Frame Negotiation).
If your part object includes a field that is a list of display-frame objects,
your InitPart method can first create the field, and your DisplayFrameAdded,
DisplayFrameRemoved, DisplayFrameConnected, and DisplayFrameClosed methods can
add or delete elements of the list, as appropriate.
You can keep additional information about the frames in this field, such as
what portion of your content (like a page number) each displays. That way, you
can instantiate only those frames that you currently need to manipulate, using
the techniques described in Lazy Instantiation.
You should store your list of frames in an annotation property in your part's
storage unit. Your Externalize method should create a property with the name
kODPropDisplayFrames in your storage unit, and write the frame list as weak
storage unit references into a value of that property.
When reading your part from storage, your InitPartFromStorage method can focus
on the kODPropDisplayFrames property and value, and read the list of stored
display frames back into your display-frame field. (Be sure your ReleaseAll
method releases any remaining display frames in that field; see The ReleaseAll
Method).
ΓòÉΓòÉΓòÉ 9.2.5. Storing and Retrieving Embedded Frames ΓòÉΓòÉΓòÉ
Your part does not explicitly store the data of embedded parts; their own part
editors take care of that. You do not even explicitly store the frame objects
that you use; OpenDoc takes care of that. You do, however, store persistent
references to the frames that are embedded in your part.
The process of storing an embedded frame for your part is simple. All you need
to do is store a strong persistent reference to the embedded frame object;
OpenDoc takes care of storing the frame itself. You follow the same steps that
you take when creating and storing a reference to any storage unit (described
in Creating Additional Storage Units):
1. Focus your storage unit on your contents property and the value that is
to contain the persistent reference to the embedded frame.
2. Create the persistent reference and place it in your contents data.
3. Write the data back into the value.
When recreated at a later time, your part can then retrieve the frame this
way:
1. Focus the storage unit on the value containing the persistent reference.
2. Retrieve the reference.
3. Get the storage-unit ID of the frame.
4. Recreate the frame itself, by calling your draft's AcquireFrame method.
For efficient memory use, you can instantiate only those embedded frames that
you currently need to manipulate or display, using the techniques described in
Lazy Instantiation.
ΓòÉΓòÉΓòÉ 9.2.6. Reading and Writing Part Info ΓòÉΓòÉΓòÉ
As noted in Part Info, you can use the part info field of a frame or facet to
store any display-related information you want associated with that particular
frame or facet. Because frames are persistent objects and facets are not, a
frame's part info can be stored persistently, whereas a facet's part info
cannot.
Just as you store multiple representations of your part's data, you should
store multiple representations of your frames' part info, so that other editors
can potentially read your part info as well as your part content. In that case,
you should give your part info formats names equivalent to the part kinds they
are associated with. For example, if you write part data whose part kind is
"SurfCorp:SurfWriter:StyledText", your associated part info data should be
written in a value whose type is "SurfCorp::SurfWriter:StyledText:PartInfo".
If you also write your part data in a standard format such as PICT or RTF or
JPEG, you would not write associated part info for those formats, because they
have no associated part info format.
As with other changes to the contents of your part's draft, any time you place
or modify information in your display frame's part info field, you should
follow the procedures listed in Making Content Changes Known. (If your frame is
a nonpersistent frame, however, you do not need to make changes to it known).
Whenever your document is saved, your part's WritePartInfo method is called for
each of your part's display frames. Here is its interface:
void WritePartInfo(in ODPointer partInfo,
in ODStorageUnitView storageUnitView);
On receiving this call, you could first examine your part's own part-info dirty
flag (if you have defined one) to see if this frame's part info has changed
since it was read in from storage. If it has, focus on as many values in the
provided storage unit view as is appropriate and write your current part info
into them. (And then clear the dirty flag).
Conversely, whenever a display frame of your part is read into memory, your
part's ReadPartInfo method is called. Here is its interface:
ODPtr ReadPartInfo(in ODFrame frame,
in ODStorageUnitViewstorageUnitView);
On receiving this call, you should allocate a structure to hold the part info,
focus on the appropriate value in the provided storage-unit view, and read the
data into your part-info structure. (And initialize your part-info dirty flag
to clean).
Your part also writes part info data when its display frames are cloned; see
ClonePartInfo Method of your Part Editor for more information.
ΓòÉΓòÉΓòÉ 9.3. Changing your Part's Content ΓòÉΓòÉΓòÉ
Users can change the content of your part through a variety of standard editing
actions involving keyboard input, menu commands, scripting, and so on. How you
can support those tasks is described throughout this book, with additional
information and other programming texts.
What this section describes is what you must (or need not) inform OpenDoc of
when you do make content changes, and what it fundamentally means to embed
parts within your part and remove embedded parts from it.
ΓòÉΓòÉΓòÉ 9.3.1. Adding an Embedded Part ΓòÉΓòÉΓòÉ
Embedding is essentially a process of data transfer. Your part embeds a new or
preexisting frame into its content; that embedded frame displays the content of
a new or preexisting part. You create a new embedded frame by calling your
draft's CreateFrame method; you embed a preexisting frame by calling your
draft's AcquireFrame method.
The data-transfer procedures for embedding a part within your part are
described in detail in Pasting from the Clipboard, Dropping, and Using a Tool
Palette to Embed Parts. See those sections for more information. In summary,
however, you perform these tasks when you embed:
1. You decide, based on your own part's content model and on information
accompanying the transferred data, what area is to be given to the
embedded part for display:
You retrieve or define a frame shape for the embedded frame. (The
part may or may not arrive with a suggested frame or frame shape).
You decide where to position the embedded frame in your part's
content; based on that position, you will eventually create an
external transform to use for positioning the facets of the embedded
frame.
2. For each of your own part's display frames that is to show the embedded
part, you create or recreate a frame object, and you either explicitly or
implicitly create or recreate a part object:
If the frame you are embedding is not already attached to its part,
you either retrieve the part object (as when embedding pasted
content that has no preexisting frame or adding a new frame to an
existing embedded part) or you create a new part object (as when
creating a new part from a palette), so that you can attach it to
its frame. The embedded part is stored in your part's draft, but not
within your part itself.
You either retrieve the supplied frame object (as when embedding
pasted content that comes with a display frame) or create a new
frame object (as when adding a new frame to an existing embedded
part or creating a new part from a palette). You store a reference
to the frame somewhere in your part's content. The frame object
itself is stored in your part's draft, but not in your part.
3. If the embedded part's frame is presently visible, you create a new facet
(or facets) to display the embedded part.
4. As necessary or appropriate, you perform other tasks related to
embedding, such as those described in the rest of this section. Finally,
you notify OpenDoc and your own containing part that you have changed
your part's content.
When one of your embedded parts is removed, your part is responsible for
releasing the embedded frame, removing all the facets of that frame, and
redisplaying its own content.
ΓòÉΓòÉΓòÉ 9.3.2. Removing an Embedded Part ΓòÉΓòÉΓòÉ
To remove an embedded part from your part, follow the instructions for removing
its frame, described in Removing an Embedded Frame.
This illustrates the fact that your part is not actually concerned with
removing embedded parts themselves. Under user instruction or for other
reasons, your part may have reason to remove one or more embedded frames; once
the last display frame of a given embedded part is removed from your part, that
part is no longer embedded in your part.
ΓòÉΓòÉΓòÉ 9.3.3. Making Content Changes Known ΓòÉΓòÉΓòÉ
Whenever you make any significant change to your part's content, whether it
involves adding, deleting, moving, or otherwise modifying your intrinsic
content or your embedded frames, you must make those changes known to OpenDoc
and to all containing parts of your part.
Call the ContentUpdated method of each of your part's display frames in
which the change occurred, passing it an identifying update ID obtained
from the session object's UniqueChangeID method (or propagated from a
link source, as described in Link Update ID, or from
EmbeddedFrameUpdated, described in The EmbeddedFrameUpdated Method of
your Part Editor This call notifies your containing parts (and their
containing parts, and so on) that your content has changed. The
notification is necessary to enable all containing parts to update any
link sources that they maintain.
Call your draft's SetChangedFromPrev method. This marks the draft as
dirty, giving your part the opportunity to write itself to storage when
the draft is closed.
You should call these methods as soon as is appropriate after making any
content changes. You may not have to make the calls immediately after every
single change; it may be more efficient to wait for a pause in user input, for
example, or until the active frame changes. In addition to sending these
notifications, you must of course also update your part's display if
appropriate; this may involve adding or removing facets, adjusting intrinsic
content, and invalidating areas that need to be redrawn.
ΓòÉΓòÉΓòÉ 9.4. Closing your Part ΓòÉΓòÉΓòÉ
When the user closes the document containing your part, or when your part is
deleted from its containing part, OpenDoc calls your part's ReleaseAll method,
followed by its somUninit method.
ΓòÉΓòÉΓòÉ 9.4.1. The ReleaseAll Method ΓòÉΓòÉΓòÉ
The ReleaseAll method is inherited from ODPersistentObject. Its purpose is to
ensure that all your part's references to other objects and ownership of shared
resources are relinquished before your part is itself deleted from memory. Your
override of the ReleaseAll method should perform at least these tasks:
It should call the Release methods of all reference-counted objects it
has references to, including iterating through its private lists of
embedded frames and display frames and releasing each one.
It should call the BaseRemoved method of any of its own extensions.
It should remove any link specifications it has written to the Clipboard.
It should fulfill any promises it has written to the clipboard.
It should relinquish all foci that it owns.
It should clear the undo stack if it has written any undo actions to it.
It should call the PartRemoved method of any embedded-frames iterators it
has created.
Your part should not write itself to storage from within its ReleaseAll
method. Your part's Release method, inherited from ODRefCntObject, is called
under different circumstances from ReleaseAll. For information on implementing
a Release method, see Reference-Counted Objects.
ΓòÉΓòÉΓòÉ 9.4.2. The somUninit Method ΓòÉΓòÉΓòÉ
After it completes ReleaseAll, your part receives no subsequent method calls
except to its SOM object destructor somUninit. The somUninit method is
inherited from the somObject class of SOM; when you subclass ODPart, you must
override somUninit.
Your somUninit method should dispose of any storage created for your part
object by the somInit method and any other storage related to additional
instance variables of your part initialized during execution. Do not perform
any tasks in this method that could fail.
ΓòÉΓòÉΓòÉ 10. Data Transfer ΓòÉΓòÉΓòÉ
This is the sixth of eight chapters that discuss the OpenDoc programming
interface in detail. It describes how OpenDoc provides support for
data-transfer operations.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. You should also be familiar with
OpenDoc storage concepts, as presented in the previous chapter. For additional
concepts related to your part editor's run-time environment, see OpenDoc
Run-Time Features.
This chapter describes general issues common to all data-transfer methods, and
then describes how your part can support
Clipboard data transfer
Drag and drop data transfer
Linking
ΓòÉΓòÉΓòÉ 10.1. Storage Issues for Data Transfer ΓòÉΓòÉΓòÉ
This section introduces some of the storage-related concepts used in clipboard
transfer, drag and drop, and linking, which are described in detail later in
this chapter.
For the purposes of this section, the term data-transfer object means any of
the following: the clipboard or drag and drop object (when reading or writing
data), a link-source object (when writing linked data), or a link object (when
reading linked data).
ΓòÉΓòÉΓòÉ 10.1.1. Data Configuration ΓòÉΓòÉΓòÉ
When a part places data on the clipboard or other data-transfer object, the
part writes a portion or all of its intrinsic content, and the part can also
cause the contents of one or more embedded parts to be written.
Whatever the nature of the data, it can be considered an independent part once
it is written to the data-transfer object. The transferred part has its own
intrinsic content and it may contain embedded parts. In writing or reading the
data, your part is directly concerned only with the characteristics (such as
part kind) of the intrinsic content of the transferred part. Any embedded parts
are transferred unchanged-as imbedded parts-during the process.
The storage unit for the data-transfer object's intrinsic content is called its
content storage unit. It is the main storage unit for the content of the
data-transfer object, equivalent to a part's main storage unit (see Main and
Auxiliary Storage Units). Any embedded parts in the transferred data, and any
other OpenDoc objects, have their own storage units.
Note: A link object or a link-source object is a persistent object, and as
such has its own main storage unit. That storage unit is not the same as
its content storage unit. Always access the content of a data-transfer
object by calling its GetContentStorageUnit method.
The intrinsic content is stored, as expected for any part, in the contents
property (type kODPropContents) of the content storage unit. When writing to a
data-transfer object, your part can store data in multiple formats in
different values of the contents property. Just as with a part, each value in
the contents property must be complete; it must not depend on other properties
or other values of the property.
The contents property is not the only property you access in writing to or
reading from a data-transfer object. When your part writes all or part of its
intrinsic data to a data-transfer object, it does not write a display frame
for the data, but it does write a suggested frame shape for the destination
part to use as a guide when constructing a display frame. Likewise, if your
part places a single embedded frame in a data-transfer object, it also writes
the frame object into a property of the content storage unit. See Annotations
for more information.
In-memory objects take precedence over their stored versions in data transfer.
If the user cuts or copies a part's frame to the clipboard or other
data-transfer object, the moved data represents the current state of the part,
including any edits that have not yet been saved to disk.
ΓòÉΓòÉΓòÉ 10.1.2. Annotations ΓòÉΓòÉΓòÉ
This section discusses the items, in addition to part content, that you can
write to or read from the storage unit of a data-transfer object.
ΓòÉΓòÉΓòÉ 10.1.2.1. Link Specification ΓòÉΓòÉΓòÉ
A part copying content to the clipboard or drag and drop object advertises the
ability to create a link by writing a link specification in addition to
content. When you copy data to the clipboard or drag and drop object, you
should create a link specification-using your draft's CreateLinkSpec method-in
case the user chooses to link to the data when pasting or dropping it in the
destination. Writing Intrinsic Content. illustrate when to write a link
specification.
Write the link specification onto the content storage unit of the clipboard or
drag and drop object as a property with the name kODPropLinkSpec. The data in a
link specification is private to the part writing it. The data you place in
your link specification is returned to your part if and when its CreateLink
method is called to create the link. All that your part needs from the link
specification data is sufficient information to identify the selected content.
Because the link specification is valid only for the duration of the current
instantiation of your part, the link specification can contain pointers to
information that you maintain.
Link specifications are not necessary in the following situations:
Do not write a link specification when you place content on the clipboard
as a result of a cut operation; you cannot link data that is no longer in
your part. (Because you cannot know at the start whether a drag will be a
move or a copy, you should always write a link specification when you
write data to the drag and drop object.)
Do not write a link specification when you write all or a portion of a
link destination (or any of your part's content, if your part itself is
in a link destination) to the clipboard or drag and drop object; that
could make your link destination a source of another link, which is
technically possible but generally a bad practice. (see the second table
in Creating a Link that Includes Already-Linked Content for an
explanation).
Do not write a link specification when your draft permissions (see
Drafts) are read-only. You would not be able to establish links to other
parts of the same (read-only) document, and any links to other documents
would be broken when your draft closed.
Do not write a link specification into a link-source object. Link
specifications are for the clipboard or drag and drop object only. Link
specifications are transitory, whereas links are persistent.
When you write a link specification to the clipboard, obtain and save the
clipboard update ID (see Clipboard Update ID). You must remove a link
specification from the clipboard if your source data changes so that the
clipboard data no longer reflects it, and you need the update ID to do that.
See Removing a Link Specification from the Clipboard.
(You never need to remove a link specification from the drag and drop object,
because it is valid only during the course of a single drag operation.)
Note: The kODPropLinkSpec property is not copied when the storage unit
containing it is cloned.
ΓòÉΓòÉΓòÉ 10.1.2.2. Frame Shape or Frame Annotation ΓòÉΓòÉΓòÉ
When you place intrinsic content (with or without embedded frames) on the
clipboard or other data-transfer object, there is no frame associated with that
content. You should, nevertheless, write a frame shape to the data-transfer
object to accompany the content; the shape is a suggestion to any part that
reads the data and must embed it as a separate part. You write the frame shape
into a property named kODPropSuggestedFrameShape in the data-transfer object's
content storage unit.
Likewise, if your part is receiving a paste or drop and copies intrinsic data
from the data-transfer object that it must embed, you should examine the
kODPropSuggestedFrameShape property to get the source part's suggested frame
shape for the data. If the kODPropFrameShape property does not exist, use your
own part-specific default shape.
If you place a part (in the form of a single embedded frame) on the clipboard
or other data-transfer object, you must place the frame object there also. You
write the frame into a property with the name kODPropContentFrame in a storage
unit of the data-transfer object's draft.
Likewise, if your part is receiving a paste or drop and copies the single
embedded part, you can note from the presence of the kODPropContentFrame
property that the data represents a single frame that should be embedded, and
you can retrieve the frame from the property.
When you transfer a single embedded frame, you can specify the frame location
relative to your part's content (that is, the offset specified in the external
transform of the frame's facet) by incorporating the offset into the frame
shape that you write. Then, the receiving part of the paste or drop can, if
appropriate to its content model, use that offset to construct an external
transform.
Note: The kODPropSuggestedFrameShape and kODPropContentFrame properties are
not copied when the storage unit containing it is cloned.
ΓòÉΓòÉΓòÉ 10.1.2.3. Proxy Content ΓòÉΓòÉΓòÉ
When you write a single embedded frame to a data-transfer object, you can
optionally write any intrinsic data that you want to be associated with the
frame. The intrinsic data might be a drop shadow or other visual adornment for
the frame, or it might be information needed to reconstruct the frame as a link
source or link destination.
This information is called proxy content; to write it, you add a the
kODPropProxyContents property to the data-transfer object, and write your data
into it as a value that your part recognizes. If the transferred part is
subsequently embedded into a part that also recognizes that value and knows how
to interpret it, the added frame characteristics can be duplicated.
Likewise, if your part is the receiving part of a paste or drop and copies the
single embedded part, you can note from the presence of the
kODPropProxyContents property that proxy content for that frame exists. If your
part understands the format of the proxy content-which you should be able to
determine by examining the value types in the property-you can read it and
duplicate the frame characteristics.
Note: The kODPropProxyContents property is not copied when the storage unit
containing it is cloned.
ΓòÉΓòÉΓòÉ 10.1.2.4. Part Name or Category Annotation ΓòÉΓòÉΓòÉ
Whenever you write any of your part's intrinsic content to the clipboard or
other data-transfer object, write also a name for the data. The name should be
the name of your part or-if your part has no name-its part category. Write the
name into a property with the name kODPropName in the data-transfer object's
storage unit. A name property is user-visible text, so you should write it in
international-text format, as described in User Strings.
ΓòÉΓòÉΓòÉ 10.1.2.5. Mouse-Down Offset Annotation ΓòÉΓòÉΓòÉ
If your part initiates a drag operation (see Initiating a Drag), you need to
create a property named kODPropMouseDownOffset in the drag and drop object's
storage unit. Write into that property a value that specifies the location of
the mouse-down event that triggered the drag. The value should be of type
ODPoint and should contain the offset from the origin of the content being
dragged.
If your part receives a drop, it should likewise check for the presence of the
kODPropMouseDownOffset property. If the property exists, and if taking it into
account is consistent with your part's content model, use it to locate the
dropped content in relation to the mouse-up event that marks the drop.
Note: The kODPropMouseDownOffset property is not copied when the storage unit
containing it is cloned.
ΓòÉΓòÉΓòÉ 10.1.2.6. Drag and Drop Annotations (OS/2) ΓòÉΓòÉΓòÉ
Each content storage unit of the drag and drop object contains a PM DRAGITEM
structure corresponding to a drag item in the current drag and drop operation.
It is written by the drag and drop object under the kODDragitem value of the
kODPropContents property. Usually parts don't have to interact with this value
other than in their Drop method, when they have to create a storage unit view
for it and pass it to the GetDataFromDragManager method of the drag and drop
object to perform data rendering.
The drag and drop object supports data rendering for two mechanisms:
DRM_SHAREDMEM and DRM_OS2FILE. If either one of these mechanisms has been
selected (RMF selection is done by calling drag and drop CanEmbed or
CanIncorporate methods from within the part's DragEnter method), the storage
unit rendered by GetDataFromDragManager will contain the rendered data with no
kODDragitem value. If the selected rendering mechanism is DRM_OS2FILE and this
is not an OPENDOC initiated drag, the rendered storage unit will contain the
following values for its kODPropContents property:
Selected kind Part kind that the selected RMF was mapped to; no value
is set for this kind. The actual data has to be read
from the dropped file.
kODFileType Name of the dropped file; value type is ODISOStr.
kODFileTypeEA Any extended attributes that are associated with the
file; value type is ODISOStr.
If the selected rendering mechanism is not handled by the drag and drop
object, the target part has to perform the data rendering. In this case, the
kODPropContents in the storage unit rendered by GetDataFromDragManager will
contain a set of values necessary for the part to carry out the rendering
conversation:
kODDragitem DRAGITEM structure; value type is DRAGITEM.
kODSelectedRMF Selected rendering mechanism and format pair; value type
is ODISOStr.
kODSelectedKind Part kind that the selected RMF was mapped to; value
type is ODISOStr.
kODDragOperation PM drag operation from the DRAGINFO structure associated
with this drag; value type is ODUShort.
ΓòÉΓòÉΓòÉ 10.1.2.7. Clonable Data Annotation Prefix ΓòÉΓòÉΓòÉ
When objects are cloned (see Cloning), the in-memory version of an object is
given preference over its storage unit, so that recent, unsaved changes to the
object are included in the cloning operation. This means, however, that any of
its properties that the object itself is unaware of are not cloned, because it
does not write them into the destination storage unit.
There are situations in which an object can store properties in the storage
unit of a part, without the knowledge or cooperation of the part itself. For
example, a service such as a spell checker might store a dictionary of
exceptions as a property of a part's storage unit. The part is unaware of the
existence of that property, but the spell checker would want the dictionary
cloned whenever the part is cloned.
To support this capability, OpenDoc defines the property prefix constant
kODPropPreAnnotation, whose value is OpenDoc:Annotation:. When that prefix is
part of a property name (for example, OpenDoc:Annotation:Exceptions) in an
object's storage unit, OpenDoc always copies that property when the object is
cloned, regardless of whether the object being cloned is aware of the existence
of the property. Therefore, if your part stores data in another object that the
object itself does not use but that you want to be cloned along with the
object, make sure you store it in a property whose name starts with
OpenDoc:Annotation:.
ΓòÉΓòÉΓòÉ 10.1.3. Cloning ΓòÉΓòÉΓòÉ
You should always transfer data to and from the clipboard or other
data-transfer object in the context of cloning. This section describes how
cloning works, what the scope of a cloning operation is, and how to implement
your part editor's CloneInto method.
To clone an object is to copy the object itself as well as all objects that it
references, plus any objects that those objects reference, and so on.
Typically, copies are made of all storage units-or their equivalent,
instantiated persistent objects-that are referenced with strong persistent
references, starting with the object being cloned. Storage units referenced
only with weak persistent references are not copied. For more information on
how strong and weak persistent references affect cloning, see Persistent
References and the figure in section Persistent References and Cloning.
Actually, each object that is cloned during the operation decides-if it is in
memory at the time-which of its own storage-unit's properties and which of its
referenced objects should be included. If the object is not currently
instantiated, all of its storage unit's properties and all of its strongly
referenced objects are copied.
ΓòÉΓòÉΓòÉ 10.1.3.1. Cloning Process ΓòÉΓòÉΓòÉ
All persistent objects have a CloneInto method by which they clone themselves,
but your part editor should not call the CloneInto method of any object
directly. Instead, you clone an object by calling the Clone (or WeakClone)
method of its draft object. The Clone method in turn calls the CloneInto method
of the object involved.
Cloning is a multistep transaction, designed so that it can be terminated
cleanly if it fails at any point. You perform a clone by calling three methods,
in this order:
1. Call the BeginClone method of the draft object of the data to be cloned.
If you are transferring data from your part, call your part's draft
object; if you are transferring data from a data-transfer object, call
that object's draft object. BeginClone sets up the cloning transaction.
(If you are cloning as a result of pasting or dropping data into your
part, you must also specify the destination frame, the display frame of
your part that is receiving the paste or drop.)
2. For each object that you are cloning, call the draft's Clone method.
Clone allows the draft object to specify and recursively locate all
objects that are to be cloned. It calls the CloneInto method of the
object to be copied, which results in calls to the CloneInto methods of
all referenced objects, and so on. For example, when Clone calls the
CloneInto method of a part, the part clones its embedded frames; the
embedded frames, in turn, clone the parts they display, and so on.
(When you are cloning within the context of your own CloneInto method,
you sometimes call the draft's WeakClone method instead of Clone. See
CloneInto Method of your Part Editor for more information.) When you call
Clone, it is important to make sure that the object you are cloning is
valid. Take these steps:
a. Starting with the persistent reference that specifies the object to
be cloned, first call the IsValidStorageUnitRef method of the
storage unit or storage unit view that contains the persistent
reference. You can never automatically assume that a persistent
reference is valid.
b. Get the object's ID (See Storage-Unit IDs) from the persistent
reference by calling the GetIDFromStorageUnitRef method of the
storage unit.
c. Pass the object ID to the Clone method.
d. Save the resultant object ID that Clone returns, along with the IDs
returned from other calls to Clone, until cloning is complete and
you have called EndClone.
If you are not actually cloning objects but simply reading or writing
intrinsic data, this is the point at which to do that (instead of calling
Clone).
3. Call the draft's EndClone method. EndClone commits to and actually
performs the cloning operation.
If, at any time after calling BeginClone, the operation cannot be
completed, you can terminate the transaction by calling the AbortClone
method instead of EndClone.
Note: You cannot instantiate and use any cloned object until after the entire
cloning operation is complete. If you are cloning several parts and
link objects, for example, you cannot call AcquirePart or AcquireLink
until after you have cloned all of the objects and EndClone has
successfully returned.
When you call BeginClone, you are returned a draft key, a number that
identifies that specific cloning transaction. You pass that key to the other
cloning methods that you call during the transaction. You also specify in the
kind parameter to the BeginClone method the kind of cloning operation that is
to be performed, so that OpenDoc can maintain the proper behavior for linked
data that is being transferred. The following table lists the kinds of cloning
transactions that OpenDoc recognizes. Cloning explains how these different
types of transactions result in different behavior for links. The following
table lists the kinds of cloning operations recognized by OpenDoc.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöékind parameter ΓöéMeaning Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneCopy ΓöéCopy object from source to Γöé
Γöé Γöédata-transfer object Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneCut ΓöéCut object from source to Γöé
Γöé Γöédata-transfer object Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODClonePaste ΓöéPaste object from Γöé
Γöé Γöédata-transfer object to Γöé
Γöé Γöédestination Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneDropCopy ΓöéCopy object to the destinationΓöé
Γöé Γöéof a drop Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneDropMove ΓöéMove object to the destinationΓöé
Γöé Γöéof a drop Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneToLink ΓöéCopy object from source to Γöé
Γöé Γöéupdate a link source Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneFromLink ΓöéCopy object from link to Γöé
Γöé Γöéupdate a destination Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöékODCloneAll ΓöéClone all objects (your part Γöé
Γöé Γöéshould not use this) Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Even when transferring only intrinsic content (and not actually cloning any
objects), you should still bracket your transfer with calls to BeginClone and
EndClone. That way, you notify OpenDoc of the kind of operation (such as cut
or copy) that is being performed and you ensure that the right actions occur
at both the source and destination of the transfer.
In cloning, the in-memory version of an object takes precedence over its
stored version. For this reason, an object does not need to be written to
storage prior to being cloned. If the object is in memory, its CloneInto
method is called to perform the clone; if the object is not in memory, its
storage unit performs the clone. This also means, however, that, if an object
is in memory, properties attached to its storage unit that the object itself
does not know about might not be copied, unless they are specially named; see
Clonable Data Annotation Prefix for an explanation.
Note that your parts, as persistent objects, must provide a CloneInto method.
See CloneInto Method of your Part Editor for more information.
Null IDs when cloning links
The Clone method returns a value of kODNullID if the desired object cannot be
cloned. For example, if you attempt to clone a link object or link-source
object into a link, Clone does not permit it and returns kODNullID. In such an
instance, you cannot clone the object so you should delete any data associated
with it that you have written into the data-transfer object. However, in the
case of a link or link source you should still write the content formerly
associated with the object, but as unlinked content.
Annotation properties not cloned
When data is cloned from a data-transfer object, most of the annotation
properties that the source part may have added to the content storage unit are
not transferred, because they make no sense as properties outside of the
data-transfer object. Specifically, the properties kODPropLinkSpec,
kODPropSuggestedFrameShape, kODPropContentFrame, kODPropProxyContents, and
kODPropMouseDownOffset are never copied during a clone.
ΓòÉΓòÉΓòÉ 10.1.3.2. Scope of a Clone Operation ΓòÉΓòÉΓòÉ
For cloning, the scope defines the set of objects that are to be included in
the cloning operation. Scope is expressed in terms of a frame object or its
storage unit.
Because a part can have more than one display frame, and because each frame can
include a separate set of embedded frames and parts, it is important to specify
the frame whose enclosed objects are to be cloned. Otherwise, extra embedded
parts or other objects not needed by the copy may be included unnecessarily..
In the example shown in the following figure, the user has selected some
content in the root part that includes display frame 1 of embedded part A. The
root part writes its intrinsic content and then clones part A, passing it a
scope of frame A1. Any content that belongs only to frame A2 (such as part C)
is not included in the clone.
Scope changes during the course of a clone. Continuing the same example as that
shown in the previous figure, the next figure shows how part A clones itself in
the context of the scope (frame A1) specified by the root part. Part A writes
the intrinsic content of its display frame A1 and then clones part B twice,
first passing it a scope of frame B1 and then a scope of frame B2. Part B thus
gets called to clone itself twice, with different scopes. Any content of B
within frames B1 and B2 is included, but any content that belongs only to frame
B3 is not.
You can specify null for the scope of a clone if you want all objects copied,
regardless of what display frame they are associated with.
ΓòÉΓòÉΓòÉ 10.1.3.3. CloneInto Method of your Part Editor ΓòÉΓòÉΓòÉ
If your part is an embedded part that is written to the clipboard (or other
data-transfer object), your part's override of its inherited CloneInto method
is called by your draft's Clone method. This is the interface to CloneInto:
void CloneInto(in ODDraftKey key,
in ODStorageUnit toSU,
in ODFrame scope);
Your CloneInto method is passed a draft key for the clone operation and a frame
that defines the scope of the cloning. The method should write your part's
intrinsic content to the provided storage unit, and it should clone in turn any
objects (such as embedded frames or auxiliary storage units) that it
references. It needn't clone any objects or write any data that is outside of
the scope.
Note: Do not implement your CloneInto method by writing your part to storage
and then cloning your storage unit. Such a method would have performance
penalties because of the extra time needed to store your data, and might
copy unneeded objects because the scope of the clone would be ignored.
To support efficient data transfer, your part should, if possible, write a
promise (see next section) instead of writing its actual intrinsic data when
CloneInto is called. It is possible to write a promise only when your part is
placed into the data-transfer object as a single, standalone frame with no
surrounding intrinsic content of its containing part. In any other situation,
your CloneInto method might have been called to help fulfill a promise, in
which case writing a promise would be inappropriate. You can determine whether
you can write a promise by examining the provided storage unit. If it contains
a property with the name kODPropContentFrame, your part is the sole content of
the storage unit's contents property, and you can write a promise instead of
actual data.
When you write a promise, be sure to identify (to yourself) the display frame
or frames of your part that are within the scope of the clone operation, so
that your FulfillPromise method can write the proper content when it is
called.
Take these general steps in your CloneInto method:
1. Check to see if your part has already been cloned in this operation.
Because an object can be referenced more than once, its CloneInto method
can be called more than once in a single cloning. In general, if a
contents property already exists in the storage unit passed in the
method, your part has already been cloned and there is no need to repeat
the process.
An exception to this rule occurs when scope is significant. If your part
is called to clone itself with different scopes during the same operation
(see, for example, the previous figure), it may have to write additional
data each time its CloneInto method is called.
2. Call your inherited CloneInto method.
3. If it does not already exist, add a property named kODPropContents to the
provided storage unit. (You do not have to call BeginClone; that method
will already have been called. Also, you do not have to add properties
other than the contents property; OpenDoc will add your part's name and
any other needed annotations.)
4. Check to see if you can write a promise. Look for a property with the
name kODPropContentFrame in the storage unit. Don't look for a value in
the property; it may be written later.
5. Focus on the contents property, and write a value for each part kind you
support. Either write a promise or the data of your part itself, using
either the storage unit's SetPromiseValue or SetValue method,
respectively.
(For data transfer, it is especially important to write a standard format
in addition to your own native part kind, because the ultimate
destination of the transferred data is unknown.)
6. Clone all objects that your part references (that are within the scope of
the cloning operation) as follows:
For each object to which your part has a strong persistent
reference, call your draft's Clone method to clone the object,
passing the same draft key that was passed to your CloneInto method.
Pass a new scope, if appropriate; for example, if you are cloning an
embedded part, its frame is the new scope.
For each object to which your part has a weak persistent reference,
call your draft's WeakClone method, passing the appropriate draft
key and scope.
(Calling WeakClone does not by itself cause an object to be copied;
it only ensures that, if the object ends up being copied because of
an existing strong persistent reference to it, your part's weak
persistent reference will be maintained across the cloning
operation.)
ΓòÉΓòÉΓòÉ 10.1.3.4. ClonePartInfo Method of your Part Editor ΓòÉΓòÉΓòÉ
Whenever your part's display frame is cloned during data transfer, the frame
calls your part's ClonePartInfo method:
void ClonePartInfo(in ODDraftKey key,
in ODType partInfo,
in ODStorageUnitView,
in ODFrame scope);
You should respond to this method call by writing your part info into the
provided storage unit view (regardless of the state of your part-info dirty
flag), and cloning any objects referenced in your part info data.
ΓòÉΓòÉΓòÉ 10.1.4. Promises ΓòÉΓòÉΓòÉ
Clipboard transfer, drag and drop, and linking can make use of promises. A
promise is a specification of data to be transferred at a future time. If a
data transfer involves a very large amount of data, the source part can choose
to write a promise instead of actually writing the data to a storage unit. When
another part retrieves the data, the source part must then fulfill the promise
it wrote. The destination part does not even know that a promise is being
fulfilled; it simply accepts the data as usual.
The format of a promise is completely determined by the source part. The only
restriction is that the promise must be able to be written to a storage-unit
value.
You are encouraged to write promises in place of actual data in most cases; it
minimizes memory requirements and increases performance.
ΓòÉΓòÉΓòÉ 10.1.4.1. Writing a Promise ΓòÉΓòÉΓòÉ
Your part can follow these steps to write a promise when it is the source of a
data transfer-that is, when it responds to a mouse-down event that initiates a
drag or when it copies data to the clipboard:
1. Gain access to the storage unit of the data-transfer object. See
Initiating a Drag for drag and drop; see, for example, Writing Intrinsic
Content for clipboard transfer. Focus the storage unit to the contents
property (kODPropContents).
2. Prepare your promise. It can have any content and format you decide; you
must be able to read it later and reconstruct the exact data that is to
be transferred.
3. Write the promise into a single value of the storage unit, using the
storage unit's SetPromiseValue method. Your promise must include at least
this information:
A specification of the actual content that is to be delivered later.
A specification of the display frame or frames of your part involved
in the data transfer (if your part can have more than one display
frame). When you fulfill the promise, you can then supply the proper
scope for the cloning operation.
A specification of the proper kind of cloning transaction (such as
kODCloneCopy or kODCloneCut) to apply when you fulfill the promise.
4. Initiate the drag or complete the transfer of information to the
clipboard. You need not, at this stage, clone any frames or create a link
specification or read the update ID; you handle all those issues when you
fulfill the promise. You should, however, write a name and a suggested
frame shape if you are promising intrinsic content.
Each promise you write is for a single part kind. You can write several
promises representing data of several kinds, so that the destination part has
a better chance of being able to incorporate the data instead of embedding it.
(Because promises are private data, the actual content of all your promises
can be the same, regardless of the part kind being promised. When you are
called to fulfill the promise, you can inspect the provided storage-unit view
object to find out which part kind is needed.)
Because a promise is valid only for the duration of the current instantiation
of your part, the promise can contain pointers to information that you
maintain.
Note: Never write a link specification into a promise. Promise data is
private and any link specification within it would never be seen by a
destination part. Likewise, never write a promise into a link
specification. Link specifications are described in Link Specification.
If your part is closing, your ReleaseAll method needs to fulfill any promises
that you have written but not yet fulfilled. See The ReleaseAll Method.
ΓòÉΓòÉΓòÉ 10.1.4.2. Getting Data from a Value Containing a Promise ΓòÉΓòÉΓòÉ
When the Drop method of a destination part retrieves the data from a drop, or
when a destination part reads data from the clipboard (using the GetValue or
GetSize methods of the dragged data's or clipboard's storage unit), the source
part is called to fulfill the promise. The destination part does not even know
that a promise is being fulfilled; it follows the procedures described in
Reading from a Data-Transfer Object and uses the same code whether the value
contains a promise or not.
ΓòÉΓòÉΓòÉ 10.1.4.3. Fulfilling a Promise ΓòÉΓòÉΓòÉ
OpenDoc calls your source part's FulfillPromise method when a promise must be
fulfilled, passing it a storage-unit view object that contains the data of the
promise. This is its interface:
void FulfillPromise(in ODStorageUnitView promiseSUView);
In your implementation of the method, take steps similar to the following:
1. Using the private information that you wrote into the promise earlier,
retrieve the actual data that the promise represents. You can clone
frames and other objects as usual at this time.
2. Set up the actual data as a byte array, and write it into the
storage-unit view object.
When your FulfillPromise method completes, the destination part receives the
data.
When you fulfill a promise, you must be sure to supply the source content that
was selected at the time the promise was written, even if that content no
longer exists in your part.
If fulfilling your promise requires cloning, you must specify the scope and
the appropriate kind of cloning transaction. If you have saved that
information in the promise itself, extract it and pass it to your draft's
BeginClone and Clone methods, respectively.
There are times when you may have to fulfill a promise on your own, without a
call to your part's FulfillPromise method. For example, when your part closes,
your part's ReleaseAll method (see Closing your Part) must fulfill all
outstanding promises. To fulfill a promise in that manner, your part needs to
have kept a record of the promises (and their update IDs) that it has written.
Then, in ReleaseAll, it can:
Access the clipboard content storage unit and verify the clipboard update
ID against the part's stored update ID
Access each value that it has written and verify that it is a promise by
calling the storage unit's IsPromiseValue method
Extract the promise from the value, fulfill it, and write the fulfilled
data back into the value.
ΓòÉΓòÉΓòÉ 10.1.5. Translation ΓòÉΓòÉΓòÉ
The OpenDoc translation object, implemented on each platform as a subclass of
ODTranslation, is a wrapper for platform-specific translation services. OpenDoc
and part editors can use the translation object to convert part data from one
format (part kind) to another.
Through the translation object, OpenDoc maintains information on what kinds of
translations are available on the user's system. OpenDoc and part editors can
then use the translation object to perform any requested translations, rather
than directly calling the platform-specific translation service itself.
In all cases, translation should occur only with explicit user approval and
instruction. OpenDoc initiates translation in the situations described in
Binding with Translation. Your part editor can initiate translation in the
following situations:
When embedding or incorporating parts through clipboard paste or through
drag and drop, your part editor performs translation if the user
specifies-in the Paste As dialog box-a part kind that requires
translation. See Handling the Paste As Dialog Box for more information.
Your part editor performs translation when updating a link destination
for which the user specified translation in the Paste As dialog box when
originally creating the link.
The user specifies the specific translation to perform by selecting a new part
kind for the part in the kind pop-up menu of the Paste As dialog box. The kind
pop-up menu allows the user to select a part kind to translate to.
To set up the information in the pop-up menu, OpenDoc examines each part kind
in the part and determines from the translation object what new part kinds
(supported by available editors) the original part kind can be translated to.
OpenDoc then presents those choices to the user in the kind pop-up menu.
Once the user picks a translated part kind, your part editor calls the
translation object to perform the translation. Note that it is possible during
data transfer for the user to request translation to a part kind that your
part editor cannot read, meaning that you will perform the translation but
another editor will be bound to the data that you have translated.
The translation object allows only one-step translation; conversion can be
only from the existing part kind directly to the part kind selected by the
user. Note also that translation applies only to the outermost (intrinsic)
portion of the data; parts embedded within it are not translated.
For detailed procedures to follow in translating transferred data, see
Translating Before Incorporating or Embedding.
ΓòÉΓòÉΓòÉ 10.1.6. Handling Cut Data ΓòÉΓòÉΓòÉ
If the user selects the Cut command to remove data from your part and place it
on the clipboard, or if the user employs drag and drop to move (rather than
copy) data from your part into another part, there are certain steps you should
take to account for the fact that the data is still valid but is no longer in
your part. Keep these points in mind:
Cutting must be an undoable action. If the data you cut from your part
includes references to objects such as embedded frames or link objects,
you should retain those references in an undo action (see Adding an
Action to the Undo History, but your part's content should release its
references to the objects. Once the objects are no longer needed for undo
support, OpenDoc calls your DisposeActionState method and you should
release them entirely. Do not, however, remove them from your draft;
other, valid references to them may remain. See also Undo for Clipboard
and Undo for Drag and Drop for related information.
Remove the facets of the cut frame. To access the facets of a frame that
has been drag-moved out of your part, don't use the moved frame's facet
iterator; the frame may have other facets in its new destination. Use the
embedded-facet iterator of your own display facet instead.
If you have more than one frame displaying an embedded part, remember
that removing the embedded part from one of your display frames does not
automatically remove it from the others. If the removed frame is
synchronized with embedded frames in your other display frames, for
example, you must remove those embedded frames also.
Do not create a link specification for the data you have cut; the data no
longer belongs to your part.
If the data you cut to the data-transfer object includes a link source,
you must call the SetSourcePart method of the link source object in your
own draft, passing it a null value, in order to relinquish your part's
ownership of the link source. Even if you write a promise instead of
actual data, call SetSourcePart at this time; don't wait until the
promise must be fulfilled.
When you cut an embedded frame to a data-transfer object, you must call
the SetContainingFrame method of the frame you have cut, setting its
containing frame to null and severing it from your part's embedding
hierarchy. You must also delete any facets displaying that frame. First
call the containing facet's RemoveFacet method and then delete the facet
object itself, as described in Removing a Facet.
If you write promises to the clipboard when cutting data from your part,
you can later fulfill that promise using either a kODCloneCut or
kODCloneCopy transaction, because the same promise may need to be
fulfilled during a paste immediately following the cut, or to subsequent
pastes that do not follow the cut. Therefore, you must take all
cut-specific actions at the time you write the promise (or, if the cut is
undone, when you handle the undo action.)
If you have cut data from your part and cloned it to the clipboard or
drag and drop object, OpenDoc may use the actual objects that were cut
from your part-not clones of them-when providing the data to be pasted or
dropped into a destination. Therefore, it is important to release, rather
than delete, objects (embedded frames, links, and so on) that you cut
from your part. Likewise, if you paste data into your part and then the
user selects Undo, make sure that you release rather than delete the
objects that you remove in the course of reversing the paste operation.
If the cut data includes a reference to a link-source object, your part
can retain that reference rather than releasing the object, but it should
not reassume ownership of the link source (through SetSourcePart) unless
and until the operation is undone.
ΓòÉΓòÉΓòÉ 10.1.7. Handling Pasted or Dropped Data ΓòÉΓòÉΓòÉ
When data is pasted or dropped into a part, the part receiving the data can
either embed the data as a separate or incorporate the data as intrinsic
content. The part may also, in some circumstances, translate the data, or it
might even refuse to accept the pasted data.
This section discusses how OpenDoc and your part editor make these decisions,
both with and without explicit user intervention. It also discusses when your
part editor might explicitly translate data.
ΓòÉΓòÉΓòÉ 10.1.7.1. Default Conventions ΓòÉΓòÉΓòÉ
In the absence of other instructions, OpenDoc expects your part to follow these
specific conventions when pasting data from the clipboard or when accepting
dropped data during a drag and drop operation.
If the transferred data consists of an arbitrary portion of the source
part's intrinsic content-plus possibly one or more embedded parts-the
destination part either incorporates or embeds that outer intrinsic
content, according to these rules:
- If any of the representations of the outer data are of a part kind
directly readable by the destination part editor, the editor should
incorporate the outer data into the intrinsic content of the
destination part, and embed any parts that were embedded in the
outer data.
- If none of the representations of the outer data is readable by the
destination part editor, the editor should create a new part, insert
all of the transferred data (including embedded parts) into the new
part, and embed the new part in a frame in the destination.
If the transferred data represents a single embedded frame that was cut
or copied (or dragged), the destination part editor should embed the data
as a separate part into the destination part, regardless of whether its
part kind is different from or identical to that of the destination.
The destination part should place clipboard data at the insertion point; it
should place dropped data at the point where the user releases the mouse
button. Note also that a pasted or dropped part takes on the view type that
its containing part prefers it to have.
ΓòÉΓòÉΓòÉ 10.1.7.2. Handling the Paste As Dialog Box ΓòÉΓòÉΓòÉ
The Paste As dialog box allows the user to override OpenDoc and specify whether
transferred data is to be embedded, incorporated, or translated and then
embedded or incorporated. It also allows the user to create a link to the
source of the transferred data.
When the user chooses the Paste as command in the Edit menu (see Edit Menu),
the active part calls the clipboard's ShowPasteAsDialog method to display the
Paste As dialog box, shown in the following figure. Also, when the user presses
Alt in a drag and drop operation, the active part calls the ODDragAndDrop
ShowPasteAsDialog method.
A part at the destination of a drop also displays this dialog box if the user
holds down the Command key while performing a drop; see Dropping.
The user can select the following options from the dialog box:
Paste with link. The user can request that a link be created between the
source and destination data. This option is not available to the user
unless the source document is open. Disable this option if your part does
not support linking.
Get Updates (of a link) Automatically/Manually. If the user creates a
link, updates can be either automatic or manual; this choice sets which
method the user wants. See Automatic and Manual Propagation of Updates
for more information.
Merge with contents. If the user chooses this option, the destination
part editor is expected to incorporate the data, if at all possible, even
if it requires translation and even if it means great loss of information
(such as converting text to a bitmap).
When you invoke the dialog box, you can check the part kinds available in
the data-transfer object and specify whether this option is enabled and
whether it is checked by default. Disable this option if your part cannot
incorporate the data, even after translation.
Embed As. If the user chooses this option, the source data must be
embedded in the destination as a separate part, even if incorporation is
possible.
When you invoke the dialog box, you can check the part kinds available in
the data-transfer object and specify whether this option is enabled and
whether it is checked by default. Disable this option if your part does
not support embedding.
With Embed As, the user can make the following selections:
- Kind (of pasted data). With this option, the user can override
OpenDoc's default pasting decision and explicitly specify a
destination part kind from a pop-up menu. The user specifies the
specific translation to perform by selecting a new part kind for the
part in the kind pop-up menu. of the Paste As dialog box. The kind
pop-up menu allows the user to select a part kind to translate to.
This option (choosing part kind) is not available if the transferred
data consists of a single frame that is being moved (not copied)
from its original source. The moved frame may not be the only frame
displaying its part, and changing its kind would then affect the
display in the other parts, which may not be what the user intended.
- View type. From the Embed As pop-up menu, the user can choose the
view type the embedded part is to have: frame, large icon, small
icon, or thumbnail.
The ShowPasteAsDialog method returns the results of the user's choices to your
part in an ODPasteAsResult structure:
struct ODPasteAsResult
{
ODBoolean pasteLinkSetting;
ODBoolean autoUpdateSetting;
ODBoolean mergeSetting;
ODTypeToken selectedView;
ODType selectedKind;
ODType translateKind;
ODEditor editor;
};
Based on the contents of the returned structure, your part either embeds or
incorporates the transferred data, either accepts it as it is or translates
it, and either creates a link to its source or does not.
If the user has chosen to create a link, respond as described in Creating
a Link at the Destination. If you create a link, take into account the
automatic/manual update setting, as well as the other Paste As settings
chosen by the user.
If the user has chosen to translate the data but is not creating a link,
respond as described in Translating Before Incorporating or Embedding. If
the user is not creating a link or translating, read the transferred data
in either of two ways:
- If your part can directly read any of the part kinds in the
data-transfer object's contents storage unit, and if the user has
not selected "Embed As", you incorporate the data. Follow the
instructions in Incorporating Intrinsic Content.
- If your part cannot directly read any of the part kinds in the
storage unit, or if the user has selected "Embed As", follow the
instructions in Embedding a Single Part.
For more on translation, see Binding with Translation.
ΓòÉΓòÉΓòÉ 10.2. Writing to a Data-Transfer Object ΓòÉΓòÉΓòÉ
You write data to a data-transfer object when the user cuts, copies, or drags
data from your part or when you create or update the source of a link. This
section discusses how to place that data into the data-transfer object.
This section does not discuss how your part handles cutting (or moving)
differently from copying. Cutting involves removing data from your part,
including possibly one or more frames of embedded parts. Removing an embedded
part is discussed in Removing an Embedded Part; other issues related to cutting
operations are discussed in Handling Cut Data.
ΓòÉΓòÉΓòÉ 10.2.1. Writing Intrinsic Content ΓòÉΓòÉΓòÉ
Intrinsic content plus possibly one or more embedded parts is the most general
configuration of data that you put into a data-transfer object. If the data to
be written consists of a combination of your part's intrinsic content plus
embedded parts, you need to write your own intrinsic content, and you need to
clone the embedded frames as well. These are the basic steps to take,
regardless of whether or not your intrinsic content is accompanied by embedded
frames:
1. Gain access to the data-transfer object and prepare to write to it. (See,
for example, the initial steps under Copying or Cutting to the Clipboard,
Initiating a Drag, Creating a Link at the Source, and Updating a Link at
the Source.)
2. Start the cloning operation, as described under Cloning. Specify the
appropriate kind of cloning operation, using one of the constants listed
in the table shown in Cloning Process.
3. Clone the embedded part into the data-transfer object by calling your
draft's Clone method. Unlike writing intrinsic content, you do not add a
kODPropContents property (the embedded part itself does that), a
kODPropName property (OpenDoc does that), or a kODPropSuggestedFrame
property (you instead add a kODPropContentFrame property).
Be sure to perform the cloning operation in this order:
a. Add a property of type kODPropContentFrame to the data-transfer
object's content storage unit. The presence of this property tells a
destination part that the data being transferred is a frame without
surrounding intrinsic content, and also signals to the embedded part
(the part being cloned) that it can write a promise instead of its
actual content.
It is important not to clone the frame yet. Wait until step 4, or
else the embedded part itself will be cloned into the wrong storage
unit.
b. Clone the embedded part into the data-transfer object's content
storage unit.
c. Clone the embedded part's frame into the data-transfer object's
draft (into any storage unit other than the content storage unit).
This cloning operation must occur after the embedded part is cloned.
d. Add a value of type kODWeakStorageUnitRef to the kODPropContentFrame
property of the data-transfer object's content storage unit. Create
a weak persistent reference from that value to the cloned frame.
This allows a destination part, upon recognizing the
kODPropContentFrame property, to locate the frame for the part in
the data-transfer object.
4. Optionally, write any intrinsic data you want associated with the frame
(such as a drop shadow or other visual adornment) as proxy content. Add a
property (of type kODPropProxyContents) to the data-transfer object, and
write your data into it as a value that you recognize. If the transferred
part is subsequently pasted into a part that also recognizes that value
and knows how to interpret it, the added frame characteristics can be
duplicated.
5. If the embedded part is the entire source or destination of a link, you
need to write additional proxy content, as described in Writing Linked
Content to Storage. follow the instructions in the note Writing Links for
Data Transfer.
6. If appropriate, write a link specification into the data-transfer object,
as described in Link Specification.
7. Perform any closing tasks specific to the individual kind of
data-transfer object you are writing to. (See, for example, the final
steps under Copying or Cutting to the Clipboard, Initiating a Drag,
Creating a Link at the Source, and Updating a Link at the Source.)
If this operation is a cut rather than a copy, note the additional
considerations listed in Handling Cut Data.
ΓòÉΓòÉΓòÉ 10.3. Reading from a Data-Transfer Object ΓòÉΓòÉΓòÉ
You read from a data-transfer object when the user pastes or drops data into
your part or when you create or update the destination of a link. This section
discusses how to extract that data from the data-transfer object.
When placing transferred data into your part, what you do with the data depends
on how the part kinds within the transferred data relate to the part kind of
your part (the destination part).
ΓòÉΓòÉΓòÉ 10.3.1. Incorporating Intrinsic Content ΓòÉΓòÉΓòÉ
As the destination of a data transfer, your part incorporates the intrinsic
content of the data-transfer object into your own part's intrinsic content (and
embeds whatever embedded parts the transferred content contains) if all of
these conditions apply:
The intrinsic content of the transferred data is stored in at least one
part kind that you can incorporate into your part.
The user has not specified Embed As in the Paste As dialog box.
The data-transfer object's content storage unit does not contain a
property named kODPropContentFrame. (If it does, the transferred data
consists of a single embedded frame without surrounding intrinsic
content, and must be embedded; see Frame Shape or Frame Annotation.)
When incorporating transferred data, you should generally read the
highest-fidelity part kind that your part editor understands. If the
transferred data includes a kODPropPreferredKind property, you should consider
that kind to be the highest-fidelity kind; otherwise, consider the first value
in the contents property to be the highest-fidelity kind.
Incorporating involves reading intrinsic content plus possibly cloning
embedded frames, links, and other objects. The following figure summarizes the
steps involved.
Here, in more detail, are the basic steps to take when incorporating:
1. Gain access to the data-transfer object and prepare to read from it.
(See, for example, the initial steps under Pasting from the Clipboard,
Dropping, Creating a Link at the Destination, and Updating a Link at the
Destination.) If you are incorporating translated data, you already will
have cloned the data into a temporary storage unit in your draft, and you
already will have translated it. Take these steps:
In the kODPropContents property of that storage unit, focus on the
value that corresponds to the translated part kind. Read the data
into your part, following your own content model.
Skip to step 5.
2. Start the cloning operation, as described under Cloning. Specify the
appropriate kind of cloning operation, using one of the constants listed
in the table shown in Cloning Process.
3. In the kODPropContents property of the data-transfer object's content
storage unit, focus on the value that corresponds to the highest-fidelity
part kind that you can incorporate into your part. (Note that this may
not be the highest-fidelity value present in the property.) Read the data
into your part, following your own content model.
As you encounter persistent references to objects (embedded frames,
link-source objects, link objects, auxiliary storage units, and so on),
clone each object into your draft, by calling the Clone method of the
data-transfer object's draft. Adjust your persistent references to point
to the newly cloned objects.
4. End the cloning operation.
5. If you are incorporating as a result of a drop, there may be a
kODPropMouseDownOffset property in the data-transfer object's content
storage unit. If so, focus on that property, read its value, and-if
appropriate for your content model-use the value to position the
incorporated data in relation to the drop location.
(If you are incorporating translated data, you must read this property
from the original data-transfer object, not the cloned storage unit.)
6. If the cloning was successful, instantiate each cloned embedded frame
into memory (with your draft's AcquireFrame method) and call the frame's
SetContainingFrame method, to make your part's display frame (the frame
that received the paste) the containing frame of the new embedded frame.
Create additional embedded frames as necessary, if your part's content
model specifies that the new part is to appear in more than one of your
display frames. If you do create new frames, synchronize them with the
first (source) frame; see Synchronizing Display Frames for more
information.
7. Change each new frame's link status to reflect its current location. If
your part does not support linking, you must nevertheless change your
cloned embedded frames' link status (to kODNotInLink).
8. If any of the objects that you have cloned into your part is a
link-source object or link object, follow the procedures described in
Reading Linked Content from Storage to make sure that the objects are
valid.
9. If any newly embedded frame is visible, assign a facet or facets to it,
as described in Adding a Facet.
10. Notify OpenDoc and your containing part that there has been a change to
your part's content; see Making Content Changes Known.
11. Perform any closing tasks specific to the individual kind of
data-transfer object you are reading from. (See, for example, the final
steps under Pasting from the Clipboard, Dropping, Creating a Link at the
Destination, and Updating a Link at the Destination.) (If you are
incorporating translated data, you can at this time remove the cloned
temporary storage unit from your draft.)
ΓòÉΓòÉΓòÉ 10.3.2. Embedding a Single Part ΓòÉΓòÉΓòÉ
As the destination of a data transfer, your part embeds the entire contents of
the data-transfer object as a single part (plus whatever embedded parts it
contains) if any of these conditions apply:
The intrinsic content is of a part kind that you cannot incorporate.
The user has specified Embed As in the Past As dialog box.
The transferred data consists of a single embedded frame, without
surrounding intrinsic content (regardless of its part kind). In this case
the data-transfer object's content storage unit contains a property named
kODPropContentFrame, that is a signal that the data consists of a single
frame and must be embedded, even if it is of a part kind that you can
incorporate. See Frame Shape or Frame Annotation.
Embedding data from a data-transfer object as a single part involves,
basically, cloning the content storage unit into your draft and then providing
for a frame and facets for the new part. The steps involved are summarized in
the following figure.
Here, in more detail, are the basic steps to take when embedding:
1. Gain access to the data-transfer object and prepare to read from it.
(See, for example, the initial steps under Pasting from the Clipboard,
Dropping, Creating a Link at the Destination, and Updating a Link at the
Destination.)
If you are embedding translated data, you already will have cloned the
data into a new storage unit in your draft, and you already will have
translated it. Skip to step 6.
2. Start the cloning operation, as described under Cloning. Specify the
appropriate kind of cloning operation, using one of the constants listed
in the table shown in Cloning Process.
3. Clone the data-transfer object's content storage unit into a new storage
unit in your draft, using the Clone method of the data-transfer object's
draft.
4. If there is a property named kODPropContentFrame in the cloned storage
unit, read the storage-unit reference it contains and use that reference
to clone the new part's frame into your draft. (Cloning the data-transfer
object's content storage unit alone does not copy the frame, because the
reference is a weak persistent reference.)
5. End the cloning operation.
6. If the data has been translated, or if this embedding occurs as a result
of the Paste As dialog box and the user has chosen a part kind that is
not the preferred kind, or if a preferred kind property does not exist
and the user has chosen a part kind that is not the highest-fidelity
(=first) value stored in the transferred storage unit's contents
property, you need to notify the future part editor of that part that a
different part kind should be used. Create a property with the name
kODPropPreferredKind in the cloned storage unit (if the property does not
already exist) and write into it a value that specifies the part kind the
editor should use.
7. If this embedding occurs as a result of the Paste As dialog box and the
user has chosen a specific part editor to edit the part, add a property
with the name kODPropPreferredEditor to the cloned storage unit, and
write into it the editor ID (returned in the editor field of the
ODPasteAsResult structure) of the preferred editor.
8. If the cloned storage unit contains a property named kODPropProxyContent,
that property contains any proxy content that the part's original
containing part wanted associated with the frame, such as a drop shadow
or other visual adornment. (Note that this property is absent if the
transferred data includes any intrinsic content in addition to the
embedded frame.)
Focus the cloned storage unit on the kODPropProxyContent property and
read in the information from the data-transfer object (not from the
cloned storage unit). Note that you must understand the format of the
proxy content in order to use it, and it becomes part of your own part's
intrinsic content to be associated with the frame. If you do not
understand the format, ignore the data.
9. If you are embedding as a result of a drop, there may be a property named
kODPropMouseDownOffset in the content storage unit of the data-transfer
object (not the cloned storage unit). If so, focus on that property, read
its value, and-if appropriate for your content model-use the value to
position the embedded data in relation to the drop location.
10. Recreate the new part's frame-if it has been provided in the
kODPropContentFrame property-using your draft's AcquireFrame method, and
call its SetContainingFrame method to assign your part's display frame
(the frame that received the paste) as the containing frame. If no frame
was provided, you need to create one:
Obtain the suggested frame shape-if it exists-from the data-transfer
object (not from the cloned storage unit). It is in a property named
kODPropSuggestedFrameShape. If it is not there, use a default frame
shape.
Recreate the new part; pass the cloned storage unit's ID to your
draft's AcquirePart method.
Create the frame for the part, using your draft's CreateFrame
method.
11. Change the link status of the new frame to reflect its current location.
If your part does not support linking, you must nevertheless change your
new embedded frame's link status (to kODNotInLink).
12. If the newly embedded frame is visible, assign facets to it, as described
in Adding a Facet.
13. Notify OpenDoc and your containing part that there has been a change to
your part's content; see Making Content Changes Known.
14. Perform any closing tasks specific to the individual kind of
data-transfer object you are reading from. (See, for example, the final
steps under Pasting from the Clipboard, Dropping, Creating a Link at the
Destination, and Updating a Link at the Destination.)
ΓòÉΓòÉΓòÉ 10.3.3. Translating Before Incorporating or Embedding ΓòÉΓòÉΓòÉ
If you must translate transferred data before incorporating or embedding it in
your part, follow the steps listed here before following those in Incorporating
Intrinsic Content and Embedding a Single Part. The OpenDoc translation service
is described in general in Translation. Basically, you translate by first
cloning the data into a storage unit in your own draft, then modifying it, and
then completing the data transfer. When you perform translation on transferred
data, you always write the translated data into a storage unit in your own
draft; do not write it back into the original data-transfer object. Subsequent
readers of the data cannot tell a translated value from a value written by the
data's original part editor.
These are the general steps you might follow:
1. Gain access to the data-transfer object that contains the untranslated
data and prepare to read from it. (See, for example, the initial steps
under Pasting from the Clipboard, Dropping, and Updating a Link at the
Source.
(If you are creating a link, remember that you read the data from the
newly created link object, not the clipboard or drag and drop that
contained the link specification.)
2. Start the cloning operation, as described under Cloning. Specify the
appropriate kind of cloning transaction, using one of the constants
listed in the table in Cloning Process.
3. Clone the data-transfer object's content storage unit into a new storage
unit in your draft, using the Clone method of the data-transfer object's
draft.
4. If there is a property named kODPropContentFrame in the cloned storage
unit, you must embed the data. Read the storage-unit reference the
property contains and use that reference to clone the new part's frame
from the data-transfer object's storage unit into your draft. (Cloning
the data-transfer object's content storage unit alone does not copy the
frame, because the reference is a weak persistent reference.)
5. Access the storage unit of the cloned-but-untranslated data, and focus on
its contents property. Add a value whose value type is the part kind you
are translating to.
6. Create two storage-unit views: one focused on the original untranslated
value, and the other focused on the translated value you have just
created.
7. Gain access to the translation object (by calling the session object's
GetTranslation method), and then call the translation object's
TranslateView method, passing it the two storage-unit views. The
translation object performs the translation and writes the new data into
the new value.
8. If the translation is successful, delete the storage-unit views.
Complete the transfer of the translated data in either of two ways, depending
on whether you are incorporating or embedding, or whether you are creating or
updating a link:
If your part can directly read the part kind of the translated data (and
if the user has not selected "Embed As" from the Paste As dialog box),
incorporate it. Follow the steps in the section Incorporating Intrinsic
Content.
The following figure summarizes the steps involved. Note that you read
the content into your part from the cloned temporary storage unit, not
the original data-transfer object, and that you remove the temporary
storage from your draft when the transfer is complete.
If your part cannot directly read the part kind of the translated data
(or if the user has selected "Embed As" from the Paste As dialog box),
follow the instructions in Embedding a Single Part.
The following figure summarizes the steps involved. Note that the cloned
storage unit is not temporary, but becomes the new embedded part's
storage unit. Note also that you must read proxy content from the
original data-transfer storage unit, not the cloned one.
ΓòÉΓòÉΓòÉ 10.4. Clipboard Transfer ΓòÉΓòÉΓòÉ
The clipboard commands Cut, Copy, and Paste constitute a common mechanism for
transferring data within and across conventional documents, even documents of
different data formats. For OpenDoc documents, these commands perform the same
tasks, but with added capabilities:
The commands operate on embedded parts as well as intrinsic content. The
data copied to or pasted from the clipboard can contain any number of
parts, of any part kinds. The parts may be displayed in frames or
represented as icons.
The Paste and Paste As commands can either embed parts or incorporate
data as intrinsic content.
The Paste and Paste As commands can create a new part when it embeds
intrinsic content of one part kind into a part of a different part kind.
The Paste As command can create a persistent link to the source of the
data being pasted.
The Paste As command also allows the user to override decisions on
incorporating vs. embedding that would normally be made by OpenDoc.
As with data placed in a conventional clipboard, if your part editor stores
multiple formats on the clipboard-including standard formats-the user has a
greater chance of being able to incorporate (rather than embed) the data into
more kinds of parts.
ΓòÉΓòÉΓòÉ 10.4.1. Clipboard Concepts ΓòÉΓòÉΓòÉ
This section discusses some basic clipboard operations common to both reading
and writing, such as acquiring the clipboard focus and using the clipboard
update ID. It also presents some special considerations related to updating the
clipboard and cutting data from your part.
Enabling the Cut and Paste menu items
OpenDoc does not permit changes to your draft (and therefore your part's
content) if the draft permissions are read-only. You should check your draft
permissions (see Drafts) before enabling the Cut, Paste, or Paste As items in
the Edit menu and before allowing the user to perform a cut or paste operation.
The OS/2 implementation of the clipboard object provides two extra methods to
help parts in determining whether or not they can enable the Paste menu items;
they are called CanEmbed and CanIncorporate. CanEmbed queries the registration
manager for a part editor capable of handling the content of the clipboard
storage unit. It is intended for use by container parts. CanIncorporate
determines if a specific type of content is present on the clipboard. Parts
should call this method for the kinds they support.
ΓòÉΓòÉΓòÉ 10.4.1.1. Acquiring and Relinquishing the Clipboard Focus ΓòÉΓòÉΓòÉ
Your part can access the clipboard only when your part is in the foreground
process; access from a background process is meaningless. Furthermore, to be
thread-safe, you must always acquire the clipboard focus before writing or
reading clipboard data. As long as you hold the clipboard focus, no other part
can access or modify the data.
Typically, your part needs to inspect the contents of the clipboard before
deciding whether to enable Edit menu items, so your AdjustMenus method can
include a call to the arbitrator's RequestFocus method to acquire the clipboard
focus.
After acquiring the clipboard focus, you can access the clipboard when
responding to a menu command (or its keyboard equivalent) with your HandleEvent
method by calling the session object's GetClipboard method. You should then
unilaterally relinquish the clipboard focus in your HandleEvent method, after
having handled the clipboard command.
These are the steps you take to acquire the clipboard focus and prepare to
write to it or read from it:
1. Acquire the clipboard focus, using the Arbitrator's RequestFocus method
(as described in Requesting Foci).
2. Get access to the clipboard object, using the session object's
GetClipboard method.
3. Acquire the clipboard focus, using the Arbitrator's RequestFocus method
(as described in Requesting Foci).
4. If you are writing to the clipboard, remove any existing data on the
clipboard with the clipboard's Clear method. (Do not take this step if
you are reading from the clipboard.)
5. Get access to the clipboard's content storage unit, using the clipboard's
GetContentStorageUnit method.
You relinquish the Clipboard focus by calling the arbitrator's RelinquishFocus
method. If you receive a deactivate event because your window is being
deactivated and your frame holds the Clipboard focus, your HandleEvent method
should call RelinquishFocus. Also, during the normal process of negotiated
focus transfer (see Relinquishing Foci), your part should also be prepared to
temporarily relinquish the clipboard focus, so that inactive parts can inspect
the clipboard when necessary.
ΓòÉΓòÉΓòÉ 10.4.1.2. Clipboard Update ID ΓòÉΓòÉΓòÉ
Whenever you copy data to the clipboard, you should get and save the
clipboard's current update ID, a number used to identify this particular
instance of clipboard contents. You obtain the update ID by calling the
clipboard's GetUpdateID method.
If a link specification that you have written to the clipboard becomes invalid
because of changes to your content that was copied to the clipboard, you must
remove the link specification from the clipboard, as described in Removing a
Link Specification from the Clipboard. You can examine the clipboard's current
update ID at any time and compare it with your saved update ID; if they are
identical, the clipboard has not changed.
ΓòÉΓòÉΓòÉ 10.4.1.3. Removing a Link Specification from the Clipboard ΓòÉΓòÉΓòÉ
If your part copies some of its content to the clipboard and the user then
modifies the equivalent content in your part (without copying anything else to
the clipboard), the clipboard data no longer exactly matches the source data in
your part. The potential link represented by the link specification you wrote
in the clipboard therefore no longer reflects the content at the source of the
link. Small changes may not matter, but if your source content has changed to
the extent that creating a link is no longer feasible, your part must remove
the link specification.
By saving the update ID of the clipboard whenever you copy data to it, you can
check that ID against the current update ID of the clipboard whenever your
source data changes. If the IDs match, the clipboard still contains the data
that you placed in it, and you should remove the link specification.
You can follow these steps to remove a link specification:
1. Acquire the clipboard focus as described in Acquiring and Relinquishing
the Clipboard Focus.
2. Get the clipboard's update ID and compare it to your stored update ID. If
they do not match, skip to step 5.
3. Access the clipboard's content storage unit. (Unlike when writing to the
clipboard, do not clear the clipboard data.)
4. Focus the storage unit on the link-specification property (type
kODLinkSpec), and remove that property, using the storage unit's Remove
method.
5. Call the clipboard object's ExportClipboard method (OS/2 implementation
only).
6. Relinquish the clipboard focus.
ΓòÉΓòÉΓòÉ 10.4.1.4. Undo for Clipboard ΓòÉΓòÉΓòÉ
If your part supports cutting, copying, or pasting, it must also support
undoing those operations. (Note that data transfer between parts is undoable
only if both parts involved have undo support.) Undo support in general is
described in Undo.
Whenever your part performs a cut, copy, or paste operation, it should call the
clipboard's ActionDone method, to notify the clipboard of the kind of cloning
transaction that was used. When your part cuts an object to the clipboard, it
should relinquish ownership of the object (as in the sense of ownership of a
link source) but not release the object, instead saving a reference to the
object in an undo action. Then, if the user chooses the Undo command and your
part's UndoAction method is called, your part should:
Reassume ownership of the object, reinstating the object's relationship
to the part's content
Notify the clipboard that the cut was undone, by calling the clipboard's
ActionUndone method, passing it the update ID you saved when you wrote
the data to the clipboard If the user subsequently chooses the Redo
command and your part's RedoAction method is called, your part should
Once again relinquish ownership of the object
Notify the clipboard that the cut was redone, by calling the clipboard's
ActionRedone method, passing it the same update ID
Calling ActionDone, ActionUndone, and ActionRedone allows the clipboard to
know whether it is involved in a cut or a copy or the restoration of a cut or
a copy; the clipboard's internal handling of its objects differs in each case.
If and when your part's DisposeActionState method is called, you can at that
point release (not remove from your draft) the object referenced in your undo
action.
To undo a cut, copy, or paste operation requires the restoration of the
previous state of the document involved, but it does not require the
restoration of the previous state of the clipboard. Therefore, if you redo a
paste operation that has been undone, you cannot assume that the clipboard
once again contains the original data that had been pasted. You must implement
the redo from your own data, which means that you should retain a copy of
pasted data in a private cache.
ΓòÉΓòÉΓòÉ 10.4.2. Copying or Cutting to the Clipboard ΓòÉΓòÉΓòÉ
You write data to the clipboard as a result of the user selecting the Cut
command or the Copy command from the Edit menu (or their keyboard equivalents).
These are the basic steps to take:
1. Acquire the clipboard focus and access the clipboard's content storage
unit, as described in Acquiring and Relinquishing the Clipboard Focus.
2. Write the data to the clipboard:
If the selection consists of a combination of your part's intrinsic
content plus zero or more embedded parts, you need to write or
promise your own intrinsic content to the clipboard, and you need to
clone the embedded frames and parts as well. Follow the steps listed
in Writing Intrinsic Content.
If the selection consists of a single frame of an embedded part,
with no surrounding intrinsic content, you need to clone the part
and provide a frame for it.
In either case, when you call the BeginClone method, specify either
kODCloneCopy or kODCloneCut, depending on whether you are copying or
cutting the data to the clipboard.
3. When you have finished, if you have written either a link specification
or a promise to the clipboard, get the clipboard's current update ID and
save it (see Clipboard Update ID), in case you later have to remove the
link specification or fulfill the promise.
4. Call the clipboard's ActionDone method, specifying either kODCloneCopy or
kODCloneCut.
5. If this operation was a cut rather than a copy, it must be undoable. Add
a single action to the action history, as described in Adding an Action
to the Undo History. Be sure to follow the special instructions in
Handling Cut Data. Delete the cut selection from your part.
6. Call the clipboard object's ExportClipboard method (OS/2 implementation
only).
7. Relinquish the clipboard focus (see Acquiring and Relinquishing the
Clipboard Focus). Alternatively, you can wait until asked to relinquish
the focus by the next requester of the clipboard focus.
ΓòÉΓòÉΓòÉ 10.4.3. Pasting from the Clipboard ΓòÉΓòÉΓòÉ
You read data from the clipboard as a result of the user selecting the Paste
command or the Paste As command from the Edit menu. These are the basic steps
to take:
1. Acquire the clipboard focus and access the clipboard's content storage
unit, as described in Acquiring and Relinquishing the Clipboard Focus.
(Do not clear the clipboard data, of course).
2. Read the data from the clipboard:
If the data consists of intrinsic content plus zero or more embedded
frames, and if the intrinsic content is of a part kind that you can
incorporate into your part, you need to read the intrinsic content
and possibly clone embedded parts, links, or other objects. Follow
the steps listed in Incorporating Intrinsic Content.
If you need to embed the data as a single part with no surrounding
intrinsic content, you need to clone the part and either extract its
frame or create a frame for it. Follow the steps listed in Embedding
a Single Part. That section lists the conditions under which you
must embed rather than incorporate clipboard data.
In either case, when you call the BeginClone method, specify
kODClonePaste.
3. Pasting must be an undoable operation. Get the update ID of the clipboard
data and save it in a single undo action (see Adding an Action to the
Undo History, so that you can undo the paste operation if necessary.
4. Call the clipboard's ActionDone method, specifying kODClonePaste.
5. Relinquish the clipboard focus (see Acquiring and Relinquishing the
Clipboard Focus).
ΓòÉΓòÉΓòÉ 10.5. Drag and Drop ΓòÉΓòÉΓòÉ
The OpenDoc drag and drop facility allows users to apply direct manipulation to
move or copy data. Users can drag items from one location in a part or document
(or the desktop) to another location in the same or different part or document
(or to the desktop).
ΓòÉΓòÉΓòÉ 10.5.1. Drag and Drop Concepts ΓòÉΓòÉΓòÉ
Drag and drop provides a direct-manipulation alternative to the clipboard. The
fundamental user-level operations-copying and moving-are similar in drag and
drop and in clipboard transfer, and are similar across all platforms.
ΓòÉΓòÉΓòÉ 10.5.1.1. User Interaction ΓòÉΓòÉΓòÉ
The user typically initiates a drag by positioning the mouse pointer over some
selected (or single-click-selectable) content, pressing and holding down the
mouse button, and then moving the pointer. The user can press a modifier key
before pressing the mouse button, or at any time before releasing the mouse
button, to force a drag to be a copy (called a drag-copy) or a move (called a
drag-move).
As the user moves the mouse pointer, an outline of the selected item (provided
by the source part, drawn by OpenDoc) is dragged to the new location. When the
user releases the mouse button, the item is placed (dropped) at the pointer
location. The part beneath the pointer is notified that something has been
dropped on it. At this point, the destination part behaves essentially as it
does when pasting from the clipboard; it makes the same decisions regarding
embedding versus incorporating data and handling links. The source part may
have to delete the items if it was a move operation or do nothing if it was a
copy.
Drag and drop allows parts to restrict the dropped content they will accept to
specific part kinds. To indicate that it can accept a drop, a part provides
appropriate feedback to the user once the mouse pointer enters its facet.
For frames and icons that are being dragged, OpenDoc provides specific behavior
and appearance for the item being dragged. For intrinsic content that is being
dragged, your part editor is responsible for defining behavior and providing
user feedback.
The user can move an active frame by dragging its border. The user moves the
pointer to the active frame border and presses the mouse button; this causes
the frame to become selected so the user can start dragging it.
The user can move a selected frame by moving the pointer anywhere within it and
pressing the button to start a drag. Also, if a frame is bundled, the user can
drag it by pressing the mouse button while the pointer is anywhere in the
interior of the frame, whether or not the frame is already active or selected.
When the user releases the mouse button, the frame and its part are dropped at
the new location. After a frame is dropped, it becomes selected. The
destination may adjust the drop location of a frame based on constraints such
as gridding (in a graphics part) or text position (in a text part).
ΓòÉΓòÉΓòÉ 10.5.1.2. Move Versus Copy ΓòÉΓòÉΓòÉ
OpenDoc follows these conventions for drag and drop:
When a user drags an item within a document, the item is moved, not
copied. Note that a window boundary is not always a document boundary;
dragging an item to or from a part window, but still within the same
document, results in a move.
When the user drags an item across a document boundary, the item is
copied, not moved.
When the user employs drag and drop to move a frame, the moved frame
displays the original part if the destination is in the same document.
However, if the destination is in a different document, the source frame
is deleted and a new part (and new frame displaying the contents) is
created at the destination. (In contrast, clipboard pasting always
creates a new part and frame.)
OpenDoc supports a mechanism for you to allow the user to force a copy or move
operation by pressing modifier keys.
Dragging stationery
Stationery parts may be copied exactly like any other part. However, if you
drag-move stationery into a part whose preferred view type for embedded parts
is frame view, a copy is "torn off" the stationery pad and displayed in a
frame. The stationery part remains in its original location, unchanged.
ΓòÉΓòÉΓòÉ 10.5.1.3. Droppable Frames ΓòÉΓòÉΓòÉ
Each frame has a SetDroppable method, through which the part displayed in the
frame controls whether or not the frame can (in general) accept dropped data.
When a display frame is added or reconnected to your part, you can call
SetDroppable and pass either kODTrue or kODFalse to allow or disallow,
respectively, dropping. (Frames are initially created as non-droppable.) You
can call SetDroppable again at any time to change the state of the frame.
OpenDoc calls the IsDroppable method of the frame before making the
drag-related method calls DragEnter, DragWithin, DragLeave, or Drop to your
part. If your display frame is non-droppable, your part does not receive any of
these calls.
Your part's display frame does not have to be droppable for you to initiate a
drag from it.
ΓòÉΓòÉΓòÉ 10.5.1.4. Undo for Drag and Drop ΓòÉΓòÉΓòÉ
If your part supports dragging and dropping data, it must also support undoing
that operation. (Note that data transfer between parts is undoable only if both
parts involved have undo support.) Undo support in general is described in
Undo.
For drag and drop, undo involves a two-stage action. The part initiating the
drag begins the two-stage action, the part receiving the drop adds a
single-stage action, and the initiating part completes the two-stage action
when the drop completes. See Adding Two-Stage Actions for more details.
ΓòÉΓòÉΓòÉ 10.5.2. Initiating a Drag ΓòÉΓòÉΓòÉ
In response to a mouse-down event at the appropriate location, the part
receiving the mouse event (the source part) has the choice of initiating a
drag. You should follow the guidelines listed under Mouse Events, Activation,
and Dragging in deciding whether to initiate a drag operation.
To initiate a drag, you (the source part) should follow these steps:
1. Call the GetDragAndDrop method of the session object to get access to the
ODDragAndDrop object. Then call the Clear method and the
GetContentStorageUnit method of the drag and drop object to get an empty
storage unit into which to copy the dragged data.
Note that there is no drag and drop focus or lock to be acquired. Only
one part at a time can initiate and complete a drag.
2. Write or promise the dragged data into the drag and drop storage unit:
If the data is intrinsic content-with or without one or more
embedded parts-follow the procedure given in Writing Intrinsic
Content.
In either case, when you call the Begin Clone method, specify
kODCloneCopy, even if you are initiating a drag-move. (The user can
override the move-versus-copy conventions when dropping the data.)
3. Create a property with the name kODPropMouseDownOffset in the drag and
drop storage unit, and write into it a value (of type kODPoint) that
specifies the offset of the mouse from the origin of the selection or
item that is being dragged. This allows the destination part to correctly
locate the item in relation the mouse-up event when it is dropped.
4. If there are frames that should not accept a drop from this drag-such as
any frames that are themselves being dragged-call the frames' SetDragging
method and pass a value of kODTrue to notify OpenDoc that it should not
allow them to be drop targets.
5. Initiate the drag by calling the drag and drop object's StartDrag method.
When you call StartDrag, you are responsible for providing OpenDoc an
image, such as an outline, for it to display to the user as dragging
feedback.
6. Add a begin action to the undo action history (see Adding Two-Stage
Actions), to allow the user to undo the drag if necessary.
The StartDrag method completes when the drop occurs; see Completion of
StartDrag.
ΓòÉΓòÉΓòÉ 10.5.3. Operations while a Drag Is in Progress ΓòÉΓòÉΓòÉ
As long as the mouse button remains pressed, the drag is in progress. Potential
destination parts need to perform certain actions during dragging, when the
mouse pointer is within their facets.
ΓòÉΓòÉΓòÉ 10.5.3.1. On Entering a Part's Facet ΓòÉΓòÉΓòÉ
Any facet the mouse pointer passes over during a drag represents a potential
drop destination, if the facet's frame is droppable. OpenDoc calls a part's
DragEnter method when the pointer enters one of its droppable frames' facets
during a drag:
ODDragResult DragEnter (in ODDragItemIterator dragInfo,
in ODFacet facet,
in ODPoint where);
On receiving a call to its DragEnter method, your part (the potential
destination part) should take these steps:
1. Examine the part kinds of the dragged data, using a drag-item iterator
(class ODDragItemIterator) passed to it. Inspect the part kinds in each
item's storage unit and determine whether or not you can accept the
dragged data.
Note: As for the clipboard object,the OS/2 implementation of the
ODDragAndDrop object provides two extra methods to help parts in
determining whether or not they can accept the dragged data; they
are called CanEmbed and CanIncorporate.
CanEmbed queries the registration manager for a part editor capable of
handling the content of the drag item. If a match is found, the
corresponding rendering mechanism and format pair will be saved in the
drag item storage unit (under the the kODSelectedRMF value of the
kODPropContents property) to be used later for rendering in the event a
drop ocurrs in this facet. This method should be used by container parts.
CanIncorporate determines if the drag item content matches a specific
kind. Parts should call this method for the kinds they support. As for
CanEmbed, if a match is found, the corresponding rendering mechanism and
format pair will be saved in the drag item storage unit.
If your part cannot accept all the dragged items, you should not accept a
drop at all.
2. If your part can accept a drop, provide the appropriate feedback to the
user, such as adorning its frame border or changing the cursor
appearance; Otherwise, take no action.
When the user initiates a drag from within your frame, you should not
display destination feedback as long as the mouse pointer has not yet
left your frame, although you should provide feedback if the mouse
pointer returns to your frame after having left it.
You can draw the drag feedback on your own, or with the help of
platform-specific services.
3. Examine the current state of the user's system, if desired, to obtain
information such as whether or not a modifier key has been pressed since
a drag was initiated. If you return kODFalse from this method, OpenDoc
assumes you cannot accept a drop and will not subsequently call your
DragWithin or Drop methods as long as the mouse pointer remains in this
facet.
ΓòÉΓòÉΓòÉ 10.5.3.2. While within a Part's Facet ΓòÉΓòÉΓòÉ
OpenDoc calls the potential destination part's DragWithin method continuously
while the mouse pointer remains inside the facet:
ODDragResult DragWithin(in ODDragItemIterator dragInfo,
in ODFacet facet,
in ODPoint where);
In response, the part can do any desired processing inside of its display. For
example, if a part allows objects to be dropped only in individual hot spots,
it may change its feedback based on mouse-pointer location.
Calls to a part's DragWithin method also give the part an additional chance to
examine the state of the user's system. For example, the part may want to find
out whether or not a modifier key has been pressed while a drag is in progress.
If you return kODFalse from this method, OpenDoc assumes you no longer wish to
accept a drop in this facet. It will not make additional calls to your
DragWithin method, and will not call your Drop method, as long as the mouse
pointer remains in this facet.
ΓòÉΓòÉΓòÉ 10.5.3.3. On Leaving a Part's Facet ΓòÉΓòÉΓòÉ
OpenDoc calls the DragLeave method of a potential destination part when the
mouse pointer leaves a droppable facet:
ODDragResult DragLeave (in ODFacet facet,
in ODPoint where);
In response, the part might remove the adornment on its frame or restore the
cursor appearance to its original form.
ΓòÉΓòÉΓòÉ 10.5.4. Dropping ΓòÉΓòÉΓòÉ
If the user releases the mouse button while the pointer is within a facet,
OpenDoc calls the Drop method of the facet's part. This is the interface to the
Drop method:
ODDragResult Drop (in ODDragItemIterator dropInfo,
in ODFacet facet,
in ODPoint where);
The potential destination part then decides whether it can receive the dragged
object. If it accepts the data, it either incorporates it (see Incorporating
Intrinsic Content) or embeds it (see Embedding a Single Part).
ΓòÉΓòÉΓòÉ 10.5.4.1. Drag Attributes and the Drop Method ΓòÉΓòÉΓòÉ
The OpenDoc human interface guidelines specify when a drop should be considered
a move, and when it should be a copy. The destination part can inspect the drag
attributes (by calling the GetDragAttributes method of the drag and drop
object) to determine how to handle the drop. Drag attributes are bit flags, and
more than one can be set at a time. These are the possible drag attributes for
a drop:
Constant Description
koDDragIsInSourceFrame The item being dragged has not yet left the
source frame of the drag.
koDDragIsInSourcePart The item being dragged has not yet left the
source part of the drag.
kODDropIsInSourceFrame The drop is occurring in the source frame of the
drag.
kODDropIsInSourcePart The drop is occurring in the part displayed in
the source frame of the drag (though not
necessarily in the source frame itself).
kODDropIsMove This drag and drop operation is a move.
kODDropIsCopy This drag and drop operation is a copy.
kODDropIsLink This drag and drop operation is a link (OS/2
implementation only).
kODDropIsPasteAs The destination part should display the Paste As
dialog box (the user has held down the Command
key while dropping).
Your Drop method might follow steps similar to these:
1. Use the ODDragItemIterator passed to you to determine whether you can
handle the drop, as described in On Entering a Part's Facet. (Or you
could set a "can-drop" flag in DragEnter or DragWithin that you inspect
at this point.)
2. Determine the drag attributes, to see, for example, whether this is a
move or a copy and whether or not to put up the Paste As dialog box.
3. (OS/2 implementation only) Focus the drag item storage unit on the
kODDragitem value of the kODPropContents property and create a storage
unit view. Then call the ODDragAndDrop object GetDataFromDragManager to
render the data. Upon successful completion,this method will place the
rendered data in the returned storage unit.
4. If the drag attribute kODDropIsPasteAs is set (that is, if the Command
key was held down when the drop occurred), take these steps before
reading from the drag and drop object:
Call the ShowPasteAsDialog method of the drag and drop object to
display a Paste As dialog box (see the figure in Handling the Paste
As Dialog Box).
If the ShowPasteAsDialog method returns a result of true, the user
has pressed the OK button; use the results of the interaction
(passed back as a structure of type ODPasteAsResult) to determine
what action to take. Handling the Paste As Dialog Box lists the
kinds of pasting that the user can specify.
5. Depending on the nature of the data and the user's instructions, you may
either incorporate the data or embed it, you may first translate it, and
you may create a link to its source.
If you are creating a link, follow the steps in Creating a Link at
the Destination. Take into account the automatic/manual update
setting, as well as the other Paste As settings chosen by the user.
If you are translating but not creating a link, follow the steps in
Translating Before Incorporating or Embedding.
If you are simply incorporating the data, follow the steps in
Incorporating Intrinsic Content.
If you are simply embedding the data, follow the steps in Embedding
a Single Part.
In all of these cases, you initially read the data from the drag and drop
object, and you specify the following kinds of clone transactions:
If it is a move (the drag attribute kODDropIsMove is set), specify
a clone transaction of kODCloneDropMove.
If it is a copy (the drag attribute kODDropIsCopy is set), specify a
clone transaction of kODCloneDropCopy. Note that, as destination
part, you can override a drag that is a move and force it to be a
copy. You cannot, however, override a copy and force it to be a
move.
6. Add a single action to the action history (see Adding an Action to the
Undo History) so that the user can undo the drop. Adding Two-Stage
Actions describes why this action must be a single-stage action.
7. If it is not already active, activate the frame in which the drop
occurred, and select the dropped content. When it completes, your Drop
method should return an appropriate result (of type ODDropResult), such
as kODDropCopy, kODDropMove, or kODDropFail. OpenDoc in turn passes that
information on to the source part; see Completion of StartDrag.
ΓòÉΓòÉΓòÉ 10.5.4.2. Accepting Non-OpenDoc Data ΓòÉΓòÉΓòÉ
When your part's Drop method is called, it is passed a drag-item iterator
(class ODDragItemIterator), so that you can access all drag items in the drag
and drop object. If OpenDoc data has been dragged, there is only one drag item
in the object, but if the data comes from outside of OpenDoc, there may be more
than one item. It is the responsibility of the destination part to iterate
through all the drag items to find out whether it can accept the drop.
You can examine the contents property of the storage unit of each dragged item
to determine the kind of data it consists of. If you can read data of that
type, you can incorporate it into your part. Otherwise, you may be able to
embed it as a separate part.
To access a file whose data you intend to incorporate, you can obtain the
item's HFSFlavor structure from a a value in the contents property of the
item's storage unit. You can then use that information to make
file-system-specific calls to open the file and process its contents into
your part.
To embed dropped non-OpenDoc data as a part, you treat it just as you
would OpenDoc data. You follow the procedures described in Embedding a
Single Part, specifying a cloning operation of kODClonePaste or
kODCloneDropMove for the content storage unit of the dropped data.
OpenDoc locates and binds a part editor to the data. That part editor
itself then must obtain the item's HFSFlavor structure, access the file,
and incorporate its contents.
ΓòÉΓòÉΓòÉ 10.5.4.3. Completion of StartDrag ΓòÉΓòÉΓòÉ
If your part is the source part of this drag, your call to the drag and drop
object's StartDrag method completes after the destination's Drop method
completes. You can now, based on the return value of StartDrag, confirm whether
the operation was a move or a copy or whether it failed. If it was a move, take
these steps:
1. Add an end action to the undo action history (see Adding Two-Stage
Actions), so that the user will be able to undo the entire transaction,
from initiating the drag to dropping the data. Note any applicable
special considerations given in Handling Cut Data.
2. Delete the dragged content from your part. Regardless of whether it was a
move or a copy, you should call your source frame's SetDragging method
once again, this time passing it a value of kODFalse to notify OpenDoc
that the frame can once more be a drop target.
Asynchronous drag and drop
For future compatibility, OpenDoc provides the ODPart method
DropCompleted, which is called to notify the source part of the
completion of a drag that it initiated, and provides the same drop
results that StartDrag returns for a synchronous drag.
ΓòÉΓòÉΓòÉ 10.6. Linking ΓòÉΓòÉΓòÉ
Linking is a mechanism for placing information into a part and allowing that
information to be updated, based on changes to source information in another
part or document.
Linking support in OpenDoc is a combination of event-handling code and storage
code. It includes a set of notification calls that keep the transferred data
synchronized with its source.
Some aspects of linking are closely related to the other data-transfer objects.
Link specifications, used in writing data to the clipboard or drag and drop
object, are described in Link Specification. The interactions between cutting
and pasting and the preservation of link-related objects are listed in Transfer
Rules for Links and Link Sources.
ΓòÉΓòÉΓòÉ 10.6.1. Link Concepts ΓòÉΓòÉΓòÉ
Linking requires the cooperation of one or two parts with several
linking-related objects. The following figure is a schematic illustration of
the objects and data involved in linking. The figure shows a link between two
separate parts (which can be in the same document or in different documents),
although links can also occur within a single part.
The user creates a link by requesting it during a paste or drop operation. The
part that contains the source of the information to be linked is called the
source part. The content that is to be copied and sent to another part is
called the source content or source. The source part creates an object, the
link source, that contains a copy of the source data; that copy is stored in
the same document as the source part.
The part that contains the destination of the information to be linked is
called the destination part. The content that is actually copied into the link
destination is called the destination content or destination. The destination
part's draft object creates an object, the link, that is stored in the same
document as the destination part.
The link-source object contains references to all link objects for which it is
the source. The user is free to edit the source of a link; when a change occurs
in the source part and the destination part needs to be updated, the source
part copies the source content into the link-source object. The destination
part then copies the content from the link object into the destination itself.
OpenDoc links are unidirectional; changes are propagated from the source to the
destination only. You should not permit users to edit any link destinations
that you maintain; such edits would be overwritten when the link is updated.
Whether it contains the source or the destination of a link, your part is
responsible for defining the exact content that constitutes the linked data and
for knowing what visual area it covers in your part's display. Linked data,
whether source or destination, is part of your own content; you store and
manipulate it according to your own content model and under your own control.
ΓòÉΓòÉΓòÉ 10.6.1.1. Link Update ID ΓòÉΓòÉΓòÉ
Each piece of linked content-that is, every region of content at the source or
at the destination of a link-must have an associated update ID. The source part
determines the update ID associated with the link and updates the ID each time
the source changes. The destination part stores the update ID of the link that
was current at the time the destination was last updated through the link.
Whenever the content in your part changes-for instance, in response to a series
of keyboard events-you need to assign a new update ID to all link sources
directly affected by that change. Note that all link sources affected by a
given change must have the same update ID. You obtain a new update ID by
calling the session object's UniqueChangeID method.
When you update a link source, you can save the update ID in your source
content and use it later to determine whether your source content and the
content of the link-source object are identical.
A circular link or recursive link can occur when changes to a link's
destination directly or indirectly affect its source. To avoid endless
recursive updates, it is important that your part take these steps:
Whenever your part updates a link destination that also is contained in a
link source for which automatic updating is specified, it must propagate
the update ID passed to it. Your part must update the affected source
with the update ID passed to the destination, rather than calling
UniqueChangeID.
It is possible that your part containing the link destination is embedded
in the link source content of another part. In that case, you must call
your frame's content updated method so that the part owning the link
source can be notified that your content has changed and be given the
update ID of the change.
Whenever your part updates a link source manually (on explicit user
instruction), it should not propagate an existing update ID. It should
always call UniqueChangeID to get a new update ID.
ΓòÉΓòÉΓòÉ 10.6.1.2. Automatic and Manual Propagation of Updates ΓòÉΓòÉΓòÉ
OpenDoc informs destination parts when the sources of their links have been
updated, either automatically or manually (when instructed to do so by the user
or part). The user selects whether updating is to be automatic or manual,
either in the Paste As dialog box when the link is first created or in the Link
Destination Info dialog box after the link exists.
The user initiates a manual update by pressing a button in Link Destination
info dialog box. Pressing the Update Now button in the Link Destination Info
dialog box updates only the link destination from the link object.
To be notified automatically of changes to the source of a link, your
destination part calls the link object's RegisterDependent method, supplying it
with the update ID of the last content it read from the link. Each link object
maintains a private registry of dependent parts and calls their LinkUpdated
methods whenever the link source changes. Your part should respond to this
method call by updating its destination content from the link object, as
described in Updating a Link at the Destination.
If updating is to be automatic, your part should register for update
notification when it first creates the link (passing an update ID of
kODUnknownUpdate), and whenever your part reads itself from storage. Be sure
you are prepared to receive a notification when you call RegisterDependent,
because your LinkUpdated method may be called before RegisterDependent returns.
You can unregister and re-register as desired, but be careful not to register
more than once at a time with the same link.
If your draft permissions are read-only, you should not register for update
notification . Even if you do register, you do not receive notifications.
ΓòÉΓòÉΓòÉ 10.6.1.3. Automatic and Manual Updating of Link Source Content ΓòÉΓòÉΓòÉ
OpenDoc allows owners of source parts to either specify that changes to link
source content become visible as soon as the content changes (automatic), or
only when explicitly requested (manually) by the source part. The source part
calls the IsAutoUpdate method to determine the update mode. In the automatic
mode (default), the part should update the Link Source Content Storage Unit
(CSU) as soon as possible after the editing operation, which changed the
content completely. In the manual mode, the parts should suppress updating the
Link Source Content Storage Unit until the part's UpdateFrameLinkSource method
is called.
ΓòÉΓòÉΓòÉ 10.6.1.4. Frame Link Status ΓòÉΓòÉΓòÉ
All frames have a link status that describes the frame's participation in
links. Parts are responsible for setting the link status of all frames that
they embed; the link status indicates whether the frame is in the source of a
link, in the destination of a link, or not involved in any link, or both. Parts
can use this information to decide whether to allow editing of linked data, and
whether to allow creation of a link within an existing link; see the second
table in Creating a Link that Includes Already-Linked Content for a list of the
possible combinations.
ΓòÉΓòÉΓòÉ 10.6.1.4.1. When to Change Link Status ΓòÉΓòÉΓòÉ
In general, any time that you create a link involving an embedded frame or add
an embedded frame to your part, you should set the frame's link status.
When your part creates a link source, it should call the ChangeLinkStatus
method of each of the embedded frames within the content area of the link
source, passing it the value kODInLinkSource.
If you add embedded frames to a link source, call ChangeLinkStatus for
each new frame, passing it the value kODInLinkSource.
If pasting and creating a link destination in your part involves adding
embedded frames at the destination, call ChangeLinkStatus for each new
frame, passing it the value kODInLinkDestination. (This may occur either
while performing a paste-with-link on embedded frames, or while updating
a link destination that includes embedded frames.)
Any time you embed a frame outside of any link source or destination that
you maintain, set its status to kODNotInLink.
If you break a link but keep the embedded data at the source or
destination, set the status of each frame that was formerly linked to
kODNotInLink.
Destinations take precedence over sources. If you have an embedded frame
that is contained in a link destination that you maintain, and that
destination is itself contained in a link source that you maintain, set
the frame's status to kODInLinkDestination. If you later break that link
destination (but not its enclosing link source), set the frame's status
to kODInLinkSource rather than kODNotInLink.
The ChangeLinkStatus method changes the value of the frame's link status, if
necessary, and calls the LinkStatusChanged method of the frame's part, so that
the part can change the link status of any of its own embedded frames.
When you set the link status of a frame in any of these situations, you need
to take into account only the links that your part maintains; you can ignore
your own display frame's link status. OpenDoc automatically adjusts the link
status of frames that you embed to account for your display frame's status.
You can examine the link status of a frame by calling its GetLinkStatus
method.
ΓòÉΓòÉΓòÉ 10.6.1.4.2. The LinkStatusChanged Method of your Part Editor ΓòÉΓòÉΓòÉ
Your own part's LinkStatusChanged method is called by the ChangeLinkStatus
method of any of your display frames, whenever the display frame's link status
is changed. This is its interface:
void LinkStatusChanged (in ODFrame frame);
Your implementation of LinkStatusChanged should iterate through all of your
part's embedded frames, calling each one's ChangeLinkStatus method:
The link status of embedded frames that are already part of links that
you maintain cannot change; sources will remain sources, and destinations
will remain destinations. Therefore, you do not need to call
ChangeLinkStatus for them.
For embedded frames not involved in links that you maintain, you can set
their link status according to your display frame's link status. However,
you can also just set their link status to kODNotInLink, and let OpenDoc
adjust their link status if necessary.
Making this call on all of your embedded frames allows them in turn to call
their parts' LinkStatusChanged method, to change the link status of more
deeply embedded frames, and so on.
Note that you do not need to call the ChangeLinkStatus method of any embedded
frames already involved in links that you manage, because their status will
not change.
ΓòÉΓòÉΓòÉ 10.6.1.5. Content Changes in Embedded Frames ΓòÉΓòÉΓòÉ
Because linked content can contain embedded frames, there must be a way for an
embedded part to inform its containing part that its content has changed, so
that the link can be updated.
ΓòÉΓòÉΓòÉ 10.6.1.5.1. The ContentUpdated Method ΓòÉΓòÉΓòÉ
Any time a content change occurs in one of your part's display frames, you
should follow the procedures described in Making Content Changes Known Part of
the procedure is to call the frame' ContentUpdated method. The ContentUpdated
method then calls the EmbeddedFrameUpdated method of your part's containing
part. The containing part then knows that the content of one of its embedded
frames has changed. If your display frame is involved in a link source
maintained by your part's containing part, the containing part can then choose
to update the link-source object with the new data.
(This ContentUpdated method is unrelated to the ContentUpdated method of
ODLinkSource, discussed in Updating a Link at the Source).
ΓòÉΓòÉΓòÉ 10.6.1.5.2. The EmbeddedFrameUpdated Method of your Part Editor ΓòÉΓòÉΓòÉ
Your part's EmbeddedFrameUpdated method is called by the ContentUpdated method
of any of your embedded frames, whenever the content of the frame's part has
changed. This is its interface:
void EmbeddedFrameUpdated(in ODFrame frame,
in ODUpdateID change);
The method is passed a reference to the embedded frame and an update ID
identifying the modification. You should respond by saving the Update ID and
updating any link-source objects that you maintain that involve that frame (and
whose updating is automatic). When updating the link source, pass it the Update
ID that you received in the call to this method.
Your EmbeddedFrameUpdated method should also call the ContentUpdated method of
your own display frames that contain the embedded frame, so that the change is
propagated upward throughout the embedding hierarchy.
ΓòÉΓòÉΓòÉ 10.6.1.6. Link Borders ΓòÉΓòÉΓòÉ
If your part contains the source or destination of a link, you are responsible
for drawing an appropriate border around the linked content area when requested
to do so.
Whenever you draw your part's content in a facet, first call the
ShouldShowLinks method of the facet's window. If ShouldShowLinks returns
kODTrue, draw borders around any link sources and destinations.
ΓòÉΓòÉΓòÉ 10.6.1.7. Link Info ΓòÉΓòÉΓòÉ
The link info structure (type ODLinkInfo) contains fields that hold information
about the nature of a link, such as the part kind of its intrinsic data, its
creation date, and its update ID. Your part should allocate and maintain a link
info structure for every link destination that it contains.
Here is the structure's definition:
struct ODLinkInfo
{
ODType kind;
ODTime creationTime;
ODTime changeTime;
ODUpdateID change;
ODBoolean autoUpdate;
};
ΓòÉΓòÉΓòÉ 10.6.1.8. Linking and Undo ΓòÉΓòÉΓòÉ
Just as the basic data-transfer actions (cutting, pasting, and dropping) should
be undoable, so should their variations that involve linked data. The user
should be able to undo (and redo) any of these actions:
Pasting or dropping content and creating a link to its source.
Pasting or dropping content that contains existing linked data.
Deleting or cutting content that includes one or more link sources or
destinations.
Breaking a link at its source or its destination (through the Link Info
dialog boxes).
When a link is created, the part receiving the data and creating the link
destination adds a begin action and end action to the undo action history (see
Adding Two-Stage Actions), whereas the source part adds a single action to the
history when it creates the link source.
Edits to the source content of a link must also-like any edits to the content
of your part-be undoable. However, updating a link source object from its
source content does not need to be an undoable action:
If the link source is updated manually, undoing or redoing changes to
source content has no effect on the link source object. Whenever an
update occurs, the link source object simply reflects the current state
of the source content.
If the link source is updated automatically, changes to source content
accomplished through an undo or redo action should cause you to update
the link source as usual. (Be sure to use the update ID associated with
the restored or redone content when updating.)
Likewise, updating the destination content of a link from its link object does
not need to be an undoable action:
Editing the content at the destination of a link is not generally
permitted. Because undoing or redoing an update to a destination would
constitute editing the destination, you should never put the update
action in the Undo or Redo stacks.
You can, however, allow changes to the destination content that can be
maintained across a link update, such as a style applied to the entire
destination. Such non-editing changes can be undoable.
When you delete or cut content that includes a link source or destination, or
when you break a link, follow the procedures outlined in Breaking and Cutting
Links to make sure that you can undo the actions.
ΓòÉΓòÉΓòÉ 10.6.2. Manipulating Links ΓòÉΓòÉΓòÉ
This section describes some of the basic procedures you follow in creating
links, reading and writing linked data, and updating links.
Related information is described as part of general data-transfer
considerations earlier in this chapter. Creating and removing link
specifications are described in Link Specification and Removing a Link
Specification from the Clipboard.
Both the source and destination parts of a link, or even separate destinations
of a single source, may attempt to access linked data simultaneously.
Therefore, many methods of the classes ODLinkSource and ODLink require that you
provide a key in order to access the storage unit containing the content of a
link. You obtain the key by first calling the Lock method of the link-source
object or link object involved.
ΓòÉΓòÉΓòÉ 10.6.2.1. Creating a Link at the Destination ΓòÉΓòÉΓòÉ
A link is created when the user decides-using the Paste As dialog box-to link
rather than statically transfer data while performing a paste. The destination
part-the part receiving the paste or drop-retrieves the link specification from
the clipboard or drag and drop object and calls the part's destination draft's
AcquireLink method. The source part's draft, in turn, calls the CreateLink
method of the source part (the part that placed the data in the clipboard or
drag and drop object).
If your part is the destination part that creates a link to pasted data from
the data's source, you can use the following steps. It is assumed that you have
previously called the ShowPasteAsDialog method of the clipboard or drag and
drop object (see Handling the Paste As Dialog Box), and are ready to act on the
results of the Paste As dialog box. For clipboard transfer, this means that you
have already acquired the clipboard focus (see Acquiring and Relinquishing the
Clipboard Focus).
1. Focus the clipboard or drag and drop storage unit on the link
specification it contains. Use your draft's CreateLinkSpec method to
instantiate the link specification, with the destination part as the
part, and null for the data. Then call the link specification's
ReadLinkSpec method to have it read itself from the storage unit.
2. Pass the link specification to your draft's AcquireLink method to
construct the link object from the link specification with 0 as the ID.
3. Create a link-info structure (type ODLinkInfo) to associate with the link
destination, as described in Link Info. Initialize its update ID (to
kODUnknownUpdate), set its creation time to the current time, set its
modification time to the modification time of the link object, and give
it a part kind and auto-update setting based on the results of the Paste
As dialog box.
4. Add this link object to whatever private list of link destinations you
maintain. Store the information you need to associate this link object
with its link-info structure and with the link-destination content in
your part. If you have translated the data that now makes up your link
destination, you also need to record the part kind that it was translated
from. You will have to translate the data again for each subsequent
update to the link, and you will need to know which part kind in the data
to read and translate.
5. This link-creation procedure should be undoable. Do this:
If the data transfer for this link is a drop, add a single action to
the undo action history; if the user undoes the drop, this link will
be deleted also.
If the data transfer for this link is a paste from the clipboard,
add a begin action to start a two-stage transaction. That way, if
the user decides to reverse the paste, both the paste and this link
creation will be undone together.
6. If the user specified auto-updating in the Paste As dialog box, call the
link's RegisterDependent method. Otherwise, manually update the link at
this time by performing whatever actions your part's LinkUpdated method
would perform (see Updating a Link at the Destination).
7. If the data transfer for this link was a paste from the clipboard, add an
end action to the action history at this time, to complete the two-stage
action started in step 5.
8. Call the ChangeLinkStatus method of any of your part's newly embedded
frames that are within the linked content area, passing them the value
kODInLinkDestination.
Never read the link content from the storage unit of the clipboard or drag and
drop object when creating the link; always read link content from the link
object's storage unit.
Every time the link is updated, your part (the destination part) must discard
the data in the link destination and read it again from the link object. That
can mean discarding embedded parts and creating them anew from the updated
link content. See Updating a Link at the Destination for details.
Your destination part must be able to draw a border around the link content
area when asked to do so, as described in Link Borders. Parts embedded in
linked data are not involved in the maintenance of the link; even in the case
of a link destination that consists of a single embedded part, you draw the
link border around the embedded part's frame.
Note that only the draft should call CreateLink; if your part is a destination
part that needs a link to be created, you should call your draft's AcquireLink
method.
ΓòÉΓòÉΓòÉ 10.6.2.2. Creating a Link at the Source ΓòÉΓòÉΓòÉ
When the user decides to create a link to data that your part placed on the
clipboard or drag and drop object, the destination part's draft calls the
CreateLink method of your part (the source part). The draft passes back the
data of the link specification that your part originally wrote when it placed
the data on the clipboard or drag and drop object. This is the interface to
CreateLink:
ODLinkSource CreateLink(in ODByteArray data);
When your source part's CreateLink method is called, the method should
duplicate in a link-source object all of the content that your part originally
wrote (or promised) to the clipboard or drag and drop object.
In your source part's CreateLink method, you can follow these general steps:
1. Examine your own structures to see if the link source object already
exists. CreateLink may have been called as a consequence of another
destination being added to this link source, as described in Replacing
Link-Source Content in CreateLink. If the link source does exist, take
either of these steps:
If the link-source object contains data (or promises) for all part
kinds that you support, simply return the existing link source
object and take no further action.
If the link-source object does not contain a complete set of
promises or data, you need to write the remaining part kinds to it.
You can do that by updating the link source in a particular manner.
Skip to step 5.
If the link-source object does not yet exist, continue with step 2.
2. Create a link source object by calling your draft's CreateLinkSource
method.
3. Add this link-source object to whatever private list of link sources you
maintain. Store the update ID and any other information you need to
associate this link-source object with the link-source content in your
part.
4. Call the ChangeLinkStatus method of any of your part's embedded frames
that are within the linked content area, passing them the value
kODInLinkSource.
5. Update the link source. Follow the steps listed in Updating a Link at the
Source.
6. Creating a link source needs to be undoable. Add single action to the
undo action history so that you can remove the link source if the user
reverses its creation.
7. Unlock the link-source object.
8. Return a reference to the link-source object as the method result.
ΓòÉΓòÉΓòÉ 10.6.2.2.1. Replacing Link-Source Content in CreateLink ΓòÉΓòÉΓòÉ
If your part's CreateLink method is called and the link-specification data
provided describes an existing link source that you maintain, your part must
ensure that all content kinds that you support are available at the
destination. If your part writes promises to the link source, you can ensure
that all kinds are promised by replacing the current content in your
link-source object. The procedure for replacing is similar to the regular
updating procedure, except that you pass an existing update ID to the
link-source object's Clear method, and you do not call the link-source object's
ContentUpdated method. See Updating a Link at the Source for details.
ΓòÉΓòÉΓòÉ 10.6.2.3. Updating a Link at the Destination ΓòÉΓòÉΓòÉ
When a source part updates its link-source object and (either automatically or
manually) calls the object's ContentUpdated method, the link-source object
notifies its associated link objects. Then, either immediately (for registered
destinations) or later (for unregistered destinations), the LinkUpdated method
is called. This is the interface to LinkUpdated:
void LinkUpdated(in ODLink updatedLink,
in ODUpdateID change);
If your part contains the destination of a link, you can update the destination
in your part's LinkUpdated method. Your part will receive such a call in
response to the user's request for an update in the Link Destination Info
dialog box.
It is not always necessary to update immediately, during execution of
LinkUpdated. For example, if the destination has scrolled offscreen but is
still registered as a dependent of the source, updating does not need to occur
until (and if) the destination scrolls back into view. Your part editor can, if
desired, generally perform link updates as a background task.
You can take the steps shown here to read the contents of a link object when
updating your link destinations.
1. Call the link object's Lock method.
2. Access the link object's storage unit by calling its
GetContentStorageUnit method. (If GetContentStorageUnit returns the error
kODErrNoLinkContent, the last source update failed; do not update you
destination at this time.)
Incorporate and/or embed the updated destination data into your part from
the link object's contents property:
If you are incorporating the linked data, follow the steps in
Incorporating Intrinsic Content.
If you are embedding the linked data as a single part, follow the
steps in Embedding a Single Part.
If you are translating the linked data before incorporating or
embedding it, follow the steps in Translating Before Incorporating
or Embedding Use information you recorded when you first created the
link to know which part kind to read for translation.
In any of these cases, the only differences from other data-transfer
operations are that you call the Clone method of the link object's
storage unit's draft, you specify a clone transaction of
kODCloneFromLink, and you read from the link object's content storage
unit.
Note that you must discard any previously embedded parts and frames,
replacing them with new ones cloned from the link. You must connect the
frames to your frame hierarchy, create facets for the currently visible
ones, and change their link status by calling their ChangeLinkStatus
method, passing the value kODInLinkDestination.
3. Update the link-info structure for the link, with a new update ID and new
change time, obtained by calling the link's GetUpdateID and GetChangeTime
methods.
4. Unlock the link.
5. Inspect your part's private structures to see if this change has affected
the data of any link source in your part. (It can if this destination is
within a source.) If it has, modify the source as shown in Updating a
Link at the Source.
Note that, if your part maintains the destination of a link whose source has
been transferred from one part to another, the new source part might not write
its data using all of the same part kinds as the original source part. Your
part, therefore, might not be able to obtain updates in the format that it
expects. If that situation occurs, your part should read whatever part kind it
can, or else break the link.
Note: This function is not supported for the current release of OpenDoc.
ΓòÉΓòÉΓòÉ 10.6.2.4. Updating a Link at the Source ΓòÉΓòÉΓòÉ
The part maintaining the source of a link can rewrite values into the
link-source object as necessary to update the link. If your part is the source
part, take the steps listed here to update the contents of your link source (or
to write it for the first time). The recommended procedure for updating a link
source is to first remove all content from the link source by calling its Clear
method. If the content written to a link includes embedded frames, your part
should always call Clear before updating the link. By calling Clear, you ensure
that all unneeded storage units are removed from the link, thereby saving space
in your draft.
Note that the Clear method deletes the contents property of the link source
storage unit and all its data. You need to add a contents property and values
back into the storage unit after calling Clear, just as if creating the link
source from scratch.
In summary, follow these steps:
1. Call the link-source object's Lock method, and then call its Clear
method. (If you are making a simple change that does not involve embedded
frames, you do not have to call Clear.)
If you are updating the link source due to content changes in your
part, or if you are writing to the link source for the first time,
or if this is a manual update, obtain a unique update ID and pass it
to Clear.
If you are updating the link source because of an update to a link
destination within your source content, pass the link destination's
existing update ID (the one that was passed to your LinkUpdated
method) to Clear.
If you are just adding new part kinds to an unchanged existing link
source, pass the link source's existing update ID to Clear. (You
must update the link if a new destination has been added and if the
link source does not have a complete set of promises for all part
kinds that you support.)
2. Access the link-source object's storage unit, create the contents
property and the appropriate values, and write your updated source data.
Write either data or a promise for all part kinds that you support,
including standard part kinds. If the shape or extent of the source
content has changed, update the frame shape annotation also.
If the data is intrinsic content-with or without one or more
embedded parts-follow the procedure given in Writing Intrinsic
Content.
Either way, specify a clone operation of kODCloneToLink. (If you are
writing simple data that does not involve storage unit references, you
can just write in the change instead of cloning.) You are encouraged to
write one or more promises instead of writing the actual data, to avoid
placing unused content into the link source.
3. If the content change originated in your source part, or if this is the
first time you are writing to the link, or if this is a manual update,
call the link-source object's ContentUpdated method and pass it a new
update ID (obtained from the session object's UniqueChangeID method), so
that it can in turn send update notifications to all its link
destinations.
4. If the content change occurred in a link destination contained within
your link source, call the link-source object's ContentUpdated method and
pass it the link destination's existing update ID (the one that was
passed to your LinkUpdated method), to avoid indefinite updating of
circular links.
5. If you are just adding new part kinds to an unchanged existing link
source, do not call ContentUpdated at all.
6. Unlock the link source.
In general, it is not necessary and not always desirable to update link-source
objects immediately in response to every content change. When content is
changing rapidly, such as during text entry, it is reasonable to wait for a
pause before updating any affected links.
ΓòÉΓòÉΓòÉ 10.6.2.5. Writing Linked Content to Storage ΓòÉΓòÉΓòÉ
When your part writes itself to storage (see Writing a Part to Storage), or
when it writes all or a portion of itself to a data-transfer object (see
Writing to a Data-Transfer Object), it writes any linked data-whether source or
destination-just as it would write any other content. In addition, however, it
needs to write information for reconstructing the link objects and link source
objects involved.
ΓòÉΓòÉΓòÉ 10.6.2.5.1. Writing Links in Externalize ΓòÉΓòÉΓòÉ
The information with which you associate the link-source object or link object
with a specific region of your content is not standardized; it depends on the
nature of your content. However, for all links your Externalize method and your
methods that write to data-transfer objects need to write persistent references
to the objects themselves plus additional information. You should write the
information in a format that is standard to your part kind; that way, any part
editor that reads your part kind can reconstruct links from the stored data.
For the source of a link, you need to include, in addition to a persistent
reference to the storage unit of the ODLinkSource object, the update ID
associated with the linked content.
For the destination of a link, you need to include a persistent reference to
the storage unit of the ODLink object, plus the information in the fields of
the ODLinkInfo structure: the part kind, creation time, modification time,
update ID, and auto-update setting of the linked data. Also, if your part has
translated the linked data at the destination, you need to store the original
part kind of the data (the kind before you translated it).
Your part may also store extra information for convenience. For example,
because your part needs to be able to draw a border around linked data, it may
store link-border shapes in a private structure.
ΓòÉΓòÉΓòÉ 10.6.2.5.2. Writing Links for Data Transfer ΓòÉΓòÉΓòÉ
If your part writes a single embedded frame (without surrounding intrinsic
content) that is a link source or link destination to the clipboard or drag and
drop object. Clone the link or link source object into the data-transfer
object's draft and add a property with the name kODPropProxyContents to the
clipboard or drag and drop storage unit. In that property write a value that
references the link or link source object, as well as the link info fields and
other necessary information. Any destination part that understands the proxy
format can then recover the link. Note that, if you write linked content to a
data-transfer object and the destination part does not support linking, the
content may be transferred but the links will be broken.
ΓòÉΓòÉΓòÉ 10.6.2.6. Reading Linked Content from Storage ΓòÉΓòÉΓòÉ
When your part reads itself into memory (see The InitPartFromStorage Method),
it may need to read in link-source objects and link objects as well. Likewise,
if the user pastes or drops information into your part that contains linked
data that you can incorporate (see Incorporating Intrinsic Content), you can
read that data and redirect the context of the links to be your own part.
Because OpenDoc can eliminate links during data transfer (see,for example, the
first table under Moving and Copying Links and Link Sources), you must perform
the following two tasks when reading in data that includes links:
Before you attempt to read in a link-related object, you must ensure that
your persistent reference to it is valid.
For every link-source object that you read in, you must ensure that your
part is assigned as its source. The original source of the link may have
been in another part, if the source content has been moved or copied.
ΓòÉΓòÉΓòÉ 10.6.2.6.1. Reading Links in InitPartFromStorage ΓòÉΓòÉΓòÉ
Here are the steps you can take to handle linked data when reading your part
(in InitPartFromStorage). The overall procedure is described in The
InitPartFromStorage Method.
1. As you encounter a persistent reference to a link object or link-source
object in the content you are reading, ensure that the reference is
valid. Before trying to read in the referenced storage unit, pass the
persistent reference to the IsValidStorageUnitRef method of the storage
unit containing the content you are reading.
If IsValidStorageUnitRef returns true, Call your draft's
AcquireLinkSource or AcquireLink method to read in the object.
If IsValidStorageUnitRef returns false, the link-source object or
link object no longer exists, and your part should no longer
consider the associated portion of its own content as linked. This
is not an error situation, and you need not notify the user if it
occurs.
2. For each link-source object you read, call its SetSourcePart method to
associate it with your part. Add the link-source object to whatever
private list of link sources you maintain, and associate it with the
specific content in your part that is the source of the link.
3. For each link object you read, associate the link object with the
specific content in your part that is the destination of the link. Add
the link object to whatever private structures you maintain for link
destinations.
4. For each link object you read, be prepared to register for update
notification if updating is to be automatic. Once the linked data becomes
visible or affects visible content, pass the last-saved update ID to the
link's RegisterDependent method.
ΓòÉΓòÉΓòÉ 10.6.2.6.2. Reading Links for Data Transfer ΓòÉΓòÉΓòÉ
Here are the steps you can take to handle linked data when reading (cloning)
from the clipboard or drag and drop object. The overall procedure for reading
from a data-transfer object is described in Incorporating Intrinsic Content.
1. After the clone operation completes successfully-assuming that you have
followed the cloning steps listed in Cloning Process and have saved all
object IDs returned by the Clone method-pass the ID of each cloned link
object or link-source object to your draft's IsValidID method to
determine if its object is valid.
If IsValidID returns true, call your draft's AcquireLinkSource or
AcquireLink method to read in the object. If IsValidID returns false, the
link-source object or link object no longer exists and was not cloned.
2. For each link-source object you read, call its SetSourcePart method to
associate it with your part. Add the link-source object to whatever
private list of link sources you maintain, and associate it with the
specific content in your part that is the source of the link. (That
information is included in the contents property of the transferred data
and is defined by the format of the part kind your editor is reading.)
3. For each link object you read, associate the link object with the
specific content in your part that is the destination of the link. Add
the link object to whatever private structures you maintain for link
destinations. (That information is included in the contents property of
the transferred data and is defined by the format of the part kind your
editor is reading.)
4. If you are reading and embedding a single part that is also a link source
or destination, there should be a property named kODPropProxyContent in
the data-transfer storage unit. The property should contain a persistent
reference to the link-source object or link object associated with the
embedded part, plus any other needed information such as the fields of
the link info structure. If you understand the format of the data in the
property, read it and use it.
Note that, if you have incorporated an existing link source into your part,
you may not be able to write all the data types (part kinds) to the link
source object that its previous owner did. That is not an error; simply write
the part kinds that you support.
ΓòÉΓòÉΓòÉ 10.6.2.7. Revealing the Source of a Link ΓòÉΓòÉΓòÉ
Users need to be able to navigate from the destination of a link to the source
content. When the user selects the Show Link Source button in the Link
Destination Info dialog box the destination part calls the ShowSourceContent
method of the link object. OpenDoc, in turn, calls the source part's RevealLink
method. This is its interface:
void RevealLink(in ODLinkSource linkSource);
If your part contains the source of a link, your RevealLink method should
display the part content that corresponds to the link source, following these
general steps:
1. Using whatever part-specific method you choose, locate a display frame of
the source content. If no suitable frame exists, open a part window to
display the source.
2. Ensure that the frame displaying the source is visible in the window.
Find its containing frame by calling its AcquireContainingFrame method,
then call the RevealFrame method of the containing frame's part. (The
procedure is simple if your part opened up a separate part window for the
link source, because the display frame, being the root frame for the
window, is already visible.)
3. Activate the frame, using your part's normal activation procedure (see
How Part Activation Happens). Scroll the content, if necessary, to make
the source content visible.
4. Select the content and draw a link border around it.
ΓòÉΓòÉΓòÉ 10.6.2.8. Editing a Link Destination ΓòÉΓòÉΓòÉ
Because OpenDoc links are unidirectional from source to destination, the
OpenDoc human interface guidelines restrict the editing that a user can perform
within the destination of a link. Basically, changing of content in a link
destination is not allowed.
It is the responsibility of the destination part to block attempts to edit its
link destinations. However, the part that initially receives an editing event
may be a frame embedded within, perhaps deeply embedded within, the destination
part. If your part's display frame has the status kODInLinkDestination and the
user attempts to edit content within it, your part should call the EditInLink
method of your frame. OpenDoc in turn calls the EditInLinkAttempted method of
the part that maintains the link destination that includes your frame. This is
the interface to EditInLinkAttempted:
ODBoolean EditInLinkAttempted (in ODFrame frame);
The part that maintains the link destination should, in its EditInLinkAttempted
method, take steps similar to these:
1. It should check that it does indeed maintain a link destination that
involves the specified frame. If not, the method should return false and
exit.
2. It should present to the user an alert box that allows the user to forego
the editing, break the link, or display its source. In any case, the part
should not activate itself.
If the user chooses to forego editing, EditInLinkAttempted should
simply return true.
If the user chooses to display the source, EditInLinkAttempted
should follow the instructions listed in Revealing the Source of a
Link.
If the user chooses to break the link, EditInLinkAttempted should
follow the instructions listed in Breaking and Cutting Links, and
return true.
If EditInLinkAttempted returns true, then EditInLink returns true to your part
(the part in whose display frame the edit attempt occurred). If the user has
broken the link, your part can allow editing in your display frame.
If EditInLinkAttempted returns false, then EditInLink returns false and
OpenDoc cannot find the part that maintains the link destination. Your part
should then put up its own simple alert box informing the user that editing in
a link destination is not allowed.
If the user selects more than one link destination in your part and then
attempts to edit the data, your part cannot even call EditInLink. It should
then disallow editing and display a dialog box, suggesting that the user
select a single destination.
ΓòÉΓòÉΓòÉ 10.6.2.9. Breaking and Cutting Links ΓòÉΓòÉΓòÉ
Users can delete links in several ways. When you delete or cut content that
includes a link source, or when you break a link at its source, your part
relinquishes ownership of the link source object, as noted in the first table
under Moving and Copying Links and Link Sources. You should:
Disassociate the link-source object from your part's content
Set the link's source part to null by calling the link source's
SetSourcePart method
save a reference to the link-source object in an undo action, rather than
releasing the link source
Call the SetChangedFromPrev method of your draft to make sure that this
change is saved when the document is closed
Change the link status of any affected frames (see Frame Link Status) for
a link that has been broken
It is not necessary at this point to update the link source to delete its
contents. If the user subsequently undoes the action, you can reverse the
steps listed in the previous paragraph. You must be sure to call the link
source object's SetSourcePart method once again to reclaim ownership of the
link source object.
When you delete or cut content that includes a link destination, or when you
break a link at its destination, you should:
Disassociate the link object from your part's content
Save a reference to the link object in an undo action, rather than
releasing the link
If there are no remaining destinations of this link in your part, call
the link object's UnregisterDependent method, so that your part will no
longer be notified when updates occur
Call the SetChangedFromPrev method of your draft, to make sure that this
change is saved when the document is closed
Change the link status of any affected frames (see Frame Link Status) for
a link that has been broken
Reversing these actions undoes the break or cut. Your part should hold the
references to a cut or broken link source or link until your
DisposeActionState method is called, at which time you can release the link or
link-source object.
ΓòÉΓòÉΓòÉ 10.6.3. Transfer Rules for Links and Link Sources ΓòÉΓòÉΓòÉ
This section summarizes the semantics of data transfer involving link objects
and link-source objects. This discussion is for informational purposes only; if
you follow the procedures presented earlier in this chapter, the correct
semantics will always occur.
During some data-transfer operations, OpenDoc may delete or redirect link or
link-source objects. Other operations are permitted by OpenDoc but should not
be supported by your part. This section lists the basic data-transfer
operations, in terms of:
What happens when your part moves or copies linked data
What happens when your part creates a link destination when pasting or
dropping data that is already linked
What configurations of links within links your part should support
ΓòÉΓòÉΓòÉ 10.6.3.1. Moving and Copying Links and Link Sources ΓòÉΓòÉΓòÉ
OpenDoc enforces the actions listed in the following table when existing links
or link sources and their associated intrinsic content are transferred by drag
and drop or with the clipboard and the appropriate kinds of cloning
transactions are specified.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéOperation ΓöéObject ΓöéBehavior Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéCopy (Drag-copy, or ΓöéLink source ΓöéData is transferred;Γöé
ΓöéCopy followed by Γöé Γöélink source is not. Γöé
ΓöéPaste) Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé ΓöéLink destination ΓöéData is transferred;Γöé
Γöé Γöé Γöélink destination is Γöé
Γöé Γöé Γöétransferred also Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé ΓöéSource and ΓöéData is transferred;Γöé
Γöé Γöédestination of same Γöélink source and Γöé
Γöé Γöélink Γöédestination are bothΓöé
Γöé Γöé Γöétransferred (but anyΓöé
Γöé Γöé Γöélinks to Γöé
Γöé Γöé Γöédestinations of thisΓöé
Γöé Γöé Γöénew link source Γöé
Γöé Γöé Γöéoutside the copied Γöé
Γöé Γöé Γöécontent are broken) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéMove (Drag-move, or ΓöéLink source ΓöéData is transferred;Γöé
ΓöéCut followed by Γöé Γöélink source is Γöé
ΓöéPaste) Γöé Γöétransferred also Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé ΓöéLink destination ΓöéData is transferred;Γöé
Γöé Γöé Γöélink destination is Γöé
Γöé Γöé Γöétransferred also Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé ΓöéSource and ΓöéData is transferred;Γöé
Γöé Γöédestination of same Γöélink source and Γöé
Γöé Γöélink Γöédestination are bothΓöé
Γöé Γöé Γöétransferred Γöé
Γöé Γöé ΓöéNote: Γöé
Γöé Γöé ΓöéThe move function Γöé
Γöé Γöé Γöéis not supported Γöé
Γöé Γöé Γöéin the current Γöé
Γöé Γöé Γöérelease of OpenDoc. Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 10.6.3.2. Creating a Link that Includes Already-Linked Content ΓòÉΓòÉΓòÉ
When pasting or accepting a drop into your part, you can incorporate or embed
data that includes existing link sources or destinations and at the same time
attempt to make the data a link destination. In that situation, OpenDoc
enforces the actions listed in the following table. Note that the link
specification in the table refers to the entire clipboard (or drag and drop)
content; it is written by the part that placed the data on the clipboard or
drag and drop object.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéContent to Paste ΓöéBehavior in New Link Γöé
Γöé ΓöéDestination Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéIntrinsic content ΓöéData is pasted at the Γöé
Γöé Γöédestination; new link Γöé
Γöé Γöédestination is created from Γöé
Γöé Γöélink specification. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink source Γöé Data is pasted at the Γöé
Γöé Γöédestination; new link Γöé
Γöé Γöédestination is created from Γöé
Γöé Γöélink specification (link Γöé
Γöé Γöésource in pasted data is not Γöé
Γöé Γöémaintained) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink destination ΓöéData is pasted at the Γöé
Γöé Γöédestination; new link Γöé
Γöé Γöédestination is created from Γöé
Γöé Γöélink specification (link Γöé
Γöé Γöédestination in pasted data is Γöé
Γöé Γöénot maintained) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéSource and destination of the ΓöéData is pasted at the Γöé
Γöésame link Γöédestination; new link Γöé
Γöé Γöédestination is created from Γöé
Γöé Γöélink specification (link Γöé
Γöé Γöésource and destination in Γöé
Γöé Γöépasted data are not Γöé
Γöé Γöémaintained) Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
You can create links within links; OpenDoc does not prevent you from creating
or pasting the source or destination of one link within the source or
destination of another link. However, only some configurations make sense, as
shown in the following table.
You can use your display frame's link status (see Frame Link Status, as well as
private link-status information about your own intrinsic content, to decide
whether to allow the creation of a link source or destination in any given
situation. In general, you cannot edit a link destination.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéTo create a new... ΓöéWithin an ΓöéRecommendation Γöé
Γöé Γöéexisting... Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink source ΓöéLink source ΓöéOK. Result is two Γöé
Γöé Γöé Γöéseparate link Γöé
Γöé Γöé Γöésources with Γöé
Γöé Γöé Γöépartially or Γöé
Γöé Γöé Γöécompletely Γöé
Γöé Γöé Γöéoverlapping content.Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink source ΓöéLink destination ΓöéNO. When the Γöé
Γöé Γöé Γöédestination updates,Γöé
Γöé Γöé Γöéit is generally Γöé
Γöé Γöé Γöéimpossible to know Γöé
Γöé Γöé Γöéwhich changes shouldΓöé
Γöé Γöé Γöébecome part of the Γöé
Γöé Γöé Γöéupdated source. Γöé
Γöé Γöé ΓöéAlso, any edits to Γöé
Γöé Γöé Γöéthe source are then Γöé
Γöé Γöé Γöélost. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink destination ΓöéLink source ΓöéOK. Updates to the Γöé
Γöé Γöé Γöédestination simply Γöé
Γöé Γöé Γöébecome part of the Γöé
Γöé Γöé Γöésource. Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéLink destination ΓöéLink destination ΓöéNO. Updates to Γöé
Γöé Γöé Γöéeither link Γöé
Γöé Γöé Γöédestination will Γöé
Γöé Γöé Γöéoverwrite the other.Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 11. Semantic Events and Scripting ΓòÉΓòÉΓòÉ
This is the seventh of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes the scripting support provided for
your parts by OpenDoc.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. For additional concepts related to
your part editor's run-time environment, see OpenDoc Run-Time Features.
Scripting support is one example of the use of the OpenDoc Extension interface.
Extending OpenDoc through its extension interface is described in general terms
in Extending OpenDoc.
This chapter first summarizes the Open Scripting Architecture and OpenDoc
scripting in general, and then describes the OpenDoc semantic interface, a set
of classes and handlers you can take advantage of in implementing your support
for scripting. The chapter then describes how to write and install:
Semantic-event handlers
Object accessors
Object-callback functions, coercion handlers, and pre-dispatch handlers
This chapter concludes with a discussion of how to construct object specifiers
and send semantic events from your part.
Note: The explanations in this chapter extend the discussions of OSA events,
the OSA event object model, and the Open Scripting Architecture
presented in the Open Scripting Architecture Programming Guide and
Reference. It is assumed that you are already familiar with, or have
access to, the descriptions of scripting found in that book.
ΓòÉΓòÉΓòÉ 11.1. Scripting and OpenDoc ΓòÉΓòÉΓòÉ
Scripting is a powerful way to automate and customize programs. By adding
scripts to a program and executing them, a user can simplify and automate tasks
that might otherwise require extensive and repetitive human interaction. If the
program supports it, a user might even be able to use scripts to change the
meaning of existing commands. As a simple example, a user might customize the
meaning of a "Save" command so that it updates a logging database every time a
document is saved.
OpenDoc allows for scripting of semantic events, distinguished from user events
in that they are high-level actions understandable by the user and by the
program, but independent of individual user actions. Opening, closing, saving
documents, embedding parts, and manipulating content can be examples of
semantic events.
OpenDoc supports your part editor's ability to receive and process semantic
events through its semantic interface, a set of classes providing methods that
you can use or override. The class ODSemanticInterface is a subclass of the
extension class (ODExtension), described in Classes for Extending OpenDoc. You
use the semantic interface class to set up and register semantic-event handlers
and callback functions. (OpenDoc also supports your part editor's ability to
create and send semantic events through another object, the messaging
interface, an object of the class ODMessageInterface).
Any entity that understands the semantic interface of a part can send it
semantic events. Script editors can convert scripting commands to semantic
events and send them to any object in any part of a document. Parts can send
semantic events to their containing parts, to embedded parts, to sibling parts,
to linked parts-to any parts for which they can find an address. Other types of
OpenDoc components can also send semantic events; spelling checkers, for
example, can use them to operate on the content of text parts.
Scripting support in OpenDoc can be pervasive; if you take full advantage of
it, literally every action a user can take may invoke a script. However, a
range of options exists, and you can decide what level of scripting support
makes the most sense for your parts.
Your part editor supports scripting through the OpenDoc semantic interface in
much the same way as a conventional application supports scripting. Your part
editor must provide an interface to its content objects and operations, and it
must accept semantic events. The document shell passes semantic events to
OpenDoc to deliver to your parts. Event targets are described by object
specifiers, which refer to objects in your parts in terms of your published
content model. The document shell needs information from your part editor to
resolve these specifiers so that it can determine where to deliver the events.
ΓòÉΓòÉΓòÉ 11.1.1. Open Scripting Architecture ΓòÉΓòÉΓòÉ
The scripting capability of OpenDoc is based on the Open Scripting Architecture
(OSA). OSA is a powerful cross-platform messaging and scripting system that can
support multiple scripting languages. OSA has three basic components:
The events messaging system
The events object model
One or more scripting systems
This section gives a brief summary of these components. For complete
documentation of OSA, see the Open Scripting Architecture Programming Guide
and Reference
ΓòÉΓòÉΓòÉ 11.1.1.1. OSA Events ΓòÉΓòÉΓòÉ
The system of semantic events at the base of the OpenDoc implementation of OSA
is OSA events. OSA events are described in the events chapters of Open
Scripting Architecture Programming Guide and Reference.
OSA events are messages that applications use to request services and
information from each other. The application making the request constructs and
sends an event to the application receiving the request. The receiving
application uses an event handler to extract pertinent data from the event,
perform the requested action, and possibly return a result.
OSA events constitute a standard vocabulary of actions that can be requested.
OSA events are grouped into suites of related events. The OSA Event Registry:
Standard Suites describes several commonly implemented suites. The most
important suite to support for general scriptability is the Core suite, the
suite that contains events common to many kinds of application software.
When sending an event, a requesting application can specify that the event
apply, not to the receiving application as a whole, but to some element of the
application's data. For example, an application can request the data of a
particular row of a particular table in a particular report. The sender
constructs an object specifier that it sends along with the OSA event to denote
the exact element or elements that the event applies to. The Event Registry
includes definitions of the kinds of objects recognized within each suite.
The Event Manager is the component of system software that manages the
construction, sending, and processing of OSA events. It handles OSA events in
general and performs event dispatching for conventional applications. With
OpenDoc, events and the Event Manager function essentially as described in the
Open Scripting Architecture Programming Guide and Reference; however, there are
some differences caused by the differences between parts and conventional
applications:
OpenDoc does its own dispatching of OSA events. This is to ensure that
the event is directed to the correct part and that the reply is directed
back to the part that sent the original event. That behavior is almost
exactly the same as that provided by the Event Manager, except that the
sender of the event no longer has access to the event's returnID
parameter. Because this parameter is for OpenDoc's internal use only it
is therefore missing from the Send method of ODMessageInterface
OpenDoc itself implements some of the Event Manager functions. For those
functions, you must use the corresponding methods defined by OpenDoc
instead of directly calling the Event Manager. Cover methods for event
functions are contained in the classes ODSemanticInterface (for calling,
installing, and removing handlers), ODMessageInterface (for constructing
and sending events), and ODNameResolver (for resolving object
specifiers). Functions for which there is no corresponding OpenDoc method
are available directly from the Event Manager. The following table lists
the OSA Event Manager functions and their equivalent OpenDoc methods:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéOSA Event Manager Function (doΓöéOpenDoc Method (do use) Γöé
Γöénot use) Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAEResolve ΓöéODNameResolver::Resolve Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAECallObjectAccessor ΓöéODNameResolver::CallObjectAccessor Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAECreateOSAEvent ΓöéODMessageInterface::CreateEvent Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAESend ΓöéODMessageInterface::Send Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAEInstallCoercionHandler ΓöéODSemanticInterface::InstallCoercionHandler Γöé
ΓöéAEInstallEventHandler ΓöéODSemanticInterface::InstallEventHandler Γöé
ΓöéAEInstallObjectAccessor ΓöéODSemanticInterface::InstallObjectAccessor Γöé
ΓöéAEInstallSpecialHandler ΓöéODSemanticInterface::InstallSpecialHandler Γöé
Γöé Γöé(plus individual InstallCallback methods) Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAEGetCoercionHandler ΓöéODSemanticInterface::GetCoercionHandler Γöé
ΓöéAEGetEventHandler ΓöéODSemanticInterface::GetEventHandler Γöé
ΓöéAEGetObjectAccessor ΓöéODSemanticInterface::GetObjectAccessor Γöé
ΓöéAEGetSpecialHandler ΓöéODSemanticInterface::GetSpecialHandler Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéAERemoveCoercionHandler ΓöéODSemanticInterface::RemoveCoercionHandler Γöé
ΓöéAERemoveEventHandler ΓöéODSemanticInterface::RemoveEventHandler Γöé
ΓöéAERemoveObjectAccessor ΓöéODSemanticInterface::RemoveObjectAccessor Γöé
ΓöéAERemoveSpecialHandler ΓöéODSemanticInterface::RemoveSpecialHandler Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
At run time, OpenDoc handles semantic events by accepting the events and
passing them to the appropriate part editor. There is no separate dispatcher
object for semantic events; the OpenDoc dispatcher that handles user events
also dispatches semantic events.
ΓòÉΓòÉΓòÉ 11.1.1.2. Event Object Model ΓòÉΓòÉΓòÉ
OpenDoc relies on the event object model to specify individual elements of a
part's content. The object model defines a hierarchical arrangement of content
objects whose nature depends on the content model of the part. OSA events have
object specifiers in their direct parameters to access individual content
objects within a part. Part editors provide object accessor functions, used by
the name resolver's Resolve method to resolve the object specifiers in OSA
events that the parts receive.
The event object model used by OpenDoc is slightly different from that used by
conventional applications. Here are the differences:
The OpenDoc version of the object model has the ability to deal with
multiple parts in a single document. It achieves this ability by defining
a context for each event. The context for an OSA event in OpenDoc is the
equivalent of the object model's default container; it is the outermost
object in the object hierarchy defined by the direct parameter. The
default container for an OSA event is typically represented by the
application that receives the event; the context for an OpenDoc OSA
event, by contrast, might be the document shell, or any of the parts in a
document.
In processing an OSA event, OpenDoc reverses the typical sequence from
that used by the OSA Event Manager. The object specifier in the direct
parameter of an OSA event is resolved before any event handler is called.
The direct object may contain a description of the destination part, and
that determines the context of the event. OpenDoc therefore replaces the
object specifier in the direct parameter of the OSA event with the token
returned from the resolution (see Returning Tokens) before calling a
part's event handler.
The Resolve method of the class ODNameResolver, which is a wrapper for
the Event Manager AEResolve function, has a parameter used only by
OpenDoc. The parameter, of type ODPart, specifies the part that is the
context from which to start an object resolution. Also, unlike AEResolve,
the Resolve method has no callbackFlags parameter, because callback flags
in OpenDoc are specified on a part-by-part basis; they are set when the
part calls the SetOSLSupportFlags method of the ODSemanticInterface
class.
Object resolution is possible for parts embedded within parts that do not
support scripting. OpenDoc provides default object accessors and event
handlers that allow a message to pass through a part that does not
support scripting into an embedded parts that does. See OpenDoc Semantic
Interface for more information.
OpenDoc adds a noun to the OSA Event Registry. To represent a part (more
strictly a display frame of a part), the registry defines the descriptor
type 'part', for which you can also use the constant cPart.
OpenDoc does not support calling the RemoveSpecialHandler function with
the keyword keySelectProc in order to disable object-model support.
Instead, making this call throws an exception.
Your part editor should implement object accessors as if your part were a
stand-alone conventional application-that is, with your part itself as the
context.
ΓòÉΓòÉΓòÉ 11.1.1.3. Scripting Systems ΓòÉΓòÉΓòÉ
A scripting system completes the OSA implementation on a platform. It is
through a scripting system that the user designs, attaches, and executes code
to generate the semantic events that a scriptable part receives.
Any OSA-compliant scripting system can be used with OpenDoc.
OpenDoc allows the user to employ any available scripting system and even to
switch among them during execution.
ΓòÉΓòÉΓòÉ 11.1.2. Part Content Model ΓòÉΓòÉΓòÉ
To fully support scripting of your parts, you must construct a content model
that includes a full set of content objects, accessible through semantic
events, to allow a complete range of operations on your user-visible content.
Your part editor must provide accessor functions to resolve external references
to content objects, and it must also provide semantic-event handlers that
implement content operations.
The advantage of developing a content model and implementing the functions for
resolving object specifiers and handling semantic events is that you increase
flexibility and you provide user control over the basic functions of your part
editor. For example, your parts will automatically be able to accept input from
currently unavailable or unforeseen user interfaces, such as voice or pen or
touch, that generate semantic events.
If you also factor your part editor-that is, if you separate your part editor's
core date engine from its user interface-you further increase its flexibility.
Interface elements such as menus and dialogs, if reconstructed to invoke
scripts or otherwise generate semantic events, will still be able to
communicate directly with your parts. Factoring also facilitates making your
parts recordable (see hdref refid=odrcrdl.).
ΓòÉΓòÉΓòÉ 11.1.2.1. Content Operations ΓòÉΓòÉΓòÉ
The operations of a content model should be consistent with user actions. They
typically include selection, creation, deletion, insertion, setting of
properties, and so on. Low-level internal functions, such as piece-table
functions for efficient text insertion or matrix inversion for fast graphics
processing, are probably not appropriate as content-model operations.
Your content operations correspond to the semantic events you support. You
implement a semantic-event handler for each operation.
ΓòÉΓòÉΓòÉ 11.1.2.2. Content Objects ΓòÉΓòÉΓòÉ
The objects of a content model should be consistent with what the user sees and
manipulates, regardless of your part editor's internal structures. Inside a
text part, for example, a user may see lines, words, paragraphs, characters,
and embedded parts. Those are typical content objects for such a part.
Internally, your part editor might maintain run-length encoding arrays, line
end arrays, and so on. However, because they are not presented to the user,
they are not part of the content model. Likewise, a graphics part might have
content objects like circles, rectangles, and lines, regardless of what
internal mathematical descriptions it uses.
If a part supports embedding, embedded parts within it constitute a special
class of content objects. The containing part can use content operations to
manipulate the frames of embedded parts and perhaps some part-wide properties,
but it cannot in general manipulate their contents-only the parts themselves
can do that.
The OpenDoc default object accessors already provide this basic ability to
access the properties of, and send semantic events to, embedded frames. If your
part is a container part and needs capabilities beyond this basic level, your
object model can include a named type of content object that represents an
embedded part to which you can pass events and from which you can extract
properties.
User selections
If you want to make user selections controllable through semantic events, you
must describe them with object specifiers, defined in terms of your part's
content model.
ΓòÉΓòÉΓòÉ 11.1.2.3. Resolving Object References ΓòÉΓòÉΓòÉ
Object specifiers in an event can refer to any content objects, including
embedded parts. When it receives events, your part must provide accessor
functions to allow those specifiers to be resolved. The accessor functions of a
part return tokens, which are descriptors that identify the content objects or
properties of those objects.
As an example of content-object resolution, consider a document whose root part
is a text part. Suppose that the user selects a portion of document and chooses
a text style of "bold". To handle this command through semantic events, the
root part could generate an object specifier to represent the current
selection. In response to the "bold" menu command, the root part sends itself a
semantic event-"Set Data to bold"-with the selection as the event's direct
parameter.
In response to the "Set Data to bold" event, the part resolves the object
specifier to a list of tokens representing its content objects. The event
handler for "Set Data to bold" then tells each of the content objects to make
itself bold. Some of those objects may represent embedded parts. If the frames
understand how to pass the "Set Data to bold" event on to the embedded parts
that they represent, they do so. If the embedded parts recognize how to set
their content to bold, they then do so; otherwise, they do nothing.
Object accessor functions and object-specifier resolution are described in more
detail in Writing Object Accessors.
ΓòÉΓòÉΓòÉ 11.1.3. Levels of Scripting Support ΓòÉΓòÉΓòÉ
You can implement different levels of support for scripting in your part
editor. Each successive level requires more effort but it gives the user
greater flexibility and control over the functioning of your parts.
ΓòÉΓòÉΓòÉ 11.1.3.1. Scriptable Parts ΓòÉΓòÉΓòÉ
If you make your parts scriptable, they will have at least a basic level of
scripting support. To do that, you must create a content model for your parts
with defined content objects (available through object accessors) and content
operations (represented by semantic-event handlers) that are meaningful to the
user. Then your part editor publishes a description of its content objects and
operations and accepts semantic events:
Your part editor publishes its list of content objects and operations by
placing them in its terminology resource, a resource of type Open
Scripting Architecture Quick Referenceaete" that each scriptable part
editor must provide.
Your part editor accepts semantic events through its semantic interface,
available publicly as an OpenDoc extension object. The semantic interface
includes object accessor functions as well as semantic-event handlers.
Even among scriptability there are different levels of support. You can allow
script access to only a few content objects and operations or to many of them.
For your parts to be fully scriptable, semantic events must be able to invoke
any action a user might be able to perform.
One advantage of making your part scriptable is that you can allow it to be
used by other parts, even parts that you may not have developed yourself. For
example, if your part is a text part and you allow script access to all of its
text-style settings, the user (or your containing part or a sibling part) can
easily format any of your text for any purpose.
ΓòÉΓòÉΓòÉ 11.1.3.2. Recordable Parts ΓòÉΓòÉΓòÉ
If you make your parts recordable, the user's actions can be captured as a
series of semantic events, converted to scripts, and replayed at a later time
to reenact the actions. A recommended way to make your parts recordable is to
completely separate the user interface from the core data engine of your part
editor.
If your part editor handles every user-interface event by generating, sending
to itself, and handling an equivalent semantic event, it can process all
semantic events through a standard bottleneck and have the Event Manager record
the actions. This use of a bottleneck not only allows recording but also
enhances code portability.
The events that pass through the bottleneck should be just the set of actions
that match the content model of your part editor. These events need not follow
exactly the inner design of your core data engine, but they should reflect the
complete range of user actions.
ΓòÉΓòÉΓòÉ 11.1.3.3. Customizable Parts ΓòÉΓòÉΓòÉ
If you make your part's interface customizable, the user can only invoke all
actions through scripts, but can also change the nature of the actions
themselves by attaching additional scripts that are invoked when the actions
are executed.
To support customizable parts, a part editor must not only be scriptable but
must also define content objects and operations for interface elements such as
menus and buttons, or provide other ways to trigger scripts. It must provide
persistent storage for any scripts that the user attaches to its interface
elements.
Making a part fully customizable requires that the part editor allow attachment
and invocation of scripts during virtually any user action. For the highest
levels of customizability, user actions such as open, close, save, and restore
as well as all editing should be scriptable. (In OpenDoc, only the root part's
attached scripts can be invoked for document-level events such as saving,
restoring, opening, closing, printing, and so on).
Before processing any user event or semantic event, the part editor must check
whether a script attached to the part wants to handle the event. If so, it
allows the script to run.
Note that, if your part is recordable, you can check for the presence of and
invoke scripts in a semantic-event dispatching bottleneck, in which case you
achieve full customizability with little extra effort.
ΓòÉΓòÉΓòÉ 11.1.4. Scripting-Related Objects ΓòÉΓòÉΓòÉ
Several OpenDoc classes provide a structure for its support of semantic events
and scripting. Implementations of these and other classes constitute the
OpenDoc semantic interface, described in OpenDoc Semantic Interface. The
following figure, a duplication of a portion of the OpenDoc class hierarchy
(first figure in A Set of Classes, Not a Framework), shows the principal
OpenDoc classes involved with scripting.
Three classes provide basic support for scripting in OpenDoc:
The class ODSemanticInterface defines the programming interface through
which script editors or other sources of semantic events communicate with
your part. It is a subclass of ODExtension and is an abstract superclass.
The class ODNameResolver represents the name resolver, a subclass of
ODObject that is instantiated by the session object. It is used in
resolving object specifiers; see Object Resolution.
The class ODMessageInterface represents the message interface, a subclass
of ODObject that is instantiated by the session object. It provides an
interface through which your part sends semantic events to other parts;
see Sending Semantic Events.
(The message interface is also the object through which OpenDoc sends semantic
events to your part, although your part does not make any calls to the message
interface in that situation).
OpenDoc also provides a set of scripting-related classes that are wrappers for
OSA event descriptor structures. These classes, whose inheritance is
diagrammed in the following figure, are analogous to the OpenDoc support
classes shown in the last figure in A Set of Classes, Not a Framework.
The object-descriptor classes exist so that future versions of OpenDoc can
support remote callbacks. They are mostly simple wrappers for structures,
although some define few methods that you can call. Other than ODOSLToken, the
structures that these objects wrap are all OSA events structures. Here is what
each of them is for:
The class ODDesc is a general wrapper for a descriptor structure (type
AEDesc), the basic structure used for building OSA event attributes and
parameters. The other classes in this list are all direct or indirect
subclasses of ODDesc. ODDesc itself is a direct subclass of ODObject.
ODDesc provides methods for extracting and replacing the data of the OSA
event descriptor it contains.
The class ODOSLToken is a wrapper for an OpenDoc token, described in
Returning Tokens. ODOSLToken provides a method for duplicating itself.
The class ODAddressDesc is a wrapper for an address descriptor structure
(type AEAddressDesc), a descriptor structure that contains a target
address.
The class ODObjectSpec is a wrapper for an object specifier structure
(type typeObjectSpecifier), a descriptor structure that describes the
location of one or more OSA event objects.
The class ODDescList is a wrapper for a descriptor list (type
AEDescList), a descriptor structure that is a list of other descriptor
structures.
The class ODRecord is a wrapper for an OSA structure (type AERecord), a
descriptor list that can be used to construct OSA event parameters and
other structures.
The class ODOSAEvent is a wrapper for an OSA event structure (type
AEEvent), a structure that describes a full-fledged OSA event.
ΓòÉΓòÉΓòÉ 11.2. OpenDoc Semantic Interface ΓòÉΓòÉΓòÉ
The class ODSemanticInterface defines the programming interface through which
your semantic-event handlers and object accessors are called.
OpenDoc provides default semantic-event handlers and object accessors that
allow it to access and send semantic events to scriptable parts embedded within
parts that do not themselves support scripting. Even if your part does support
scripting, you can use these defaults and build on their capabilities, rather
than duplicating them.
ΓòÉΓòÉΓòÉ 11.2.1. Default Get Data and Set Data Event Handlers ΓòÉΓòÉΓòÉ
To allow senders of semantic events to access certain basic information about
any part in a document-regardless of whether that part is scriptable-OpenDoc
provides two default event handlers. The handlers for the Get Data and Set Data
OSA events are used for any part that does not replace them with its own
versions.
The information manipulated by these handlers is the standard set of Info
properties. These handlers cannot manipulate the content of any part. The event
class of the handlers is kCoreEventClass, and their event IDs are kAEGetData
and kAESetData.
You can rely on the default Get Data and Set Data handlers to provide script
access to your parts standard Info properties. Such limited access does not
really constitute scriptability; to provide script access to your part's
intrinsic content or to other Info properties of your part or of parts embedded
in your part, you need to write you own handlers.
When you write your own semantic-event handler, follow the instructions in
Writing Semantic-Event Handlers.
ΓòÉΓòÉΓòÉ 11.2.2. Default Object Accessors ΓòÉΓòÉΓòÉ
OpenDoc provides default object accessors that give script access to
information within parts that do not support semantic events. Even if your part
editor does support scripting, you can rely on these accessors to perform the
following specific tasks; you need write object accessors only for other
purposes, such as accessing your intrinsic content.
Part. From the null container, a default accessor can return a token
representing an embedded part. If your part is not scriptable, if it does
not provide such an accessor, or if its accessor does not handle this
task, the name resolver's Resolve method uses a default accessor to
return a reference to a frame embedded within your part.
The default accessor can resolve references to embedded parts specified
by part name, part index, or part ID. Part ID in this case is not the
part's storage-unit ID, but its persistent object ID, an identifying
value used only for script access. A part's persistent object ID is
unique within its draft and is valid across sessions. You can obtain a
persistent ID for a part or a frame by calling the GetPersistentObjectID
method of its draft; you can recreate a part or frame object by passing
its persistent object ID to its draft's AcquirePersistentObject method.
The token returned by this default accessor has the format described in
Standard Embedded-Frame Token.
Property. From the null container, a default accessor can return a token
representing a standard Info property of the part that is the current
context. If you do not provide such an accessor, or if your accessor does
not handle this task, the name resolver uses this default accessor to
return a reference to a standard Info property of your part.
Wild card. From a container of type cPart, a default accessor can return
a swap token (see Returning a Swap Token), so that the name resolver can
switch the context from the current part to a more deeply embedded one.
If you do not provide such an accessor, or if your accessor does not
handle this task, the name resolver uses this default accessor to change
the context to the appropriate part embedded within your part.
If your part editor supports scripting but you do not want to duplicate the
functions of these default accessors, you can write accessors only for tasks
beyond the defaults: accessing objects in your part's intrinsic content,
accessing Info properties that you have defined for your own part of for parts
embedded in your part, altering the ordering/indexing scheme for embedded
parts, and so on.
When one of your object accessors receives a token that it does not recognize
as having been created by another of your own object accessors, it can simply
return the OSA event error errAEEventNotHandled to the name resolver; the name
resolver then attempts the resolution with the default accessors. Conversely,
when your accessor receives a token that it recognizes as your own, the
accessor creates a token as described in Returning Tokens, and returns it to
the name resolver.
Note that tokens created by your object accessors contain data in a format
that your semantic-event handlers can understand, whereas tokens describing
Info properties created by the default accessors are in a private format.
Therefore, if you allow the default accessor to return tokens for the standard
Info properties, you also need to allow the default Get Data and Set Data
handlers to manipulate those standard Info properties. In that case, your own
GetData and Set Data handlers need only manipulate your intrinsic content plus
any custom properties you have defined for your part or for embedded parts.
ΓòÉΓòÉΓòÉ 11.2.3. Standard Embedded-Frame Token ΓòÉΓòÉΓòÉ
To allow access to scriptable parts embedded within parts that are not
scriptable, OpenDoc provides the default part accessor (described in the
previous section). That accessor returns a token in a standard format. The
token has a descriptor type cPart, and its dataHandle field contains only a
frame pointer and a part pointer (of type ODFrame and ODPart, respectively).
If your part is a container part and is scriptable, it must be able to support
the creation of an embedded-frame token, either through its own object
accessors or by letting the default part accessor construct the token.
ΓòÉΓòÉΓòÉ 11.3. Implementing your Semantic Interface ΓòÉΓòÉΓòÉ
To be scriptable, your part must create a semantic interface that allows it to
accept semantic events, handle them, and reply to them. It must:
Provide semantic-event handlers for at least the core suite of OSA events
Provide object accessors for its content objects
Provide other kinds of handlers (such as object callback functions and
coercion handlers) as appropriate
Make public, through a terminology resource, the events it handles and
content-object types it recognizes
ΓòÉΓòÉΓòÉ 11.3.1. Writing Semantic-Event Handlers ΓòÉΓòÉΓòÉ
You need to create a handler for every semantic event that your part editor
recognizes. Your list of semantic-event handlers defines the content operations
that your parts engage in and allow users to access.
How your semantic-event handlers manipulate your parts' content is entirely up
to you. This section discusses only the process by which your handler receives
and handles a semantic event. Installing your handler is described in
Installation
A typical source of semantic events is a script engine or another part. The
script engine or part generates semantic events and sends them to your
document's document shell. This is how your part ends up receiving and
processing a semantic event:
1. The identity of your part-the destination part-generally have been
encoded in the object specifier in the direct parameter of the OSA event.
Your part can be at any level of embedding in the document, and the
encoding can be either explicit or implicit; for example, your part may
be the implicit target when the event explicitly requests a custom Info
property of one of your embedded parts.
2. The OpenDoc document shell receives the OSA event and passes it to the
message interface object, by calling the message interface's
ProcessSemanticEvent method.
3. The message interface object calls the name resolver's Resolve method for
the event's direct parameter. Within the Resolve method, the
object-resolution process may cycle several times (see Resolving Object
References), until your part's semantic interface has been accessed and
an object accessor of yours has returned a token that specifies your part
or an object contained within it.
4. The message interface object calls the CallEventHandler method of the
semantic interface to dispatch the event to your semantic-event handler.
The CallEventHandler in turn examines the event class and ID to determine
to which handler to dispatch.
5. Your part may have an attached script for any semantic event. If so, you
execute the script at this point. If not, or if the script does not
handle the event, or if the script handler passes the event on after
processing, you dispatch the event to the semantic-event handler.
6. Your semantic event handler, on receiving the event, decides whether and
how to resolve additional parameters. In general, you should only perform
destructive actions (such as writing) on your own part; do not resolve
parameters that would result in destructive actions being taken on
embedded parts.
7. Your event handler carries out its operation, returning a reply OSA event
if appropriate.
You fill out reply OSA events just as conventional applications do, as
described in the Open Scripting Architecture Programming Guide and
Reference. If the semantic event was sent by another part, the message
interface object generates and keeps track of the return ID for the
event, so that your reply can be routed back to the sender's reply event
handler.
ΓòÉΓòÉΓòÉ 11.3.2. Writing Object Accessors ΓòÉΓòÉΓòÉ
Object specifiers in an OSA event can refer to any content objects. If your
part is to receive OSA events, it must provide accessor functions to allow
those specifiers to be resolved. Your accessor functions return tokens (see
Returning Tokens) that your own semantic-event handlers know how to interpret.
OpenDoc passes those tokens to your handlers when it resolves objects in the
direct parameters of OSA events; your handlers themselves call the name
resolver's Resolve method and receive the tokens for other OSA event parameters
that are object specifiers.
If your scriptable part is also a container part, it must provide object
accessor methods for objects that represent embedded parts, and it must be able
to relinquish the context-that is, it must be able to hand off the
object-resolution process to an embedded part (see Returning a Swap Token).
OpenDoc includes default object accessors that provide the minimum capability
for swapping context, but your part can replace or add to the capabilities of
those accessors, if desired.
This section describes how your object accessor is called and which tokens it
constructs and returns. The section also describes the default accessors
provided with OpenDoc so that semantic events can be sent to parts embedded
within containing parts that do not themselves support scripting.
ΓòÉΓòÉΓòÉ 11.3.2.1. Object Resolution ΓòÉΓòÉΓòÉ
OpenDoc resolves object specifiers for content objects within parts much as the
OSA Event Manager resolves object specifiers in conventional applications.
However, there are some differences, including these:
For semantic events sent from outside of a document, the document shell
is the first handler of an object-specifier resolution. Its role is to
hand off that resolution to correct part. To find that part, OpenDoc uses
object accessor functions provided by individual parts to obtain
part-relative tokens (tokens for which the part is the context).
OpenDoc reads parts into memory, if necessary, when interpreting object
specifiers. For example, an embedded part referred to in a specifier may
not be currently visible and thus not yet read into memory. To resolve
the chain of objects further, OpenDoc may have to read in the part and
then access an object within it.
The callback flags used by the Resolve method (equivalent to the
callbackFlags parameter of the OSA Event Manager AEResolve function) have
a scope that is local to each part, rather than global to the object
resolution process. Each part sets its callback flags by calling the
SetOSLSupportFlags method of its semantic interface.
OpenDoc takes the following steps to resolve a reference to a content object.
The process starts when the document shell (in the case of semantic events
from outside the document) receives a semantic event and calls the message
interface's ProcessSemanticEvent method, as described under steps 1 and 2 in
Writing Semantic-Event Handlers.
1. The message interface calls the Resolve method of the name resolver
(class ODNameResolver), passing it the object specifier in the direct
parameter of the OSA event (steps 1 and 2 of the figure shown at the end
of these steps).
If the direct parameter does not exist, there may be a subject attribute
in the event that takes the place of a direct parameter (see Sending
Semantic Events). If there is no direct parameter or subject attribute,
or if the direct parameter is not an object specifier, Resolve is not
called and the event goes to the root part.
2. Not shown in the figure represented at the end of these steps is the fact
that the Resolve method first accesses the document shell's semantic
interface and gives it a chance to resolve the object specifier. If, as
is typical, the event is targeted to a part rather than to the document
shell itself, the document shell passes resolution to the root part. The
Resolve method then takes the following steps, first with the root part
and then with the appropriate embedded parts until the specifier is
finally resolved.
(Note that, because OSA event objects exist in a hierarchy of containers,
several cycles through the following steps, and thus several context
swaps may be necessary to identify the specific object within a
hierarchy).
a. Starting with the default container (this part's context), the
Resolve method calls the AcquireExtension method of this part to get
the list of its object accessors. The Resolve method finds the
accessor for the specified property or element and calls it (step 3
of the figure shown at the end of these steps).
b. The object accessor returns a token to the Resolve method, following
the steps listed in Returning Tokens.
If the object is not an embedded part, the accessor puts into
the token whatever information is needed to map the token to
the right content object, and returns the token to the Resolve
method.
If the content object represents an embedded frame as a whole,
the accessor creates and returns a token that specifies the
embedded frame.
OpenDoc provides a default part accessor that performs this
task if this part's object accessor does not, or if this part
is not scriptable. See Default Object Accessors.
If the content object represents a directly accessible property
of an embedded part-either a standard Info property such as the
embedded part's modification date, or a custom property that
this part may have defined, such as "is-selected"-the accessor
creates and returns a token that specifies the requested
property.
OpenDoc provides a default property accessor that performs this
task (for the standard Info properties only) if this part's
object accessor does not, or if this part is not scriptable.
See Default Object Accessors.
If the content object represents an object within an embedded
part, or a property of the embedded part that this part cannot
access, the accessor returns a special token (see Returning a
Swap Token). At the next pass, the embedded part's list of
accessors is used instead (step 4 of the figure shown at the
end of these steps).
OpenDoc provides a default wild-card accessor that performs
this swap if this part's object accessor does not, or if this
part is not scriptable. See Default Object Accessors.
c. The Resolve method finds the object accessor for the next property
or element in the hierarchy of the object specifier and passes the
returned token as the container to that accessor. That accessor, in
turn, returns another token. This cycle continues, with context
swaps occurring when appropriate, until the innermost element of the
object specifier has been converted to a token and passed back to
Resolve (steps 5 and 6 of the figure shown at the end of these
steps).
3. After resolving the object specifier, Resolve returns the final token to
the message interface object (step 7 of the figure shown at the end of
these steps). OpenDoc then passes that final token to the proper part's
semantic-event handler, as the direct parameter of the OSA event (step 8
of the figure shown at the end of these steps).
ΓòÉΓòÉΓòÉ 11.3.2.2. Returning Tokens ΓòÉΓòÉΓòÉ
In the OpenDoc version of OSA events, a token is a special descriptor
structure, implemented as an OpenDoc object of type ODOSLToken, that a part
uses to identify one or more content objects within itself. Your object
accessor functions return tokens when they successfully resolve object
specifiers. The structure of a token is not public, but it contains an OpenDoc
object (of type ODDesc) that is itself a wrapper for an OSA event descriptor
structure (type AEDesc). An OSA event descriptor consists of a 4-byte
descriptorType field followed by a 4-byte dataHandle field.
OpenDoc hides the structure of the ODOSLToken and ODDesc objects; you cannot
manipulate their fields directly. This privacy allows OpenDoc to store extra
information that it needs inside a token, and it also ensures that OpenDoc's
scripting support will be compatible with future distributed-object models.
When your object accessor needs to return a token, it modifies the ODOSLToken
object that was passed to it, placing the data of an AEDesc structure into the
ODOSLToken object before returning:
1. The accessor creates a descriptor of type AEDesc and sets its
descriptorType and dataHandle fields to store the information needed.
(Note that the dataHandle field must really be a handle).
2. The accessor then calls the GetUserToken method of the name resolver to
access the OpenDoc descriptor object (of type ODDesc) that is contained
within the token that was passed to the accessor.
3. The accessor assigns the AEDesc descriptor to the ODDesc object, using
the function AEDescToODDesc (from a utility library provided with
OpenDoc), and then disposes of the AEDesc.
4. Because the ODOSLToken object passed to the accessor and the ODDesc
object returned by GetUserToken are not copies, any changes that the
accessor has made are already reflected in the ODOSLToken object itself.
Therefore, the accessor simply returns, and the name resolver can then
examine the modified token.
The ODDesUtl utility library also provides the function ODDescToAEDesc, which
allows you to extract the AEDesc descriptor structure from an ODDesc object,
in order to inspect or modify it.
Your object accessors can verify the tokens passed to them by calling the name
resolver's IsODToken method. The name resolver also provides the
GetContextFromToken method, which allows your accessor to determine, for
example, which display frame of its part contains the target of the event.
ΓòÉΓòÉΓòÉ 11.3.2.3. Returning a Swap Token ΓòÉΓòÉΓòÉ
Sometimes your object accessor function is asked to access a content object (or
a property that your part cannot directly access) from a object whose class is
cPart-meaning that the requested item is something within a frame embedded in
your part. In this case, the accessor must pass back a special token, called a
swap token, to inform the name resolver of its inability to furnish the
required token. Your accessor creates this token by calling the
CreateSwapToken method of the name resolver to initialize the swap token,
passing it a pointer to the embedded frame and a pointer to the part (your
part) containing the embedded frame. Your accessor then should simply return a
value of noErr, taking no further action.
Upon receipt of the swap token, the name resolver changes the current context
from your part to the embedded part, and retries the object access in that
context.
If your part is a container part and is scriptable, it must support such
context switches with swap tokens, either through its own object accessors or
by letting a default accessor (see Default Object Accessors) perform the swap.
Note: OpenDoc reserves the descriptor type "swch" for its own use. Do not use
this value, or any value consisting solely of lowercase letters (such as
"part"), in the descriptorType field of your own tokens.
ΓòÉΓòÉΓòÉ 11.3.2.4. Other Considerations ΓòÉΓòÉΓòÉ
When you write an object accessor, note that, in interpreting object
specifiers, "part X of doc Y" implies "part X of <current draft> of doc Y".
Your part can provide object accessors for document-wide user-interface
elements, to be used when it is the root part of a document. For example, as
root part it can provide accessors for window scroll bars or for document
characteristics such as page size.
If your object accessor needs to know the frame through which your part was
accessed-the frame that displays the part that is the current context-it can
call the GetContextFromToken method of the name resolver. The value returned
represents the most recent frame passed to CreateSwapToken.
ΓòÉΓòÉΓòÉ 11.3.3. Writing Other Kinds of Handlers ΓòÉΓòÉΓòÉ
In addition to semantic-event handlers and object accessors, you can write
other special-purpose functions and install them for use in interpreting
semantic events. This section discusses OpenDoc issues related to
object-callback functions, coercion handlers, and other kinds of handlers.
ΓòÉΓòÉΓòÉ 11.3.3.1. Object Callback Functions ΓòÉΓòÉΓòÉ
Your part can provide object callback functions for the Resolve method to call
where your part needs to provide extra information before object resolution can
occur. You can use these functions, also called special handlers, for a variety
of purposes:
You can provide an object-counting function (CountProc) so that, when the
object specifier involves a test, Resolve can determine how many elements
it must examine in performing the test. You must provide this function if
you want the OSA Event Manager to perform whose tests, that is, to
resolve object specifier records of key form formTest without assistance
from your part.
You can provide an object-comparison function (CompareProc), which
determines whether one element or descriptor record is equal to another.
You must provide this function if you want the OSA Event Manager to
perform whose tests, that is, to resolve object specifier records of key
form formTest without assistance from your part.
You can provide a token-disposal function (DisposeTokenProc)-which
overrides the Event Manager's AEDisdposeDesc function-in case you need to
do any extra processing when your tokens are disposed of.
You can provide an error-callback function (GetErrDescProc), provides a
descriptor into which the Event Manager can write information about
resolution failures.
You can provide three kinds of marking-callback functions, which allow
your part to use its own marking scheme to identify sets of objects:
- A marking function (MarkProc) marks a set of objects.
- An unmarking function (AdjustMarksProc) removes marks from
previously marked object sets.
- A marker-token function (GetMarkTokenProc) returns a token that can
be used to mark a set of objects.
All callback functions in OpenDoc function exactly as they do with
conventional applications, except for those minor changes:
Each has an additional parameter, of type ODPart, to allow access to your
part object from the callback function.
Some parameters have different types from their conventional equivalents.
For example, parameters of type AEDesc in conventional callbacks have
types ODDesc or ODOSLToken in OpenDoc callbacks.
OpenDoc callback functions return an error type of ODErr, instead of the
OSErr type returned by callback functions for conventional applications.
You install object callbacks as described in Installing Handlers, Accessors,
and Callbacks.
Object-callback functions (and whose tests) are described in more detail in
Open Scripting Architecture Programming Guide and Reference.
ΓòÉΓòÉΓòÉ 11.3.3.2. Coercion Handlers ΓòÉΓòÉΓòÉ
Coercion handlers are functions that convert data of one descriptor type into
data of another descriptor type. Coercion handlers are common in OSA events.
Some are provided by the Event Manager, others may be provided by your part
editor. Any coercion handlers installed by your part editor are called only
when your part is the context for the event. Normally, the document shell is
the context, but there are two situations in which your part can become the
context:
When an object accessor installed by your part must be called during the
resolution of an object specifier
When your part's semantic-event handler is called
Coercion handlers are not chained by OpenDoc. That is, an embedded part does
not inherit the coercion handlers of its containing part, and a root part does
not inherit the coercion handlers of the shell.
Coercion handlers are described in more detail in the Open Scripting
Architecture Programming Guide and Reference
ΓòÉΓòÉΓòÉ 11.3.3.3. Pre-Dispatch Handlers and Recording ΓòÉΓòÉΓòÉ
A pre-dispatch handler is a function that is called whenever your document
receives any event, before your part's handler for that OSA event is called.
OpenDoc allows you to install pre-dispatch handlers and to specify whether or
not you are currently using a given pre-dispatch handler.
For example, if your part is recordable, you can install a pre-dispatch handler
to intercept the Start Recording and Stop Recording OSA events that are sent to
the document shell. (OpenDoc does not automatically forward those events to
each part in a document). Even so, your part may be read into memory and
initialized after the user has turned recording on, in which case your
pre-dispatch handler won't receive the Start Recording OSA event. Therefore,
when your part initializes itself, it should also check with the OSA Event
Manager to see if recording is on. If so, it can record its actions.
Pre-dispatch handlers are described in more detail, along with the
keyPreDispatch constant, in Open Scripting Architecture Programming Guide and
Reference.
ΓòÉΓòÉΓòÉ 11.3.4. Installation ΓòÉΓòÉΓòÉ
Once you have an instance of the semantic interface, you need to install its
components as described in this section.
ΓòÉΓòÉΓòÉ 11.3.4.1. Making the Semantic-Interface Extension Available ΓòÉΓòÉΓòÉ
You must override the (inherited) part methods HasExtension and
AcquireExtension so that they return the semantic interface object. The ODType
constant that names the semantic interface extension is
kODExtSemanticInterface; the constant is passed by callers of your HasExtension
and AcquireExtension methods.
In implementing and interacting with the semantic interface, follow the rules
for using OpenDoc extensions, as described in OpenDoc Extension Interface.
ΓòÉΓòÉΓòÉ 11.3.4.2. Installing Handlers, Accessors, and Callbacks ΓòÉΓòÉΓòÉ
Your part editor's semantic interface is mainly a table of handlers that
OpenDoc uses when processing a semantic event: You call methods of the semantic
interface to install, remove, and call event handlers, object accessors and
other special callback functions.
You install semantic-event handlers by calling the InstallEventHandler
method.
You install object accessors by calling the InstallObjectAccessor method.
You install coercion handlers by calling the InstallCoercionHandler
method.
You install object callback functions in either of two ways. You can call
the InstallSpecialHandler method and pass it a parameter specifying the
kind of function to install, or you can call function-specific methods
such as InstallCountProc and InstallMarkProc. Constants for the parameter
you pass to InstallSpecialHandler are defined by the OSA Event Manager,
and have names such as keyAECompareProc.
You install a pre-dispatch handler either by calling the
InstallSpecialHandler method, passing it a parameter whose value is
keyPreDispatch, or by calling the UsingPreDispatchProc method.
Other methods of the semantic interface allow access to and removal of these
handlers, accessors, and callbacks.
You use the SetOSLSupportFlags method to set flags that, during the resolution
process, notify the name resolver's Resolve method of the kinds of
object-callback support that your part editor can provide for resolution of
object specifiers.
ΓòÉΓòÉΓòÉ 11.3.4.3. Installing System-Level Handlers ΓòÉΓòÉΓòÉ
To install system-level semantic-event handlers, coercion handlers, object
accessors and object callback functions, you can use the standard OSA Event
Manager installation functions and set the isSysHandler parameter to TRUE.
In general installing system-level handlers is discouraged. If you do install
them, be sure that you don't leave them installed after your part has closed.
You should remove any installed handlers in your part's ReleaseAll method, or
possibly in your part's Release method when the reference count goes to 0.
ΓòÉΓòÉΓòÉ 11.3.5. Making your Terminology Resource Available ΓòÉΓòÉΓòÉ
When a scripting system first compiles a script that targets an OpenDoc
document, it needs access to the terminology ("aete") resources associated with
that document. The scripting system gains this access by calling the GetAETE
method of the OSATerminology class.
Your part editor should install a terminology resource in the registration
database by using the OSAInstallApplication function when it is first
registered. When the GetAETE method is called with OpenDoc specified, all the
terminology resources of all installed part editors, including the document
shell terminology resource, are returned. Because the scripting system merges
all terminology resources into a single composite resource for all parts and
all documents, it is extremely important that your part editor avoid
terminology conflicts with other part editors.
Also, because the only available terminologies are those in the system-wide
merged "aete" resource, your part editor cannot make its terminologies known
dynamically; it cannot itself handle the Get AETE event.
ΓòÉΓòÉΓòÉ 11.4. Sending Semantic Events ΓòÉΓòÉΓòÉ
Although the ability to send semantic events is not required for scriptability,
OpenDoc provides specific support for it. This section discusses the
OpenDoc-specific aspects of sending semantic events; for more information, see
the Open Scripting Architecture Programming Guide and Reference.
The OpenDoc message interface object (ODMessageInterface) is responsible for
constructing OSA events, getting and setting event attributes, parsing events,
and sending events. This section describes how your part uses the message
interface and the OSA Event Manager to send a semantic event.
To construct the OSA event, you call the CreateEvent method of the message
interface object. You can then construct additional parameters and add them to
the OSA event by calling the OSA Event Manager AEPutParamPtr or AEPutParamDesc
function.
ΓòÉΓòÉΓòÉ 11.4.1. Constructing Object Specifiers ΓòÉΓòÉΓòÉ
To send an OSA event to an OpenDoc part, the sender must use an object
specifier that identifies the target part. The object specifier can be in the
direct parameter of the OSA event, or it can be in a subject attribute. A
subject attribute is an object specifier that refers to a part by its
persistent object ID (see Default Object Accessors). All recorded events (all
events sent through the Send method of the message interface) include a subject
attribute. The presence of a subject attribute allows a scripting system to
record the events' targets, even for events that have no direct parameter.
Conventional applications using the OSA Event Manager to send events to a part
in an OpenDoc document construct the appropriate object specifier themselves.
If your part sends a semantic event, you can use the methods described in this
section to construct an object specifier. Note that if you send a semantic
event to your own part (as when recording), you follow the same procedures as
when sending an event to another part.
ΓòÉΓòÉΓòÉ 11.4.1.1. Using CreatePartObjSpec ΓòÉΓòÉΓòÉ
If your part sends a semantic event to another part, you can use the
CreatePartObjSpec method of the message interface to construct an object
specifier for the direct parameter of the event. You must also call the
CreatePartAddrDesc method of the message interface to create an address
descriptor that identifies the process (OpenDoc document) in which the
destination part resides.
If you use CreatePartObjSpec, OpenDoc dispatches to the specified part
directly, without calling the Resolve method of the name resolver. This
dispatching is efficient and fast. However, you cannot inspect the contents of
the object specifier constructed by CreatePartObjSpec or use it for recording,
and you cannot use it as a component of another object specifier that you
construct.
ΓòÉΓòÉΓòÉ 11.4.1.2. Using EmbeddedFrameSpec ΓòÉΓòÉΓòÉ
You can also use the EmbeddedFrameSpec method of ODPart to help construct an
object specifier. You can inspect the resulting specifier, you can use it for
recording, and you can use it as the direct parameter or as another parameter
in an OSA event. Object specifiers created through EmbeddedFrameSpec have forms
such as "embedded frame 2 of embedded frame 1 of the root frame".
To construct an object specifier in this way, you call the EmbeddedFrameSpec
method of the part containing the frame that is the target for the event. Here
is the interface to EmbeddedFrameSpec:
void EmbeddedFrameSpec(in ODFrame embeddedFrame,
in ODObjectSpec spec);
A part receiving this method call should create an object specifier for the
supplied embedded frame (using index number or any other appropriate
identifying characteristic), call the EmbeddedFrameSpec method of its own
containing part, and then concatenate the results. The final object specifier
returned to the original caller thus describes the target frame in the context
of its document.
Constructing object specifiers with EmbeddedFrameSpec is most useful for
situations in which a target part's position in the embedding hierarchy is more
important than its specific identity. Note, however, that EmbeddedFrameSpec
fails if any part in the embedding hierarchy from the target frame to the root
frame is not scriptable or has not implemented the EmbeddedFrameSpec method.
Also, an object specifier constructed by this method will become incorrect if
the identifying characteristic of any frame in the hierarchy changes.
ΓòÉΓòÉΓòÉ 11.4.1.3. Using Persistent Object ID ΓòÉΓòÉΓòÉ
The most reliable method for constructing an object specifier for a part is by
using its persistent object ID. If you are constructing an object specifier and
your call to EmbeddedFrameSpec fails, or if you are sending an OSA event to a
part with no display frames, or any time you need an object specifier that will
be valid for a part regardless of its position in the embedding hierarchy, use
its persistent object ID.
Call the GetPersistentObjectID method of the target frame's or part's draft and
use the return value in the object specifier. If your own frame is the target,
as when recording, call your own draft's GetPersistentObjectID method and pass
it your own display frame.
ΓòÉΓòÉΓòÉ 11.4.2. Sending the Event ΓòÉΓòÉΓòÉ
To send the OSA event, you call the Send method of the message interface
object. The message interface adds a subject attribute, representing the target
part (which is your part if you are sending the part to yourself) to the event.
The message interface also generates and keeps track of the return ID for the
event so that the reply can be routed back to your part editor's reply event
handler.
ΓòÉΓòÉΓòÉ 12. Extending OpenDoc ΓòÉΓòÉΓòÉ
This is the last of eight chapters that discuss the OpenDoc programming
interface in detail. This chapter describes how you can use or alter portions
of the OpenDoc class library to enhance the capabilities of your part editors.
Before reading this chapter, you should be familiar with the concepts presented
in Introduction and Development Overview. For additional concepts related to
your part editor's run-time environment, see OpenDoc Run-Time Features.
This chapter discusses the following ways in which you can extend OpenDoc:
By creating OpenDoc extension objects for your part editor, you can add
programming interfaces to your parts for any purpose. The semantic
interface support in OpenDoc, described in Semantic Events and Scripting
is an example of the use of extension objects.
By creating a settings extension, you can give users access to
editor-specific settings through the Properties notebook.
By creating specialized dispatch modules, you can define new kinds of
user events that your parts can respond to.
By creating specialized focus modules, you can define new categories of
foci (shared resources) that your parts can acquire and exchange.
By creating a subclass of ODTransform, you can extend the ways in which
your part transforms the images it draws.
By creating a shell plug-in, a modification of the functions of the
document shell, you can add additional document-wide capabilities to
OpenDoc.
By patching (replacing) specific OpenDoc objects, you can modify some of
the fundamental capabilities of OpenDoc.
ΓòÉΓòÉΓòÉ 12.1. OpenDoc Extension Interface ΓòÉΓòÉΓòÉ
You can greatly extend the capabilities of your parts, in terms of fast
processing of information or communication with other parts, if you use the
interface-extension capabilities of OpenDoc. The extension protocol allows
parts or other OpenDoc objects to increase their capabilities by extending
their programming interfaces. By using extension interfaces, your parts can
communicate with other parts or other kinds of OpenDoc components in ways not
possible with the standard OpenDoc programming interface.
The Semantic Interface extension to OpenDoc, described in Semantic Events and
Scripting, is an example of the use of extensions to support scripting. Other
kinds of extension interfaces can be especially valuable in those situations
where scripting cannot provide enough integration or bandwidth.
You design, create, and attach such an extension to your part editor. At
run-time, other parts can then access and use the extension interface, through
calls to your parts.
ΓòÉΓòÉΓòÉ 12.1.1. Extension Objects ΓòÉΓòÉΓòÉ
All subclasses of ODObject, including shapes, facets, documents, windows,
frames, and parts, can be extended. An extension is itself an object, an
instantiation of a subclass of ODExtension. Each extension object is related to
its base object -the object whose interface it extends-by its extension name,
an ISO type name. A caller accesses a base object's extension through that
extension name. (Note that base object in this sense has nothing to do with
inheritance; OpenDoc uses the term superclass to describe an ancestor in the
class hierarchy). For example, the extension name for the semantic interface
extension to a part is kODExtSemanticInterface.
Extensible objects create and delete their own extensions and manage the
extensions' storage. If desired, a base object can share an extension object
among multiple clients, perhaps using a reference-counting scheme to decide
when to delete the extensions. A caller can query an extensible object to see
if it supports a specified extension.
The ODExtension class itself has minimal functionality. It is designed to act
as a superclass class for subclasses that implement actual extension
interfaces. Every extension object knows what base object it is an extension of
and forwards two kinds of calls to its base object: Release calls and calls to
its own interface.
ΓòÉΓòÉΓòÉ 12.1.2. Using an Extension ΓòÉΓòÉΓòÉ
To access the extension interface of an extensible part, a client, or caller,
takes these steps:
1. It calls the part's override of its inherited HasExtension method to see
if the extension is supported, passing the part (the base object) an
extension name.
2. If the part has such an extension, the client then calls the part's
(override of its inherited) AcquireExtension method to get a reference to
the extension object. The part either creates the extension object or
increases its reference count (if the object already exists) and passes
the reference back to the client.
3. The client makes extension-interface calls directly to the extension
object.
When the client has finished using the services of the extension object, it
takes these steps:
1. The client calls the extension's (override of its inherited) Release
method, to let the extension know that the client no longer needs it.
2. The extension, in turn, calls the (override of the inherited)
ReleaseExtension method of its part (its base object) if its reference
count has dropped to 0. The base object can then delete the extension.
However, if the extension's base object has already been deleted and has
called the extension's BaseRemoved method (see Closing your Part), the
extension cannot call its base's ReleaseExtension method.
ΓòÉΓòÉΓòÉ 12.1.3. Implementing Extensions ΓòÉΓòÉΓòÉ
Your part editors can implement any kinds of desired extension interfaces
through this mechanism. The capabilities gained through extensions can be in
almost any area. Examples include extensions to handle text search,
spell-checking, linking, specialized text formatting, database access, and
specialized graphics processing. In general, extension objects are best suited
for tasks requiring high bandwidth or tight integration, for which scripting is
not appropriate.
If you implement an extension object for your part, the extension must include
a SOM constructor (somInit), a SOM destructor (somUninit), and an
initialization (InitExtension) method. The extension must also support a
GetBase method, through which a client can obtain a reference to the
extension's base object. (The ODExtension class provides a default
implementation for GetBase).
Note that your part is the factory for its extensions, which are
reference-counted objects. You must follow the procedures described in Factory
Methods when creating, managing, and deleting extensions. Also, if your part's
document is closed while it has extensions remaining in memory, your part's
ReleaseAll method must call the extensions' BaseRemoved method.
An extension object must always be valid (attached to its base object) to be
used. If a client tries to access an invalid extension, a
kODErrInvalidExtension exception is generated. Any time after your part, as
base object, calls its extension's BaseRemoved method, the extension is
invalid. If you want to provide your own validation scheme for extensions, you
need to override the ODExtension methods CheckValid, IsValid, and BaseRemoved.
Your extension interfaces can be private to your parts, or you can establish or
follow public standards. CI Labs is the agency responsible for coordinating
standard extension interfaces for parts. For information on existing extension
interfaces, or to propose new interfaces, please contact CI Labs at the address
shown in Cross-Platform Consistency and CI Labs.
ΓòÉΓòÉΓòÉ 12.2. The Settings Extension ΓòÉΓòÉΓòÉ
The Properties notebook (see Selection properties) is accessed by the user from
the Document or View menu and displayed by the Info object (ODInfo class). The
information that OpenDoc displays in a part's Properties notebook consists of
the part's Info properties. Info properties are those properties in a part's
storage unit, separate from the part's contents property, that are intended to
be visible to the user. They include properties such as creation date and
modification date (which cannot be changed by the user) as well as name and
part kind (which can be changed by the user).
The Properties notebook provides access to only the standard Info properties
that all OpenDoc parts have. To define and allow access to Info properties
specific to your part editor, you can create a Settings extension to display
your own Properties notebook.
When you select the menu choice to display your part's properties, the ODInfo
object queries your part for an ODSettingsExtension by calling the HasExtension
method with the extension name kODSettingsExtension. If a part provides an
extension, AcquireExtension is called and your parts notebook are displayed. If
your part does not provide one, the standard notebook is displayed.
Your settings extension should be implemented as a subclass of the
ODSettingsExtension class. ODSettingsExtension is itself a subclass of
ODExtension.
ΓòÉΓòÉΓòÉ 12.3. Custom Event Types ΓòÉΓòÉΓòÉ
You can extend OpenDoc's event-dispatching architecture to include new kinds of
events by creating your own dispatch module.
ΓòÉΓòÉΓòÉ 12.3.1. Creating a Dispatch Module ΓòÉΓòÉΓòÉ
The class ODDispatchModule is an abstract superclass. OpenDoc uses instances of
a subclass of ODDispatchModule to dispatch certain types of events (such as
keystroke events) to part editors. For normal program execution, you do not
need to subclass ODDispatchModule or even access the existing dispatch module
objects directly. Your interaction with OpenDoc regarding event dispatching is
mainly through the dispatcher.
You can, however, provide for dispatching of new types of events or messages to
your part editor by subclassing ODDispatchModule. For example, you could create
a dispatch module that handled events from an exotic input device such as a 3D
glove for a virtual reality game.
Patching the dispatcher
It is possible to use custom dispatch modules to patch the functioning of the
dispatcher in relation to all event types, although that is in general not
recommended. In most cases there is no need for such drastic alteration of
OpenDoc functionality.
At run-time the dispatcher maintains a dictionary of installed dispatch
modules, keyed by event type. When the dispatcher's Dispatch method is called,
it looks up the dispatch module for the supplied event type and calls the
Dispatch method of that module.
When the standard OpenDoc dispatch module transforms an event of one type (such
as a mouse-down in the menu bar) into an event of another type (such as an
OpenDoc menu event), it passes the event back to the dispatcher for
redispatching, by calling the dispatcher's Redispatch method. This
redispatching allows your custom dispatch module to patch out the standard
dispatch module for just those transformed events.
If you subclass ODDispatchModule, you need to implement a SOM constructor
(somInit), a SOM destructor (somUninit), and an initialization method. Your
initialization method should call (but not override) the InitDispatchModule
method of ODDispatchModule.
Your dispatch module is responsible for performing the actual dispatching of
events. The dispatcher calls your module's override of the Dispatch method,
passing it the event information.
You install the dispatch module by calling the AddDispatchModule method of the
dispatcher; you remove a dispatch module by calling the RemoveDispatchModule
method of the dispatcher. The installation might occur during the
initialization of your part (or shell plug-in, if you create one).
Your dispatch module should be installed just before your part will be handling
events of the specified type, and removed right after your part is finished
handling events of that type. If you are unable to add your dispatch module
because another dispatch module already exists, you can still handle your
events by doing the following:
Use GetDispatchModule to obtain the address of the existing dispatch
module
Remove the existing dispatch module (using its address)
Add your own dispatch module
Handle your events
Remove your dispatch module
Add the previous dispatch module back
ΓòÉΓòÉΓòÉ 12.3.2. Using a Dispatch Module as a Monitor ΓòÉΓòÉΓòÉ
You can also use a dispatch module as a monitor, in which case it is notified
of events of its kinds but does not have to dispatch them. You might use a
monitor in debugging, for example, to capture all events and display a log of
them in a window.
You install a monitor with the dispatcher's AddMonitor method. For a given
event, the dispatcher calls the Dispatch method for all installed monitors (of
that event type) before calling the Dispatch method of the regular dispatch
module for that event type. The dispatcher ignores the Boolean function result
of the Dispatch method of all monitors; thus, unlike with normal use of a
dispatch module, you can have more than one monitor for a single event type.
ΓòÉΓòÉΓòÉ 12.4. Custom Focus Types ΓòÉΓòÉΓòÉ
You can extend OpenDoc's model for shared-resource arbitration by creating your
own focus module, allowing your parts to recognize and negotiate ownership of
new kinds of focus.
ΓòÉΓòÉΓòÉ 12.4.1. Creating a Focus Module ΓòÉΓòÉΓòÉ
The class ODFocusModule is an abstract superclass. OpenDoc uses instances of a
subclass of ODFocusModule to assign ownership of specific types of focus to
part editors. For normal program execution, you do not need to subclass
ODFocusModule or even access the existing focus module objects directly. Your
interaction with OpenDoc regarding focus ownership is mainly through the
arbitrator.
You can, however, provide for new types of focus ownership, perhaps related to
new types of peripheral devices or new classes of shared resources, by
subclassing ODFocusModule. For example, if you provide an exotic input device
such as a 3D glove for a virtual reality game, you could create a focus module
that tracked the ownership of input from the glove.
You define a new kind of focus to be handled by that focus module by creating
an ISO string that is the name of the focus. As an example, the ISO string that
defines the scrolling focus is "Scrolling"; that and other currently defined
foci are listed in the table shown in Focus Types. Patching the arbitrator
It is possible to use custom focus modules to patch the functioning of the
arbitrator in relation to all types of focus, although that is in general not
recommended. In most cases there is no need for such drastic alteration of
OpenDoc functionality.
If you subclass ODFocusModule, you need to implement a SOM constructor
(somInit), a SOM destructor (somUninit), and an initialization method. Your
initialization method should call (but not override) the InitFocusModule method
of ODFocusModule.
Your focus module is responsible for maintaining the identities of the
individual frames that own the foci that your focus module manages. You must
implement the methods AcquireFocusOwner, SetFocusOwnership,
UnsetFocusOwnership, and TransferFocusOwnership, all called by the arbitrator
to request or change the owner of a focus.
You must also implement the methods BeginRelinquishFocus,
CommitRelinquishFocus, and AbortRelinquishFocus, which your focus module uses
in the two-stage process of relinquishing the ownership of a focus. The
arbitrator calls these methods, and your focus module in turn calls the
equivalent methods of the part that currently owns the focus.
You install a focus module by calling the RegisterFocus method of the
arbitrator; you remove it by calling the UnregisterFocus method of the
arbitrator.
ΓòÉΓòÉΓòÉ 12.4.2. Focus Modules for NonExclusive Foci ΓòÉΓòÉΓòÉ
The arbitrator and focus modules allow foci to be nonexclusive, meaning that a
given focus can have more than one owner. As a possible example, if video input
is provided, a focus module for video focus might track the part or parts that
are currently receiving video input.
If you create a focus module for nonexclusive foci, you must implement methods
for testing exclusivity (IsFocusExclusive), and for allowing a caller to
iterate over the owners of a focus (CreateOwnerIterator).
ΓòÉΓòÉΓòÉ 12.5. Custom Transform Objects ΓòÉΓòÉΓòÉ
OpenDoc transform objects provide powerful transformational capabilities that
are sufficient for most 2-dimensional drawing. With transforms, you can not
only position your graphical objects, but you can also easily scale, rotate,
and skew them. You can combine the operations of frame-internal transforms with
those of facet-external transforms to achieve sophisticated effects with a
minimum of code.
If you need to extend the power of transform objects even further, you can
obtain the extra capability most efficiently by creating your own transform
subclass. If you need to provide for nonlinear transformations (such as curved
projections or sophisticated perspective effects), you can implement them as
new methods and as overrides to the methods of ODBaseTransform, the superclass
of ODTransform.
If you subclass ODBaseTransform, you must override at least the following
methods:
Copy
CopyFrom
GetMatrix (must throw the ODErrTransformErr exception)
GetMATRIXLF (must throw the ODErrTransformErr exception)
HasMatrix (must return kODFalse)
Invert
InvertPoint
InvertShape
PostCompose
PreCompose
ReadFrom
Reset
TransformPoint
TransformPoints
TransformShape
WriteTo
For more information on matrices and transformations in two-dimensional
drawing, you can consult any standard computer-graphics textbook, such as
Computer Graphics Principles and Practice, 2nd ed., by Foley, vanDam, Feiner,
and Hughes (Addison-Wesley, 1990).
ΓòÉΓòÉΓòÉ 12.6. Shell Plug-Ins ΓòÉΓòÉΓòÉ
You can extend the capabilities of the document shell or add session-wide
functionality to OpenDoc by implementing shell plug-ins. Shell plug-ins are
shared libraries, rather than subclasses of ODExtension. A shell plug-in is not
associated with any particular part object.
Your shell plug-in must be installed on the user's machine when a document
first opens, if it is to be used with that document. When called by the
document shell, your plug-in can add its own dispatch modules or focus modules,
add entries to name spaces, or even patch the session-level objects such as the
clipboard, as described later in this chapter.
A shell plug-in has a single exported entry point (its Install function).
Execution of a shell plug-in happens like this:
1. Whenever the user opens an OpenDoc document, OpenDoc launches the
document shell. The document shell initializes itself and the session
object, and gains access to the document's current draft.
2. The document shell then accesses each plug-in library and calls its
Install function. The Install function performs the functions the plug-in
is designed for, and exits.
3. After all plug-ins have executed, the root part of the OpenDoc document
then opens the document window. Your plug-in library needs to implement
only an Install function, with this interface:
void ODShellPlugInInstall(Environment *ev,
ODDraft *draft
ODShellPlugInActionCodes *action);
Your Install function should perform whatever tasks the library was designed
to perform: installing custom focus modules or dispatch modules, patching
session-level objects, or otherwise modifying shell functionality.
ΓòÉΓòÉΓòÉ 12.7. Patching OpenDoc ΓòÉΓòÉΓòÉ
Besides implementing the extension interfaces, dispatch modules, and focus
modules discussed earlier in this chapter, you can enhance or alter the
functioning of OpenDoc in another way. Because OpenDoc is modular and
object-oriented, you can directly replace certain of its objects with your own
versions.
OpenDoc allows you to patch any of its session-level objects. The session-level
objects are those directly referenced by the session object, as shown in the
figure presented in Session Object. They are represented by these OpenDoc
classes:
ODArbitrator
ODInfo
ODSemanticInterface subclass
(The document shell's semantic interface)
ODBinding
ODClipboard
ODDispatcher
ODDragAndDrop
ODLinkManager
ODMessageInterface
ODNameResolver
ODNameSpaceManager
ODStorageSystem
ODTranslation
ODUndo
ODWindowState
This section discusses the mechanics of writing a patch to a session-level
object, but it does not discuss the capabilities of the individual objects or
why you would want to patch one. Before patching any object, be sure you are
very familiar with its purpose and its interface; see information elsewhere in
this book for more information.
ΓòÉΓòÉΓòÉ 12.7.1. Writing a Patch ΓòÉΓòÉΓòÉ
Because your OpenDoc patch replaces a specific object with a known public
interface, you should write it as a subclass of the class of object you are
patching. You must override every method, and your overrides (other than
somInit, somUninit, and your initialization method) should not call their
inherited versions. If you want only partial replacement of OpenDoc's
functionality, delegate to the patched-out object (the one you have replaced)
those methods that you do not wish to change.
Immediately after creating your replacement object, your patch installer should
call the object's initialization method (InitClassName). For example, if you
are replacing the drag-and-drop object, you would call your replacement
object's InitDragAndDrop method immediately after calling new to create it.
Your initialization method should call the GetClassName method of the session
object to get a reference to the current object, and store that reference in a
field. For the drag-and-drop example, your InitDragAndDrop method would call
the session object's GetDragAndDrop method, and store the returned reference in
a field such as fOldDragAndDrop.
At the end of initialization, your InitClassName method should assign the
replacement object as the current object, by calling the SetClassName method of
the session object and passing itself (somSelf) as the new object reference.
Your destructor method (somUninit) should delete the patched-out object (the
one referenced in the fOldDragAndDrop field, for example) before calling
inherited somUninit.
If the object you are replacing has additional entry points besides those based
on the session object's reference to it, you will need to patch those also. For
example, the drag-and-drop object registers callbacks with the Drag Manager;
your replacement object would have to re-register those callbacks to point to
itself.
ΓòÉΓòÉΓòÉ 12.7.2. Installing a Patch ΓòÉΓòÉΓòÉ
Once you have written your patch, you need to install it so that OpenDoc uses
it in place of the original object. Depending on what your patching needs are,
you can place your patch installer in either of two places.
If the scope of your patch needs to be global (applied to all OpenDoc
documents), make your patch installer a Shell plug-in (see Shell
Plug-Ins). Install the patch within your plug-in's Install method.
If the scope of your patch is confined to the document containing your
part, you can use your part editor's Open method to install the patch.
Only the root part of a document can install an OpenDoc patch.
When called, your installer instantiates and initializes the patch object.
Potential patch conflicts
Every time it opens a document, the document shell installs, in order, all
shell plug-ins, and then the root part opens its window. This can lead to
patching conflicts; because you do not control the order in which shell
plug-ins are installed, you cannot ensure that your patch will not itself be
patched out by a subsequently installed patch. Ultimately, the root part
controls which patches remain, because it has the final opportunity to install
its own patches.