lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Child documents [Was: Adding open files to MDI "Window" menu]


From: Greg Chicares
Subject: Re: [lmi] Child documents [Was: Adding open files to MDI "Window" menu]
Date: Sun, 15 Mar 2009 19:25:03 +0000
User-agent: Thunderbird 2.0.0.19 (Windows/20081209)

On 2009-01-30 22:57Z, Vadim Zeitlin wrote:
[...]
> On Thu, 20 Mar 2008 22:30:40 +0000 Greg Chicares <address@hidden> wrote:
> 
> GC> All I want is "if your parent closes, then you close" semantics,
> GC> with the loosest possible physical coupling. Here's an untested
> GC> sketch of how this might be implemented. (It changes the ABI,
> GC> which rules it out for the current wx version.)
> 
>  I don't think this can be implemented in any reasonable way without
> breaking ABI and it also seems a kind of a new feature which belongs to the
> trunk [first] so for now I've implemented it mostly following your sketch.

I don't see it in snapshot 'wxWidgets-2009-03-12.tar.bz2'. Are snapshots
made from a release branch that differs from svn trunk?

> GC> Class wxDocument already has this member:
> GC> 
> GC>     wxDocument* m_documentParent;
> GC> 
> GC> which AFAICT is used nowhere yet.
> 
>  In fact I initially wanted to implement the entire child document concept
> at LMI level as it seems to be rather specific to it and there be no
> particular problems with doing it outside of wx. But the presence of this
> m_documentParent made me change my mind as, as long as we have it anyhow,
> we'd better use it in at least some way. And it also seems to mean that
> someone else at least planned to do something similar so it's not something
> which only LMI risks to ever use.

Ted Neward's _Advanced OWL 5.0_, ISBN 1-884777-46-5, contains the only
discussion I've ever seen of OWL's child document idea, which is where
I originally got this lmi idea. He calls this "one of the most underused
features in OWL". Comparing that to a full embedding concept (like ole
or opendoc), he says "Compound documents in OWL are far more limiting.
They simply group TDocument objects in a hierarchy, cascading operations
on a parent document to, in turn, be called upon by each of the children,
which implies that wxDocument::Save[As]() would save child documents
(as the analogous OWL TDocument::Commit() indeed does).

Compared to that "far more limiting" concept, what lmi uses is still
more limited, because it omits the Save[As]() part: all that remains is
to "simply group TDocument [wxDocument] objects in a hierarchy" and to
cascade only one operation, i.e., closing.

Suppose you load a document in a word processor, and issue a print-
preview command. A "view" is created. If you close the document,
then the view presumably vanishes. If you change the document, then
you can't reproduce exactly the same view. It's an ephemeral view
that's thrown away if you don't print it immediately. That's the
kind of thing I'm talking about.

> GC> I would propose adding
> GC> 
> GC>     wxList<wxDocument> m_documentChildren;
> GC> 
> GC> and changing three member functions as follows:
> GC> 
> GC> wxDocument::wxDocument()
> GC>   // This is needed only for closing and deleting later.
> GC>   if(m_documentParent)
> GC>     m_documentParent->m_documentChildren->Insert(this);
> GC> 
> GC> wxDocument::~wxDocument()
> GC>   if(m_documentParent) {
> GC>     m_documentParent->m_documentChildren->DeleteContents(true);
> GC>     // Maybe DeleteAllViews should be called on each child?
> GC>     m_documentParent->m_documentChildren->Clear();
> GC>     }
> GC> 
> GC> wxDocument::Close()
> GC>   for each element 'e' of m_documentChildren
> GC>     do e->Close() /* or e->DeleteAllViews() ? */
> 
>  I've done this but modified Close() to call OnSaveModified() for all the
> children and then effectively close them all in OnCloseDocument() as it
> seems to be better: if any child refuses to close, nothing is done at all
> whereas with the approach above some children would have been already
> closed.

Following this idea, my patch does the work in OnCloseDocument().

>  You may wonder why didn't I change OnSaveModified() itself to call the
> same method for all child documents. In fact I'm still not sure if this
> wouldn't be the right thing to do but I hesitate to do it because
> OnSaveModified() is also called from OnNewDocument()

However, that has now changed:
  http://trac.wxwidgets.org/changeset/59449

> and it doesn't seem
> clear what, if anything, should be done with the child documents when a
> new parent document is (re)created.

I suspect you're thinking of a full embedded-document concept.
What I'm looking for is a lot less than that. Only the parent
document is real. Once it closes, its children vanish, as though
they had never existed.

> GC> and adding just one new function:
> GC> 
> GC>   wxDocument* wxDocument::GetParent() const {
> GC>     return m_documentParent;
> GC>     }
> 
>  I called it GetDocumentParent() for consistency with the other methods.
> 
> GC> That's all there is to it, AFAICS.
> 
>  It is not totally clear yet how should opening/saving the child documents
> work. Right now I didn't constrain them at all but I'm not sure if this is
> really correct. To try to see if it is, I'd like to better understand the
> only use-case which we currently have, namely the use of child documents in
> LMI so let me rephrase your explanation of the consolidated view of the
> census files to check if I understood it correctly:

These lmi children don't exist in any way that permits persistence
apart from their parent. They can neither be saved nor loaded
independently: they can only use their parent's storage.

> ---------------------------------------------------------------------------
>  The consolidated view is not a view of CensusDocument because it doesn't
> change when the document changes. In fact there can be several such views
> corresponding to several versions of CensusDocument open at the same time.
> So they must correspond to their own EphemeralCensusDocument and not the
> main CensusDocument. EphemeralCensusDocument can't be saved because it's
> always subordinate to an existing CensusDocument (why does it have to be so
> though?) and hence it can't be opened neither because there are no files
> containing it.
> ---------------------------------------------------------------------------
> 
>  Is this right?

Yes. (For insurance-company readers, this "consolidated view" is
commonly referred to as a "composite illustration", BTW.)

>  Supposing it is, I think the child document concept does make sense for
> them. But I also wonder if we shouldn't explicitly support documents which
> can't be opened/saved and even if we shouldn't consider that all child
> documents like this. The latter seems to make sense because if you can open
> a child document from a file then you shouldn't close it when the parent
> document closes, i.e. it's a contradiction and the only solution is to not
> make any documents corresponding to disk files child documents at all.
> 
>  Also, while I think that it may be possible to modify the child documents
> (although it isn't in LMI case it might in the example of an image embedded
> in a word processor), it still doesn't make sense to save them to disk
> files. Instead all changes to them (if allowed at all) should be reflected
> in their parent document.
> 
>  Do you agree with this?

Yes.

Let me try to explain it in another way.

Every CensusDocument can be saved as a '.cns' file.

An IllustrationDocument can be saved as an '.ill' file,
iff it's not a child of a CensusDocument.

Compare the xml for these two file formats, e.g., 'sample.ill' and
'sample.cns' in lmi HEAD.

'.ill':
  <single_cell_document>
    <cell>...</cell>
  </single_cell_document>

'.cns':
  <multiple_cell_document>
    <cell>...</cell> <!-- exactly one for 'case' default values -->
    <cell>...</cell> <!-- one for each set of 'class' defaults -->
    <cell>...</cell> <!-- one for each individual 'cell' -->
  </multiple_cell_document>

Suppose each 'cell' contains exactly two elements, name and country.
You could have an '.ill' file containing only
  <single_cell_document>
    <cell>
      <name>Vadim</name>
      <country>FR</country>
    </cell>
  </single_cell_document>
and the program might produce output like "Vadim lives in France."
That's the easy part.

Here's the more complex part. Where
  '.ill' or <single_cell_document> is a single-element container of <cell>,
  '.cns' or <multiple_cell_document> is a multiple-element <cell> container.
Perhaps we could have, or should have, made <multiple_cell_document> a
container of <single_cell_document>, but we didn't--probably just because
we know we don't want a '.cns' file to be a collection of pointers to
distinct actual '.ill' files.

And a <multiple_cell_document> contains the 'case' and 'class' defaults
mentioned above, too. They're just <cell>s like any other in form, but
they don't correspond to any person. Suppose we have:
  <multiple_cell_document>
    <cell> <!-- 'case' default parameters -->
      <name>INSERT NAME HERE</name>
      <country>US</country>
    </cell>
    <cell>...</cell> <!-- 'class' default parameters -->
    <cell>
      <name>Vadim</name>
      <country>FR</country>
    </cell>
    <cell>
      <name>Greg</name>
      <country>US</country>
    </cell>
  </multiple_cell_document>
If you open that '.cns' file, you'll see only two actual people on
the screen (in the wxView, that is). The "defaults" represent no
actual person, though they have the same <cell> data structure.
If you do "Census | Add cell", then a new cell is inserted in the
container, after the last person (here, "Greg"). The new cell's
parameters are copied from the 'case' default. If you edit that
new cell with "Census | Edit cell", you'll see "INSERT NAME HERE"
in the field for the person's name, which we hope will prompt you
to enter an actual name; and by default the person will reside in
the US (appropriate if most of the individuals you must deal with
live there--otherwise you can change that default, e.g., so that
any new person you add thereafter resides in UZ instead).

Now, if you're working with an '.ill' file, you can save it and
load it, and produce an "illustration" from it. It's enough for
our present purposes to define an illustration as this string:
  "[name] lives in [country]."
Thus: "Greg lives in the US."

But if you're working with a '.cns' file, you can save or load it
(the whole collection of <cell>), and you can produce individual
illustrations from it. In the example above, you could produce:
  "Vadim lives in France."
or
  "Greg lives in the US."
And there's another kind of output you can produce:
  "Vadim lives in France, and Greg lives in the US."
which consolidates various cells into a "composite illustration".
But you can do these things only for actual people shown in the
"census" view (individually, or consolidated all together)--you
cannot produce a "case-default illustration":
  "INSERT NAME HERE lives in the US." // No, can't do this.

I won't explain in detail what 'class' defaults are, because
they're almost the same as the 'case' default: simply values with
which the container can be extended, much as resizing a std::vector
appends zeros even if the original vector contained no nonzero
element.

A '.cns' is somewhat like a "compound document", but I think that
term generally refers to a collection whose constituent documents
can be saved independently--like a collection of pointers to objects
that have an independent existence. OTOH, a '.cns' is perhaps more
like a collection that holds objects by value: you can think of its
<cell> elements as corresponding to "virtual" wxDocument objects,
but you cannot instantiate any such object.

And indeed the consolidated "composite" view cannot correspond to
any single document that could possibly exist. An '.ill' file
(a <single_cell_document>) contains only one <cell>, with only
one <country> element; such a thing can never produce output like
  "Vadim lives in France, and Greg lives in the US."
which mentions two countries.

If I move to CA, say, then the '.cns' document above would change:
     <cell>
       <name>Greg</name>
-      <country>US</country>
+      <country>CA</country>
     </cell>
and you could save it now and reload it later. But you can't produce
an '.ill' file from it. There's no particular reason why you can't,
and technically it would certainly be possible, but in the problem
domain that doesn't appear useful. All persistent storage is in the
'.cns' file. Only the '.cns' file can be edited, saved, and loaded.
And in the "composite" case, it's not even possible to produce a
corresponding '.ill' file, or even to conceive of one's existence,
because no single <cell> can incorporate two countries of residence.

So why have we got these two things (which I'd like to deprecate):
  bool IllustrationDocument::is_phony_
and
  enum LMI_WX_CHILD_DOCUMENT
? Because:
 - we need to make a wxView, specifically an IllustrationView, from
     the "Vadim" cell in the above document;
 - I have a belief (shatter it if appropriate) that I can't have a
     wxView without a wxDocument of some sort;
 - that wxDocument must not be saved (and cannot be loaded) as though
     it were a first-class IllustrationDocument instance: it's merely
     a formalistic dummy document.
I'd like to represent that this way:
  NULL == parent_document --> real  <cell> from an '.ill' file
  NULL != parent_document --> dummy <cell> from an '.cns' file
(not that this is necessarily the best ideal, but it's what the
legacy system did). But I can't, or couldn't when lmi migrated to wx
in 2005, or at least didn't realize that I could; so here's what I
did for a dummy <cell> from a '.cns' file:
  set LMI_WX_CHILD_DOCUMENT as a creation flag;
  upon creation with LMI_WX_CHILD_DOCUMENT, set the is_phony_ member;
  use is_phony_ to inhibit commands for editing and saving.
That command inhibition is described here:
  https://savannah.nongnu.org/support/?104430
And I also want to close "phony" views when their parent closes:
  https://savannah.nongnu.org/support/?104485
which is what we're discussing directly here. But the two are really
different aspects of the same thing.

Compound documents may be...
  http://en.wikipedia.org/wiki/OpenDoc
| an oversold concept
...which are useful for embedding graphics only, while...
| beyond that it seems there aren't all that many other examples
So maybe they're a solution in search of a problem. It just so
happens that a limited use of the "limited" case described above
for OWL seemed to be a good solution to this lmi problem. However,
that doesn't mean that no other solution can exist. That's why,
here, today, I've tried to explain the real underlying issues that
I seek to resolve for lmi, instead of supposing that compound or
hierarchical documents are a firm requirement that must be
implemented per se.





reply via email to

[Prev in Thread] Current Thread [Next in Thread]