Problem: 1615435

Title: (TFileBasedDocuments -Close) Wrong doc closed with same name

Received: Dec 24 1996 11:34AM


There is a bug in closing documents when the documents have the same name (the disk files are in different directories, of course ). This can be reproduced by using the MacApp example application DemoText with the following steps:
  1. Create a document named "Untitled" on the desktop and leave it open.
  2. Create a document on the root of your harddisk named "Untitled" and leave it open and in front.
  3. Close the front window with the close box or the close menu item and thebottom window will close, leaving the front window open.
I traced it as far as I could easily trace it ( until it called AEResolve ), and it appears that the wrong document is found and closed because only the document name is used to specify the document to close instead of using the full filespec.

I haven't found the code that resolves the appleevent, but since TDocuments might not have a file associated with them, maybe have the TFileBasedDocuments add an additional optional parameter added to the appleevent specifying the filespec that could be used to resolve the correct document. I also have NOT checked with the AppleEvent Registry on this, so I'm not sure what it says for closing documents.


There is a bug in TApplication::GetContainedObject which affects all OSA-events trying to access an object within a document or the document itself. The problem affects CloseDocument commands and other AppleScript stuff. The problem is that TApplication::GetContainedObject simply iterates the document list until it finds a document with the name specified in the OSA event (TDocuments are identifed by name). If the user has two documents with the same name, the first document in the document list is used (which is not necessary the frontmost window). Steps to reproduce the problem:
  1. Start DemoText
  2. Type some text and save the document using the name "untitled 2"
  3. Create a new document (you no have two documents named "untitled 2")
  4. Close the new document
Now what you see is that the wrong document is closed. Yeah, thats what the user expects. The fix is to iterate the window-list (front to back) instead of iterating the document list. This way the frontmost document will be found first. Below you will find my solution. I left the original code, because I wanted to handle documents wich don't have a window (just in case). I have cleaned up the original code, too. It now uses an iterator instead of a while loop.:
MScriptableObject* TApplication::GetContainedObject(DescType desiredType,
                                                  DescType selectionForm,
                                                  const CAEDesc& selectionData)
{
        MScriptableObject* result = NULL;
        if (desiredType == cFile)
        {
                // When you script 'file "Macintosh HD:My File"' AppleScript will
                // give the app a pathname and expect a file object in return.
                TFile * resultFile = this->DoMakeFile(cOpen);
                CStr255 thePathName;
                selectionData.GetString(thePathName);
                resultFile->SpecifyWithTrio(0, 0, (CStr63 &)thePathName);
                TOSADispatcher::fgDispatcher->AddTemporaryToken(resultFile);
                result = resultFile;
        }
        else if (desiredType == cDocument && selectionForm == formName)
        {
                CStr255         theDocName;
                selectionData.GetString(theDocName);
                // !!! BUG-FIX !!! First we iterate through the windowlist (front to back)
                CWMgrIterator iter;
                for (WindowRef aWinPtr = iter.FirstWMgrWindow();
                     iter.More(); aWinPtr =
                     iter.NextWMgrWindow())
                {
                        TWindow * aWindow = this->WMgrToWindow(aWinPtr);
                        if ((aWindow) && (aWindow->IsShown())
                          && (!aWindow->fFloats))
                        {
                                TDocument* aDocument = aWindow->fDocument;
                                if (aDocument && (aDocument->GetOMClass() == desiredType)
                                    && (aDocument->fTitle == theDocName))   // Should this be IUEqualString?
                                {
                                        result = aDocument;
                                        break;
                                }
                        }
                }
                if ( ! result)
                {
                        // We might be specifing a document without a window, so scan the document list.
                        CNoGhostDocsIterator    iter(this);
                        for (TDocument* aDocument = iter.FirstDocument();
                             iter.More();
                             aDocument = iter.NextDocument())
                        {
                                if ((aDocument->GetOMClass() == desiredType)
                                    && (aDocument->fTitle == theDocName))
                                   // Should this be IUEqualString?
                                {
                                        result = aDocument;
                                        break;
                                }
                        }
                }
        }
        else
                result = MScriptableObject::GetContainedObject(desiredType, selectionForm, selectionData);
        return result;
}

Another suggestion:

One woraraound is overriding GetSpecifierForm for your TDocument to return formUniqueID or formAbsolutePosition. This way your documents will be identified as "document 3538798" or "document 1" instead of "document \"my Name\"". This should be of no problem since your documents can still be identified by name and existing scripts should still work. Recorded scripts however will always use your new specifier forms and will get problems when executing in a different context (or not work at all ...).


Yet another suggestion:

I have pursued this issue at great length in apple-hi-developers newsgroup with the following conclusions:

a) There is no HI standard for naming windows for identically named (but different) documents. The most complex example is full pathname titles, like used in MPW. The least complex is nothing - wing it when an external event arrives that might be ambiguous. The group's consensus is that SOMETHING ought to be done - what that is is unknown.

b) The MacApp problem is indeed a bug and should be resolved by using formUniqueID for the appleevent that closes the window/document, not formName. This will not resolve ambiguities like a script that says 'close document whose name is "DocName"' or 'close document "DocName"' when there are two documents whose name is "DocName".

c) There IS a convention used fairly widely in the Finder, Clipping Extension, PC Exchange, MacLinkPlus, and in naming untitled documents - append a monotonically increasing (like that?) integer number to the name of the second and later items, never rename a window once that's done (ie: Don't rename "untitled 2" when "untitled" is closed).

d) A number of proposals on the newsgroup are interesting - the one I like best (and is probably hardest to implement) is one which titles the documents (retitling the first opened if necessary) to reflect the location of the original documents' containers. So, if there are two "Letter to Mary" documents at HD:folder1:Doc and HD:folder2:Doc, the window titles are "Letter to Mary (in folder1)" and "Letter to Mary (in folder2)". This gets complicated when the documents are on different disks (ie: "Letter to Mary (on Floppy Disk)") or on different machines on a network (ie: "Letter to Mary (on Mac3@zone7)"). Yikes! For now, I'd just settle with the appropriate fix to MacApp so that when you open two identically named documents and click in the close box of the topmost one, the backmost one isn't the one that closes!


Fix:
UDocument.h/cp:
  TDocument::GetSpecifierForm - removed so that the default behavior provided in 
  MScriptableObject::GetSpecifierForm (formAbsolutePosition) is used.