高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

spacer image


Technote 1085

Using the Drag Manager to Interact with and Manipulate File System Entities


目次

flavorTypeHFSflavorTypePromiseHFS について

flavorTypeHFS の使い方

flavorTypeHFS の送信

Finder を使ったコピーのバグ

flavorTypeHFS の受信

flavorTypePromiseHFS の使い方

flavorTypePromiseHFS の送信

ファイルの作成

ファイル書き込みの延期

flavorTypePromiseHFS の受信

ファイル検索を使ったコピー

ファイル検索をめぐる問題

要約

付録

Drag Manager は、ファイルシステムのエンティティを操作するために 2 つのデータフレーバー (Data Flavor) を定義しています。『The Drag Manager Programmer's Guide』にもこれらのフレーバーの説明はありますが、それらの使い方を完全に理解するためには十分詳しい説明とはいえません。

この TECHNOTE は、開発中のアプリケーションに Drag Manager を介したファイルシステムエンティティの操作を「組み込もう」としているデベロッパ (あるいは、これまでに組み込んだ経験のあるデベロッパ) には参考になるはずです。

この TECHNOTE では、読者が『The Drag Manager Programmer's Guide』で説明されている内容に習熟していて、特に、2-36 および 2-37 ページを読み、DragSendDataProc の処理を十分に理解していることを前提にしています。なお、DragSendDataProc の詳しい説明は、2-72 ページの最後から始まっています。また、AppleEvent Manager のデータ構造体である AEDesc に習熟していることも前提になります (AppleEvent Manager は、『Inside Macintosh: Interapplication Communication』の第 3 章で詳しく説明されています)。さらに最後になりますが、File Manager 呼び出しの PBGetCatInfo に習熟していれば、なお一層望ましいといえます。

この TECHNOTE にあげたすべてのコード、FinderDragPro の Metrowerks Project、および『The Drag Manager Programmer's Guide』は、ここでそれぞれの項目をクリックするか、この TECHONOTE の最後にある「ダウンロード」セクションの適切なアイコンをクリックしてダウンロードすることができます。


flavorTypeHFS と flavorTypePromiseHFS について
ファイルシステムのエンティティを操作するため、flavorTypeHFS および flavorTypePromiseHFS という 2 つのデータフレーバーが用意されています。名前は似ていますが、これらの働きはまったく異なります。最も大きな違いは、一方ではファイルが存在していて、もう一方ではファイルがまだ存在していないということです。

既存のファイルを参照する flavorTypeHFS データを DragReference の中に置くということは、「ドラッグの受信側 (Drag Receiver) が関心を持つ既存のファイル (自分で作成したかどうかに関係なく) を知っている」と発言するようなものです。一方、flavorTypePromiseHFS データを DragReference に置くということは、「誰か (ドラッグの受信側) が保存場所を指定してくれれば、すぐに新しいファイルを作成する用意がある」と発言することに似ています。

注意:
Drag Manager には、DragReference にデータを与えることを「約束」するという概念があります。ただし、このことを flavorTypePromiseHFS と混同しないでください。これら 2 つの約束は異なり、DragReferenceflavorTypeHFS データを与えると約束することと、flavorTypePromiseHFS とは何の関係もありません。

このような混同が特によく起こるのは、アプリケーションが DragReferenceflavorTypePromiseHFS データを与えることを約束するときです。この場合、約束したデータは、さらに、受信側のアプリケーションに新しく作成されたファイルを参照するデータを供給することを約束することになります。つまり、間接的な約束の約束ということになります。

この TECHNOTE では、できるかぎり「約束」という用語を同時に複数の意味で使わないようにします。それでも、十分に注意して本文の内容を読んでください。


重要:
Drag Manager のフレーバーデータ (Flavor Data) は慣例化したものにすぎません。つまり、送信側 (Sender) と受信側 (Receiver) がこのデータを正しく使用できるように何らかの API が用意されているわけではないという意味です。これらのフレーバーを厳密にインプリメントするには常に十分な注意が必要です。

実際の状況はなお一層悪いともいえます。この書類は、Drag Manager のリリースから数年後に執筆されました。その結果、Drag Manager を早い時期に採用したデベロッパがこれらのフレーバーを適切にインプリメントすることは困難でした。しかも、この TECHNOTE の読者が開発したアプリケーションほど良心的に開発されていないアプリケーションも存在します。このため、返されるエラー値のチェックには細心の注意を払い、開発するアプリケーションのコードの中にアサーションを組み込むようにしてください。そうすれば、この TECHNOTE で説明する慣習から予想に反して逸脱する他のアプリケーションにも対処できる準備が整います。

flavorTypeHFS の使い方
理論的には、flavorTypeHFS データの使い方は単純に見えますが、実際には、あらかじめ知っておく必要のあるいくつかのトリックが存在します。ここでは、これらのトリックのいくつかについて説明します。参考のため、<Drag.h> で定義されている HFSFlavor 宣言のコピーを次に示します。
struct HFSFlavor
{
     OSType fileType;        // ファイルタイプ
     OSType fileCreator;     // ファイルクリエータ
     unsigned short fdFlags; // Finder フラグ
     FSSpec fileSpec;        // ファイルシステムの spec
};

typedef struct HFSFlavor HFSFlavor;

flavorTypeHFS の送信
flavorTypeHFS データを含むドラッグを開始するには、まず HFSFlavor レコードを宣言する必要があります。このレコードには、FSSpec と、潜在的なドラッグの受信側が FSpGetFInfo を呼び出すのを避けるための、その他いくつかのフィールドが含まれています。

次のステップとして、fileSpec フィールドを適切に初期化して、データがファイルを参照するかどうかを決定します。ファイルを参照する場合は、ファイルに対する情報に合わせて、fileTypefileCreator、および fdFlags の各フィールドを単純にセットします。flavorTypeHFS データがディレクトリまたはボリュームを参照する場合は、表 1 を参考にして、HFSFlavor レコードの fileType および fileCreator フィールドをセットします。

表 1 HFSFlavor レコードの fileType および fileCreator フィールド
エンティティのタイプfileCreatorfileType
ディレクトリ(フォルダ)MACSfold
ボリューム(ディスク)MACSdisk


これらの値は、ファイル以外のものを取り扱う潜在的なドラッグの受信側へのヒントとなります。これらは、アプリケーションのバンドルリソースの中で使用し、アプリケーションがそのアイコン上にドロップされたフォルダやディスクを受け入れることを Finder に認識させる値と同じものです。

コード #1: fileCreator および fileType フィールドをセットする方法を決定する
pascal OSErr MakeHFSFlavor
       (short vRefNum, long dirID, ConstStr255Param path,
        HFSFlavor register *hfsFlavorP)
{
    OSErr err = noErr;

    if (!(err = FSMakeFSSpec
                (vRefNum, dirID, path, &(hfsFlavorP->fileSpec))))
    {
        CInfoPBPtr cipbp = (CInfoPBPtr) NewPtrClear (sizeof (*cipbp));
        if (!(err = MemError ( )))
        {
            cipbp->hFileInfo.ioVRefNum = hfsFlavorP->fileSpec.vRefNum;
            cipbp->hFileInfo.ioDirID = hfsFlavorP->fileSpec.parID;
            cipbp->hFileInfo.ioNamePtr = hfsFlavorP->fileSpec.name;

            if (!(err = PBGetCatInfoSync (cipbp)))
            {
                hfsFlavorP->fdFlags = cipbp->hFileInfo.ioFlFndrInfo.fdFlags;

                if (hfsFlavorP->fileSpec.parID == fsRtParID)
                {
                    hfsFlavorP->fileCreator = 'MACS';
                    hfsFlavorP->fileType = 'disk';
                }
                else if (cipbp->hFileInfo.ioFlAttrib & ioDirMask)
                {
                    hfsFlavorP->fileCreator = 'MACS';
                    hfsFlavorP->fileType = 'fold';
                }
                else
                {
                hfsFlavorP->fileCreator = cipbp->hFileInfo.ioFlFndrInfo.fdCreator;
                hfsFlavorP->fileType = cipbp->hFileInfo.ioFlFndrInfo.fdType;
                }
            }

            DisposePtr ((Ptr) cipbp);
            if (!err) err = MemError ( );
        }
    }

    return err;
}

Finder を使ったコピーのバグ
アプリケーションから Finder に flavorTypeHFS データをドラッグするということは、常に起こりうることが想定されます。しかし、Finder のバグにより、大部分のアプリケーションではこの機能を正常に使用できません。 Finder の側から見ると、flavorTypeHFS データの受信には、次のような 2 つのケースがありえます。この場合、2 番目のケースの方がより興味深いといえます。

  1. ドロップ位置が flavorTypeHFS データと同じボリューム上にあるとき、Finder はファイルを単純にドロップ位置に移動します。
  2. ドロップ位置が異なるボリューム上にあるとき、Finder はファイルを新しいボリュームにコピーする必要があります。

Finder は AppleEvent に対応しています。このため、Finder は自分自身に AppleEvent を送信し、ファイルの進捗状況を示すウィンドウの表示など、あらゆる種類の動作を実行するように自分自身に命令します。しかし、Finder のドラッグ受信コードは、誤って、これらの特殊な AppleEvent をカレントプロセスではなくフロントプロセスに送信してしまいます。フロントプロセスは一般にドラッグを開始するアプリケーションです。アプリケーションは、これらのイベントに対応するハンドラを持たないため、AppleEvent Manager は Finder の AESend 呼び出しにエラーを返し、Finder は処理全体をキャンセルしてしまいます。

このバグがフィックスされるまで、アプリケーションでは、これらの AppleEvent を何とか「処理」して、こうした問題の発生を避けることができます。Finder のバグがフィックスされたシステムでは、このハンドラはアプリケーション内で冬眠状態になります。というのも、AppleEvent はアプリケーションではなく Finder に送られるようになるためです。残念なことに、現在のところ、これらのイベントを Finder に「反映」させることはできません。しかし、Apple ではそうなるように努力を続けています。これは、進捗状況の表示ダイアログを使うことができないという意味です。しかし、致命的な障害が発生するよりはましと考えるしかありません。

コード #2: Finder から偽の AppleEvent を受信する
pascal OSErr BogusFinderEventHandler(const AppleEvent *, AppleEvent *, long)
{
    return noErr; // そのイタズラ者をフロアに落っことして (ドロップして) みる
}

pascal OSErr InstallBogusFinderEventHandler (void)
{
    OSErr err = noErr;

    static AEEventHandlerUPP bogusFinderEventHandlerUPP;

    if (!bogusFinderEventHandlerUPP)
    {
        bogusFinderEventHandlerUPP = NewAEEventHandlerProc (BogusFinderEventHandler);

        if (!bogusFinderEventHandlerUPP)
            err = nilHandleErr;
        else
        {
            err = AEInstallEventHandler
            ('cwin', '****', bogusFinderEventHandlerUPP, 0, false);

            if (err)
            {
                DisposeRoutineDescriptor (bogusFinderEventHandlerUPP);
                bogusFinderEventHandlerUPP = nil;
            }
        }
    }

    return err;
}

flavorTypeHFS の受信
flavorTypeHFS の受信 (しばしば Finder から) は、データの他のフレーバーの受信とほぼ同じです。ただし、アプリケーションの中には、不完全なレコードを与えるものがあるため注意が必要です。これらのアプリケーションでは、HFSFlavor レコードの fileSpec フィールドに含まれる名前フィールドの末尾にある未使用バイトを供給しません (これは送信側のアプリケーションのバグですが、それが他社のアプリケーションである場合、そのバグをフィックスすることはほとんど不可能です)。

コード #3: FSSpec の最小バイトを計算する
(コード #4、#6、および #14 から呼び出される関数)


static pascal Size MinimumBytesForFSSpec (const FSSpec *fss)
{
    // 呼び出し元ではメモリを移動しないことを前提にしている
    return sizeof (*fss) - sizeof (fss->name) + *(fss->name) + 1;
}


コード #4: flavorTypeHFS データを抽出する
pascal OSErr GetHFSFlavorFromDragReference
             (DragReference dragRef, ItemReference itemRef, HFSFlavor *hfsFlavor)
{
    OSErr err = noErr;

    Size size = sizeof (*hfsFlavor);
    err = GetFlavorData
    (dragRef, itemRef, flavorTypeHFS, hfsFlavor, &size, 0);

    if (!err)
    {
        Size minSize = sizeof (*hfsFlavor) - sizeof (hfsFlavor->fileSpec);
        minSize += MinimumBytesForFSSpec (&(hfsFlavor->fileSpec));
        // MinimumBytesForFSSpec については、コード #3 を参照
        if (size < minSize)
            err = cantGetFlavorErr;
    }

    return err;
}

flavorTypePromiseHFS の使い方
flavorTypePromiseHFS データの使い方は、flavorTypeHFS データの使い方に比べるとかなり複雑です。こうした複雑さは、主として、flavorTypePromiseHFS データが複数の部分から構成されているということに起因しています。参考のために、<Drag.h> で定義されている PromiseHFSFlavor 宣言のコピーを次に示します。

struct PromiseHFSFlavor
{
    OSType fileType;           // ファイルタイプ
    OSType fileCreator;        // ファイルクリエータ
    unsigned short fdFlags;    // Finder フラグ
    FlavorType promisedFlavor; // 約束したフレーバー
};

typedef struct PromiseHFSFlavor PromiseHFSFlavor;

flavorTypePromiseHFS の送信

ファイル作成の約束
TrackDrag を呼び出す前に、アプリケーションでは、データの各部分について 1 度ずつ、その都度同じ ItemReference値を渡して、AddDragItemFlavor を 2 度呼び出す必要があります。

最初の呼び出しでは、PromiseHFSFlavor 型のレコードを宣言し、promisedFlavor フィールドに 'fssP' (0x66737350) を設定します。『The Drag Manager Programmer's Guide』では、promisedFlavor に任意の値を設定すると書かれていますが、ここに示した値を指定することをお勧めします (詳細については後述します。アプリケーションが既に他の値を使っていても、それが 'rWm1' でなければ特に問題はありません)。PromiseHFSFlavor レコードの他のフィールドを適切な値で埋め、FlavorType パラメータに flavorTypePromiseHFS を渡して、レコードを DragReference に追加します。

2 度目の AddDragItemFlavor 呼び出しでは、FlavorType パラメータに 'fssP' を渡します。dataPtr および dataSize パラメータには 0 を渡し、後で守る約束をセットアップします。

コード #5: flavorTypePromiseHFS データを追加する
pascal OSErr AddDragItemFlavorTypePromiseHFS
             (DragReference dragRef, ItemReference itemRef,
              OSType fileType, OSType fileCreator,
              UInt16 fdFlags, FlavorType promisedFlavor)
{
    OSErr err = noErr;

    PromiseHFSFlavor phfs;

    phfs.fileType = fileType;
    phfs.fileCreator = fileCreator;
    phfs.fdFlags = fdFlags;
    phfs.promisedFlavor = promisedFlavor;

    if (!(err = AddDragItemFlavor
                (dragRef, itemRef, flavorTypePromiseHFS,
                 &phfs, sizeof(phfs), flavorNotSaved)))
    {
        err = AddDragItemFlavor
        (dragRef, itemRef, promisedFlavor, nil, 0, flavorNotSaved);
    }

    return err;
}


重要:
Finder の一部のバージョンにはバグがあるため、アプリケーションでは、どのフレーバーデータよりも前に flavorTypePromiseHFS フレーバーデータを追加し、そのすぐ後に promisedFlavor フィールドに対するフレーバーデータを追加する必要があります。アプリケーションがこれらのフレーバーをこの順序で追加しないと、Finder はそのファイルのアイコンを正しい位置に表示できなくなります。


注意:
アプリケーションが SetDragSendDataProc を呼び出して、DragSendDataProcDragReference にまだアタッチしていない場合は、この機能を追加する必要があります。


この DragReference に提供する必要のあるその他のフレーバーを追加すれば、TrackDrag を呼び出す準備が整います。

約束の遵守
Drag Manager が flavorTypePromiseHFS データの promisedFlavor フィールドに等しい FlavorType をリクエストするとき、約束したファイルを引き渡すことによって約束を守るのはデベロッパの役割です。約束を守るということの中には、ドラッグの受信側が最終的にファイルを必要とする場所を見つけたり、ファイルを作成する場所を決定すること、さらにファイルそのものを作成することが含まれます。これは、DragReference に関連づけられた DragSendDataProc の中で行うことになります。

ドロップ位置の取得
まず第 1 に、DragSendDataProc はドラッグの受信側がファイルを必要とする場所を見つける必要があります。このためには、GetDropLocation を呼び出します。この呼び出しは AEDesc レコードを作成します。このレコードに含まれるデータの型は、ドラッグの受信側によって定義されます。たとえば、Finder は typeAlias データをドロップ位置に置きます。このデータを FSSpec に変換するには、その型を強制的に typeFSS にして、返される記述子に FSSpec データをコピーします。

コード #6: ドロップフォルダを抽出する
pascal OSErr GetDropDirectory (DragReference dragRef, FSSpecPtr fssOut)
{
    OSErr err = noErr;

    AEDesc dropLocAlias = { typeNull, nil };

    if (!(err = GetDropLocation (dragRef, &dropLocAlias)))
    {
        if (dropLocAlias.descriptorType != typeAlias)
            err = paramErr;
        else
        {
            AEDesc dropLocFSS = { typeNull, nil };
            if (!(err = AECoerceDesc(&dropLocAlias, typeFSS, &dropLocFSS)))
            {
                // MinimumBytesForFSSpec はメモリを移動しないと仮定する
                FSSpecPtr fss = (FSSpecPtr) *(dropLocFSS.dataHandle);
                BlockMoveData (fss, fssOut, MinimumBytesForFSSpec(fss));
                // MinimumBytesForFSSpec については、コード #3 を参照
                err = AEDisposeDesc (&dropLocFSS);
            }
        }

        if (dropLocAlias.dataHandle)
        {
            OSErr err2 = AEDisposeDesc (&dropLocAlias);
            if (!err) err = err2;
        }
    }

    return err;
}


注意:
FSSpec データはディレクトリを記述します。つまり、ファイルの作成に使うことのできる FSSpec ではありません。作成しようとするファイルに対するディレクトリ ID を取得するには、「付録 C」の関数のように PBGetCatInfo を使います。


ドロップ位置のデータが typeAlias でない場合、AECoerceDesc 呼び出しは正常に終了しません。おそらくこの状況では、DragSendDataProc はデータを提供せず、エラーを返します。しかし、Finder 以外のアプリケーションは自由に typeAlias のドロップ位置を提供できる点に注意してください (しかも一部のアプリケーションは実際にそれを行います)。このため、typeAlias は、Finder がドロップの受信側であることを示していると、無条件に判断しないでください。

注意:
現在 Finder には、flavorTypePromiseHFS をドロップする場所の決定に関連するいくつかのバグがあります。フォルダのエイリアス、ゴミ箱のエイリアス、および PromiseHFSFlavor レコードに示されたファイルタイプを受け入れるアプリケーションは、あたかもドラッグを受け入れるかのようにハイライト表示されます。しかし、マウスボタンを離すとき、これらはドラッグを拒絶します。後者の場合 (アプリケーション)、ドロップ位置はアプリケーションファイルそのもののエイリアスになります。この問題に対処するうまい方法はありません。


注意:
ドロップ位置によって指定したボリュームとは異なるボリュームにファイルを作成しないでください。Finder はこのファイルをドロップ位置にコピーしません。

ファイルの作成
ファイルを置く場所が決定したら、次のような関数を呼び出してファイルを作成することができます。

コード #7: 約束したファイルまたはフォルダを作成する
pascal OSErr CreatePromisedFileOrFolder
             (const PromiseHFSFlavor *phfs, const FSSpec *fss, ScriptCode scriptTag)
{
    OSErr err = noErr;

    if (phfs->promisedFlavor == kPromisedFlavorFindFile)
        err = paramErr;
    else if (phfs->fileType == 'disk')
        err = paramErr;
    else if (phfs->fileType == 'fold')
        err = CreatePromisedFolder (phfs, fss, scriptTag); // コード #9 を参照
    else
        err = CreatePromisedFile (phfs, fss, scriptTag);   // コード #8 を参照

    return err;
}


コード #8: コード #7 によって呼び出される関数
static pascal CreatePromisedFile
              (const PromiseHFSFlavor *phfs, const FSSpec *fss, ScriptCode scriptTag)
{
    OSErr err = noErr;

    if (!(err = FSpCreate(fss, phfs->fileCreator, phfs->fileType,scriptTag)))
    {
        if (phfs->fdFlags)
        {
            FInfo finderInfo;

            if (!(err = FSpGetFInfo (fss, &finderInfo)))
            {
                finderInfo.fdFlags = phfs->fdFlags;
                err = FSpSetFInfo (fss, &finderInfo);
            }
        }
    }

    return err;
}


コード #9: コード #7 によって呼び出される関数
static pascal CreatePromisedFolder
              (const PromiseHFSFlavor *phfs, const FSSpec *fss, ScriptCode scriptTag)
{
    OSErr err = noErr;

    long newDirID; // スタートライン
    if (!(err = FSpDirCreate (fss, scriptTag, &newDirID)))
    {
        if (phfs->fdFlags)
        {
            DInfo finderInfo;

            // FSpGetDInfo と FSpSetDInfo については、付録 Bを参照

            if (!(err = FSpGetDInfo (fss, &finderInfo)))
            {
                finderInfo.frFlags = phfs->fdFlags;
                err = FSpSetDInfo (fss, &finderInfo);
            }
        }
    }

    return err;
}

ファイル書き込みの延期
ファイルを作成したら、その内容を DragSendDataProc に書き込むかどうかを決定します。ファイルのサイズが大きかったり、アプリケーションがその後でファイル内のデータを生成する必要があるときは、ファイルの書き込みを延期させます。Drag Manager のコールバック中はコンテキストスイッチが無効になっているため、仮にファイルの書き込みに時間を費やしたとしても、他のアプリケーションは実行時間を取得できません。たとえ、WaitNextEvent の周期的な呼び出しが安全だとしても (実際には安全ではありません)、同様です。

このような状況では、DragSendDataProc でファイルを開き、それを開いたままにしておきます。さらに、内部ステータス変数をセットして、ファイルの書き込みを必要とするアプリケーションの他の部分にもそのことを伝えます。TrackDrag が戻り値を返した後で、WaitNextEvent を周期的に呼び出して、アプリケーションの他の部分にファイルの書き込みを行わせます。

ドラッグの終了
ファイルが正常に作成されたら (そして、その場合のみ)、ドラッグの受信側にファイル名とファイルが作成された場所を認識させます。この処理を行うには、SetItemFlavorData を呼び出します。FlavorType パラメータには、PromiseHFSFlavor レコードの promisedFlavor フィールドの値を渡します。フレーバーデータには、ファイルの名前と保存場所を記述する FSSpec レコードを渡します。promisedFlavor データは、常に HFSFlavor ではなく、FSSpec でなければなりません。次のコードは、データを正しく追加する単純な手続きから構成されています。

コード #10: 約束した FSSpec を追加する
pascal OSErr SetPromisedHFSFlavorData
             (DragReference dragRef, ItemReference itemRef,
             const PromiseHFSFlavor *phfs, const FSSpec *fss)
{
    return SetDragItemFlavorData
           (dragRef, itemRef, phfs->promisedFlavor, fss, sizeof(*fss), 0);
}


“ファイル検索”の役割
既存のファイルを参照する DragReference を提供する必要があり、それが可能な場合は、flavorTypeHFS を送信してください。しかし、やむをえず、その代わりに flavorTypePromiseHFS を送信するときは、次の点を確認してください。

  • PromiseHFSFlavor レコードの promisedFlavor フィールドを 'rWm1' (0x72576D31) に設定します。
  • Drag ManagerDragSendDataProc'rWm1' データを要求し、GetDropLocationdescriptorType フィールドに typeNull を含む AEDesc を作成するときには、ファイルのオリジナルの位置を提供します。
  • GetDropLocationdescriptorType フィールドに typeAlias を含む AEDesc を作成するときには、ファイルをドロップ位置にコピーします。'rWm1' は、ドラッグの受信側へのヒントにすぎませんが、このヒントを取り去ってはいけません。


重要:
これらのステップはすべてのドラッグ項目に対して実行するか、いずれの項目にも実行しないようにします。一部の項目だけを対象に実行しないでください。


この TECHNOTE の「ファイル検索を使ったコピー」では、なぜこれらのステップが必要なのかについて詳しく説明します。次のコードは、呼び出し元がドロップするファイルをコピーするかどうかを呼び出し元に確認する道筋をインプリメントしています。

コード #11: ドロップされたファイルをコピーするかどうかを決定する
pascal OSErr ShouldCopyToDropLoc
             (DragReference dragRef, FlavorType promisedFlavor, Boolean *shouldCopy)
{
    OSErr err = noErr;

    AEDesc dropLoc = { typeNull, nil };

    *shouldCopy = false;

    if (!(err = GetDropLocation (dragRef, &dropLoc)))
    {
        if (dropLoc.descriptorType == typeAlias)
        {
            // ヒントなし、または受信側はそれを見つけることができない
            *shouldCopy = true;
        }
        else if (dropLoc.descriptorType != typeNull)
        {
            // 未知のドロップ位置記述子の型
            err = paramErr;
        }
        else if (promisedFlavor != kPromisedFlavorFindFile)
        {
            // null 記述子だが、予期されたヒントなし (DragPeeker)
            err = dirNFErr;
        }

        if (dropLoc.dataHandle)
        {
            OSErr err2 = AEDisposeDesc (&dropLoc);
            if (!err) err = err2;
        }

    }
    return err;
}

flavorTypePromiseHFS の受信
たいていのアプリケーションは、flavorTypePromiseHFS データを受け取る必要はありません。ほとんどの場合、flavorTypeHFS で十分です。多くの送信側が flavorTypeHFS を提供しますが、少なくとも 1 つ、flavorTypePromiseHFS を提供する重要なアプリケーション (“ファイル検索”) があります。いずれにせよ、flavorTypePromiseHFS に労力を傾ける前に、flavorTypeHFS のことを慎重に検討してください。

2 つのフレーバーの取得
ドラッグ追跡ハンドラ (Drag Tracking HandlerFlavor ) では、PromiseHFSFlavor である flavorTypePromiseHFS データを取得してもかまいませんが、promisedデータを取得しないでください。ドラッグ追跡ハンドラでは、アプリケーション内の指定されたウィンドウがデータの究極の受信側であるかどうかを認識することはできません。つまり、究極の受信側は、そのアプリケーションの別のウィンドウであることもあれば、別のアプリケーションのウィンドウであることもあります。ドラッグ追跡ハンドラが flavorTypePromiseHFS データを要求する場合、Drag Manager は送信側の SendDataProc を呼び出し、その後、データは DragReference にキャッシュされます。その結果、他の潜在的な受信側がキャッシュされたデータを取得することになり、送信側はレシーバのドロップ位置に従って、そのデータを調整するチャンスを失ってしまいます。

ドラッグ受信ハンドラ (Drag Receive Handler) では、flavorTypePromiseHFS データと promisedFlavor データの両方を取得するのが安全です。ただし、promisedFlavor データをリクエストする前に、必ず SetDropLocation を呼び出してください。次のコードは、このプロセスを管理する関数です。フォルダパラメータは NIL の場合もあります。これは、呼び出し元がファイル検索をサポートしているという意味です。この関数がどのように動作し、なぜこの関数を実行しなければならないかについては後述します。

コード #12: flavorTypeHFS の受信
pascal OSErr ReceivePromisedFile
             (DragReference dragRef, ItemReference itemRef,
              HFSFlavor *hfsFlavor, const FSSpec *folder)
{
    OSErr err = noErr;

    if (folder)
        // SetDropFolder については、コード #13 を参照
        err = SetDropFolder (dragRef,folder);

    if (!err)
    {
        // 'isSupposedlyFromFindFile' については後述
        Boolean isSupposedlyFromFindFile = (folder == nil);
        err = GetHFSFlavorFromPromise // コード #14 を参照
        (dragRef, itemRef, hfsFlavor, isSupposedlyFromFindFile);
    }

    return err;
}


ドロップ位置の設定
flavorTypePromiseHFS を受信するプロセスの中でも、この部分は比較的簡単です。まず、ドロップ位置のエイリアスを作成します。flavorTypePromiseHFS の場合、これは常にディレクトリになるはずです。次に、そのエイリアスを AEDesc の中にコピーします。そして最後に、SetDropLocation を呼び出します。この手続きは、次のコードで具体的に説明されています。

コード #13: コード #12 によって呼び出される関数
static pascal OSErr SetDropFolder
                    (DragReference dragRef, const FSSpec *folder)
{
    OSErr err = noErr;

    AliasHandle aliasH;

    if (!(err = NewAliasMinimal (folder, &aliasH)))
    {
        HLockHi ((Handle) aliasH);
        if (!(err = MemError ( )))
        {
            Size size = GetHandleSize ((Handle) aliasH);
            if (!(err = MemError ( )))
            {
                AEDesc dropLoc;

                if (!(err = AECreateDesc
                            (typeAlias, *aliasH, size, &dropLoc)))
                {
                    OSErr err2;

                    err = SetDropLocation (dragRef, &dropLoc);

                    err2 = AEDisposeDesc (&dropLoc);
                    if (!err) err = err2;
                }
            }
        }

        DisposeHandle ((Handle) aliasH);
        if (!err) err = MemError ( );
    }

    return err;
}

ファイル検索を使ったコピー
多くのドラッグ受信側では、ファイル検索の検索結果ウィンドウからドラッグされたデータを受信できることが望ましいといえます。大部分のデベロッパが DragReference 内で検索しようとする第 1 のフレーバーは、flavorTypeHFS であると思われます。しかし、ファイル検索は、この TECHONOTE で述べた Finder のバグに対処しようとして、flavorTypeHFS ではなく flavorTypePromiseHFS を提供します。

ファイル検索の不具合の本質
ファイル検索は、Finder の内部ではかなりうまく動作しますが、flavorTypePromiseHFS を受信する他の多くのアプリケーションとはうまく動作しません。既に述べたように、flavorTypePromiseHFS はまだ存在していないファイルを作成するための約束ですが、ファイル検索の検索結果ウィンドウには既存のファイルだけが表示されます。このとき、セマンティックな矛盾が発生します。ここでは、具体例を通して、どのようにしてこの矛盾が問題の原因になるのかを探ってみましょう。

電子メールアプリケーションがメッセージへの添付物として flavorTypePromiseHFS を受け入れ、ドラッグの送信側がこの TECHNOTE で説明した flavorTypePromiseHFS の意味を心得ていると仮定します。このとき、電子メールアプリケーションはドロップ位置をその送信スプールフォルダに設定し、関連づけられたメッセージが正常に送信されたときに、そのファイルを削除しようとします。その結果、flavorTypePromiseHFS の意味は、受信側のアプリケーションが排他的に使用するためにファイルを作成するということになります。

しかし、その代わりにファイル検索がそのスプールフォルダに既存のファイルを単純に移動する場合、電子メールアプリケーションはそのデータのコピーを 1 つ削除してしまいます。あるいは少なくとも、ファイル検索はユーザが予期しない、または理解できない場所にファイルを移動してしまいます。これはファイル検索の実際の動作です。

なぜこのようなことが起こるかといえば、Finder にはバグがあるため、ファイル検索はドロップが発生したことを Finder に納得させ、ドロップされたファイルを削除します。さらに Finder に AppleEvent を送信し、単独で flavorTypeHFS を処理させます。ファイル検索が Finder から得たいデータはドロップ位置だけです。

ここで説明した補足情報とは関係なく、開発するアプリケーションは、この TECHNOTE の各セクションの内容にできるかぎり厳密に準拠している必要があります。

ファイル検索をめぐる問題
ファイル検索のエンジニアは、希望通りに動作させるために Finder に無理を強いることはありませんでした。しかし、他のアプリケーションが HFS に関連したドラッグを敏感に受信するための方法を提供しています。これまで、このことはマニュアルに書かれていませんでした。

ドラッグ追跡ハンドラでは、flavorTypePromiseHFS データを取得して、その promisedFlavor フィールドが 'rWm1' (0x72576D31) であるかどうかチェックしてください。これは、ファイル検索が常に使用する値です。promisedFlavor がこの値になっている場合は、フラグをセットして、後で SetDropLocation を呼び出さないようにします。

ドラッグ受信ハンドラでは、promisedFlavor データを要求する前に、通常は SetDropLocation を呼び出します。ただし、ファイル検索から flavorTypePromiseHFS データを受信している場合は、このステップを省略して、Drag Manager に promisedFlavor データを要求します (もちろんこの場合も、promisedFlavor は常に 'rWm1' という値を持ちます)。これにより、ファイル検索にファイルのコピーや移動をさせることなく、FSSpec データが作成されます。

flavorTypePromiseHFS プロトコルに対するこのバリアントを正常に動作させるには、ドラッグ受信ハンドラの中で promisedFlavor データをリクエストする必要があります。これをドラッグ追跡ハンドラの中でリクエストすると、単純にエラーが返されます。

ここまでの説明で、promisedFlavor の値がなぜ重要なのかは理解できるはずです。値が 'rWm1' であれば、データはファイル検索に由来し、それ以外の値 ('fssP' [0x66737350] をお勧めしますが、プログラムが既に別の値を使っていても特に問題はありません) であれば、データは他のアプリケーションに由来します。ファイル検索以外のアプリケーションは、flavorTypePromiseHFS の本来の意味に準拠する必要があります。

次のコードでは、ファイル検索に関する混乱が発生しないようにいくつかのチェックを加えて、両方のフレーバーを取得する方法を示しています。

コード #14: コード #12 によって呼び出される関数
static pascal OSErr GetHFSFlavorFromPromise
                    (DragReference dragRef, ItemReference itemRef,
                     HFSFlavor *hfs, Boolean isSupposedlyFromFindFile)
{
    OSErr err = noErr;
    PromiseHFSFlavor phfs;
    Size size = sizeof (phfs);

    err = GetFlavorData(dragRef, itemRef, flavorTypePromiseHFS, &phfs, &size, 0);

    if (!err)
    {
        if (size != sizeof (phfs))
            err = cantGetFlavorErr;
        else
        {
            Boolean isFromFindFile =
                    phfs.promisedFlavor == kPromisedFlavorFindFile;

            if (isSupposedlyFromFindFile != isFromFindFile)
                err = paramErr;
            else
            {
                size = sizeof (hfs->fileSpec);
                err = GetFlavorData
                      (dragRef, itemRef, phfs.promisedFlavor,
                       &(hfs->fileSpec), &size, 0);

                if (!err)
                {
                    Size minSize = MinimumBytesForFSSpec(&(hfs->fileSpec));
                    // MinimumBytesForFSSpec については、コード #3 を参照

                    if (size < minSize)
                        err = cantGetFlavorErr;
                    else
                    {
                        hfs->fileType = phfs.fileType;
                        hfs->fileCreator = phfs.fileCreator;
                        hfs->fdFlags = phfs.fdFlags;
                    }
                }
            }
        }
    }

    return err;
}

要約
Drag Manager に関連づけられるファイルシステムオリエンティッドな 2 つのフレーバータイプがあります。1 つは比較的単純なフレーバーで、Finder のバグに対するいくつかの対処方法を除けば、他の大部分のフレーバーと同様に取り扱うことができます。一方、もう 1 つの flavorTypePromiseHFS はおそらく、デベロッパが取り扱うフレーバーの中でも最も複雑なものであり、これを正しくインプリメントするには、細心の注意とめんどうな対処方法に対する理解が必要です。

次に、もう一度繰り返す価値のある重要なポイントをまとめてみます。

  • 既存のファイルには flavorTypeHFS を使います。また、まだ存在していないが、作成したいファイルには、flavorTypePromiseHFS を使います。
  • Drag Manager のフレーバーデータを約束するという概念と、 flavorTypePromiseHFS とを混同しないでください。両者とも 1 つの約束ですが、約束の種類がまったく異なります。
  • すべてのエラーコードをチェックし、コードの中にアサーションを組み込んで、期待した動作に従わないアプリケーションが問題を起こすのを避けてください。
  • GetDropLocationSetDropLocation をうまく使ってください。
  • 削除しようと思っているファイルに対する flavorTypePromiseHFS を受信したときは、ファイル検索が関係しているかどうかを確認し、ユーザが残しておきたいと思っているデータが破壊されるのを回避します。


参考文献
The Drag Manager Programmer's Guide。これは、Developer CD Series Mac OS SDK ディスクに収録されています。また、ここをクリックしてダウンロードすることも可能です。 AEDesc は、Inside Macintosh: Interapplication Communication の 3-12 ページ以降で説明されている AppleEvent Manager のデータ構造体です。


ダウンロード
PDF Drag Manager Programmer's Guide [386k] (Adobe Acrobat 書類/英語)
prj FinderDragPro プロジェクト [245k] (Metrowerks CodeWarrior 書類)


謝辞
資料の収集からテクニカルレビューまで、この TECHNOTE の完成を支援してくれた友人たちに心から感謝します。Andy Bachorski、Brian Bechtel、Steve Christensen、Steve Dorner、Dave Evans、Nitin Ganatra、Peter Lewis、Bill Monk、Matt Mora、Pete Resnick、Leonard Rosenthol、Rich Siegel、jud spencer、James Thomson のみなさんです。

付録
この付録では、これまでにあげたコードを完全に理解するために必要でありながら、本文の流れから割愛せざるを得なかったコードを示します。

付録 A
これは、「付録 B」および「付録 C」の関数によって呼び出されるユーティリティ関数で、CInfoPBRec が指定されたディレクトリに関する情報を含むようにします。この関数がエラーを返さない場合、呼び出し元では、CInfoPBRec が正常に処理されます。
static pascal OSErr FSpGetDirInfo(const FSSpec *spec, CInfoPBPtr *cipbpp)

{
    OSErr err = noErr;

    CInfoPBPtr pbp = (CInfoPBPtr) NewPtrClear (sizeof (*pbp));

    *cipbpp = nil;

    if (!(err = MemError ( )))
    {
        pbp->dirInfo.ioVRefNum = spec->vRefNum;
        pbp->dirInfo.ioDrDirID = spec->parID;
        pbp->dirInfo.ioNamePtr = (StringPtr) spec->name;

        err = PBGetCatInfoSync (pbp);

        if (!err && !(pbp->hFileInfo.ioFlAttrib & ioDirMask))
            err = dirNFErr;

        if (err)
            DisposePtr ((Ptr) pbp);
        else
            *cipbpp = pbp;
    }

    return err;
}


付録 B
これらの関数は、FSpGetFinfo および FSpSetFInfo と同じ API に従うように配慮されています。これらは、「付録 A」にあげた FSpGetDirInfo を呼び出します。
static pascal OSErr FSpGetDInfo(const FSSpec *spec, DInfo *fndrInfo)
{
    OSErr err = noErr;

    CInfoPBPtr cipbp;

    if (!(err = FSpGetDirInfo (spec, &cipbp)))
    {
        *fndrInfo = cipbp->dirInfo.ioDrUsrWds;

        DisposePtr ((Ptr) cipbp);
        if (!err) err = MemError ( );
    }

    return err;
}



static pascal OSErr FSpSetDInfo(const FSSpec *spec, const DInfo *fndrInfo)
{
    OSErr err = noErr;

    CInfoPBPtr cipbp;

    if (!(err = FSpGetDirInfo (spec, &cipbp)))
    {
        cipbp->dirInfo.ioDrUsrWds = *fndrInfo;
        cipbp->dirInfo.ioDrDirID = spec->parID;

        err = PBSetCatInfoSync (cipbp);

        DisposePtr ((Ptr) cipbp);
        if (!err) err = MemError ( );
    }

    return err;
}


付録 C
この関数は、指定されたフォルダのディレクトリ ID を返します。この関数は、「付録 A」にあげた FSpGetDirInfo を呼び出します。
pascal OSErr GetDirectoryID (const FSSpec *spec, long *dirID)
{
    OSErr err = noErr;

    CInfoPBPtr cipbp;

    if (!(err = FSpGetDirInfo (spec, &cipbp)))
    {
        *dirID = cipbp->dirInfo.ioDrDirID;

        DisposePtr ((Ptr) cipbp);
        if (!err) err = MemError ( );
    }

    return err;
}


更新日: 1997 年 3 月 17 日