home *** CD-ROM | disk | FTP | other *** search
/ Software of the Month Club 1999 November / SOTMC_Nov1999-Ultimate.iso / mac / REALbasic ƒ / Plugins / Plugins SDK / SelectFolder.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1999-02-16  |  13.4 KB  |  521 lines  |  [TEXT/CWIE]

  1. #include "rb_plugin.h"
  2.  
  3. #ifdef WIN32
  4.  
  5. static Boolean gSelectFolderOK;
  6. static char gSelectFolderPath[_MAX_PATH];
  7.  
  8. LRESULT CALLBACK OKSubclass(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  9. UINT APIENTRY SelectFolderHook(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
  10.  
  11. LRESULT CALLBACK OKSubclass(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  12. {
  13.     WNDPROC oldWndProc = (WNDPROC) GetProp(hWnd, "SubOldWnd");
  14.     switch (message)
  15.     {
  16.         case WM_COMMAND:
  17.             if (LOWORD(wParam) == IDOK)
  18.             {
  19.                 gSelectFolderOK = true;
  20.                 PostMessage(hWnd, WM_COMMAND, IDCANCEL, 0);
  21.                 return TRUE;
  22.             }
  23.             break;
  24.         case WM_DESTROY:
  25.             SetWindowLong(hWnd, GWL_WNDPROC, (long) oldWndProc);
  26.             RemoveProp(hWnd, "SubOldWnd");
  27.             RemoveProp(hWnd, "SubHookWnd");
  28.             break;
  29.     }
  30.     return CallWindowProc(oldWndProc, hWnd, message, wParam, lParam);
  31. }
  32.  
  33. UINT APIENTRY SelectFolderHook(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  34. {
  35.     LPNMHDR lpnm;
  36.     HWND hParent, hOK;
  37.     int len;
  38.     char szFolderPath[_MAX_PATH];
  39.  
  40.     switch (message)
  41.     {
  42.         case WM_INITDIALOG:
  43.             hParent = GetParent(hDlg);
  44.             SetProp(hParent, "SubHookWnd", hDlg);
  45.             SetProp(hParent, "SubOldWnd", (void *) GetWindowLong(hParent, GWL_WNDPROC));
  46.             SetWindowLong(hParent, GWL_WNDPROC, (long) OKSubclass);
  47.             hOK = GetDlgItem(hParent, IDOK);
  48.             break;
  49.         case WM_NOTIFY:
  50.             lpnm = (LPNMHDR) lParam;
  51.             switch (lpnm->code)
  52.             {
  53.                 case CDN_FOLDERCHANGE:
  54.                     len = CommDlg_OpenSave_GetFolderPath(GetParent(hDlg), szFolderPath, _MAX_PATH);
  55.                     strcpy(gSelectFolderPath, szFolderPath);
  56.                     break;
  57.             }
  58.             break;
  59.     }
  60.     return FALSE;
  61. }
  62.  
  63. static REALfolderItem SelectFolder(void)
  64. {
  65.     OPENFILENAME ofn;
  66.     char szPath[_MAX_PATH];
  67.  
  68.     szPath[0] = 0;
  69.  
  70.     memset(&ofn, 0, sizeof(ofn));
  71.     ofn.lStructSize = sizeof(ofn);
  72.     ofn.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_HIDEREADONLY;
  73.     ofn.lpstrFile = szPath;
  74.     ofn.nMaxFile = _MAX_PATH;
  75.     ofn.lpstrFilter = "Directories\0*.~~~~\0\0";
  76.     ofn.lpfnHook = SelectFolderHook;
  77.     GetOpenFileName(&ofn);
  78.  
  79.     if (gSelectFolderOK)
  80.         return REALFolderItemFromPath(gSelectFolderPath);
  81.     else
  82.         return nil;
  83. }
  84. #else
  85. #include <ToolUtils.h>
  86. #include <Folders.h>
  87. #include <StandardFile.h>
  88. #include <Aliases.h>
  89. #include <A4Stuff.h>
  90.  
  91. enum {
  92.     kSelectItem = 10,             // select button item number
  93.     kSFGetFolderDlgID = 3000,    // dialog resource number
  94.     kStrListID = 3000,            // our strings
  95.     kSelectStrNum = 1,            // word 'Select: ' for button
  96.     kDesktopStrNum = 2,            // word 'Desktop' for button
  97.     kSelectNoQuoteStrNum = 3,    // word 'Select: ' for button
  98.     
  99.     kUseQuotes = true,            // parameter for SetButtonName
  100.     kDontUseQuotes = false
  101. };
  102.  
  103. // the data we need to pass to our standard file hook routine
  104. // includes a pointer to the dialog, a pointer to the standard
  105. // file reply record (so we can inspect the current selection)
  106. // and a copy of the "previous" file spec of the reply record
  107. // so we can see if the selection has changed
  108.  
  109. struct UserDataRec {
  110.     StandardFileReply    *sfrPtr;
  111.     FSSpec                oldSelectionFSSpec;
  112.     DialogPtr            theDlgPtr;
  113.     long                 lUseA4;
  114. };
  115. typedef struct UserDataRec
  116.     UserDataRec, *UserDataRecPtr;
  117.  
  118. static void GetLabelString(StringPtr theStr, short stringNum)
  119. {
  120.     GetIndString(theStr, kStrListID, stringNum);
  121. }
  122.  
  123. static void CopyPStr(StringPtr src, StringPtr dest)
  124. {
  125.     BlockMoveData(src, dest, 1 + src[0]);
  126. }
  127.  
  128. static char GetSelectKey(void)
  129. {
  130.     // this is the key used to trigger the select button
  131.     
  132.     // NOT INTERNATIONAL SAVVY; should at least grab it from resources
  133.     
  134.     return 's';
  135. }
  136.  
  137. // SetButtonName sets the name of the Select button in the dialog
  138. //
  139. // To do this, we need to call the Script Manager to truncate the
  140. // label in the middle to fit the button and to merge the button
  141. // name with the word Select (possibly followed by quotes).  Using
  142. // the Script Manager avoids all sorts of problems internationally.
  143. //
  144. // buttonName is the name to appear following the word Select
  145. // quoteFlag should be true if the name is to appear in quotes
  146.  
  147. static void SetButtonName(DialogPtr theDlgPtr, short buttonID, StringPtr buttonName,
  148.                     Boolean quoteFlag)
  149. {
  150.     short     buttonType;
  151.     Handle    buttonHandle;
  152.     Rect    buttonRect;
  153.     short    textWidth;
  154.     Handle    labelHandle;
  155.     Handle    nameHandle;
  156.     Str15    keyStr;
  157.     Str255    labelStr;
  158.     OSErr    err;
  159.     
  160.     nameHandle = nil;
  161.     labelHandle = nil;
  162.     
  163.     // get the details of the button from the dialog
  164.     
  165.     GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
  166.     
  167.     // get the string for the select button label, "Select ^0" or "Select ╥^0╙"
  168.     
  169.     GetLabelString(labelStr, (quoteFlag == kUseQuotes) ? kSelectStrNum : kSelectNoQuoteStrNum);
  170.     
  171.     // make string handles containing the select button label and the
  172.     // file name to be stuffed into the button
  173.     
  174.     err = PtrToHand(&labelStr[1], &labelHandle, labelStr[0]);
  175.     if (err != noErr) goto Bail;
  176.     
  177.     // cut out the middle of the file name to fit the button
  178.     //
  179.     // we'll temporarily use labelStr here to hold the modified button name
  180.     // since we don't own the buttonName string storage space
  181.     
  182.     textWidth = (buttonRect.right - buttonRect.left) - StringWidth(labelStr);
  183.  
  184.     CopyPStr(buttonName, labelStr);
  185.     (void) TruncString(textWidth, labelStr, smTruncMiddle);
  186.     
  187.     err = PtrToHand(&labelStr[1], &nameHandle, labelStr[0]);
  188.     if (err != noErr) goto Bail;
  189.     
  190.     // replace the ^0 in the Select string with the file name
  191.     
  192.     CopyPStr("\p^0", keyStr);
  193.     
  194.     (void) ReplaceText(labelHandle, nameHandle, keyStr);
  195.     
  196.     labelStr[0] = (unsigned char) GetHandleSize(labelHandle);
  197.     BlockMoveData(*labelHandle, &labelStr[1], labelStr[0]);
  198.     
  199.     // now set the control title, and re-validate the area
  200.     // above the control to avoid a needless redraw
  201.     
  202.     SetControlTitle((ControlHandle) buttonHandle, labelStr);
  203.     
  204.     ValidRect(&buttonRect);
  205.  
  206. Bail:
  207.     if (nameHandle)        DisposeHandle(nameHandle);
  208.     if (labelHandle)    DisposeHandle(labelHandle);
  209.     
  210. }
  211.  
  212. // FlashButton briefly highlights the dialog button 
  213. // as feedback for key equivalents
  214.  
  215. static void FlashButton(DialogPtr theDlgPtr, short buttonID)
  216. {
  217.     short    buttonType;
  218.     Handle    buttonHandle;
  219.     Rect    buttonRect;
  220.     unsigned long    finalTicks;
  221.     
  222.     GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
  223.     HiliteControl((ControlHandle) buttonHandle, kControlButtonPart);
  224.     Delay(10, &finalTicks);
  225.     HiliteControl((ControlHandle) buttonHandle, 0);
  226. }
  227.  
  228. static Boolean SameFSSpec(FSSpecPtr spec1, FSSpecPtr spec2)
  229. {
  230.     return (spec1->vRefNum == spec2->vRefNum
  231.             && spec1->parID == spec2->parID
  232.             && EqualString(spec1->name, spec2->name, false, false));
  233. }
  234.  
  235. // MyModalDialogFilter maps a key to the Select button, and handles
  236. // flashing of the button when the key is hit
  237.  
  238. static pascal Boolean SFGetFolderModalDialogFilter(DialogPtr theDlgPtr, EventRecord *eventRec,
  239.                                             short *item, Ptr dataPtr)
  240. {
  241. #pragma unused (dataPtr)
  242.  
  243.     // make certain the proper dialog is showing, 'cause standard file
  244.     // can nest dialogs but calls the same filter for each
  245.     
  246.     if (((WindowPeek) theDlgPtr)->refCon == sfMainDialogRefCon)
  247.     {
  248.         // check if the select button was hit
  249.         
  250.         if ((eventRec->what == keyDown)
  251.             && (eventRec->modifiers & cmdKey) 
  252.             && ((eventRec->message & charCodeMask) == GetSelectKey()))
  253.         {
  254.             *item = kSelectItem;
  255.             FlashButton(theDlgPtr, kSelectItem);
  256.             return true;
  257.         }
  258.     }
  259.     return false;
  260. }
  261.  
  262.  
  263. // MyDlgHook is a hook routine that maps the select button to Open
  264. // and sets the Select button name
  265.  
  266. static pascal short SFGetFolderDialogHook(short item, DialogPtr theDlgPtr, Ptr dataPtr)
  267. {
  268.     UserDataRecPtr    theUserDataRecPtr;
  269.     long            desktopDirID;
  270.     short            desktopVRefNum;
  271.     FSSpec            tempSpec;
  272.     Str63            desktopName;
  273.     OSErr            err;
  274.     
  275.     // be sure Std File is really showing us the intended dialog,
  276.     // not a nested modal dialog
  277.     
  278.     if (((WindowPeek) theDlgPtr)->refCon != sfMainDialogRefCon)
  279.     {
  280.         return item;
  281.     }
  282.     
  283.     theUserDataRecPtr = (UserDataRecPtr) dataPtr;
  284.  
  285.     long lSaveA4 = GetCurrentA4();
  286.     SetA4(theUserDataRecPtr->lUseA4);
  287.     
  288.     // map the Select button to Open
  289.     
  290.     if (item == kSelectItem)
  291.     {
  292.         item = sfItemOpenButton;
  293.     }
  294.     
  295.     // find the desktop folder
  296.     
  297.     err = FindFolder(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
  298.                     kDesktopFolderType, kDontCreateFolder,
  299.                     &desktopVRefNum, &desktopDirID);
  300.     
  301.     if (err != noErr)
  302.     {
  303.         // for errors, get value that won't match any real vRefNum/dirID
  304.         desktopVRefNum = 0;
  305.         desktopDirID = 0;
  306.     }
  307.     
  308.     // change the Select button label if the selection has changed or
  309.     // if this is the first call to the hook
  310.     
  311.     if (item == sfHookFirstCall
  312.         || item == sfHookChangeSelection
  313.         || item == sfHookRebuildList
  314.         || ! SameFSSpec(&theUserDataRecPtr->sfrPtr->sfFile,
  315.                     &theUserDataRecPtr->oldSelectionFSSpec))
  316.     {
  317.         // be sure there is a file name selected
  318.         
  319.         if (theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
  320.         {
  321.             SetButtonName(theDlgPtr, kSelectItem, 
  322.                             theUserDataRecPtr->sfrPtr->sfFile.name, 
  323.                             kUseQuotes);    // true -> use quotes
  324.         }
  325.         else
  326.         {
  327.             // is the desktop selected?
  328.             
  329.             if (theUserDataRecPtr->sfrPtr->sfFile.vRefNum == desktopVRefNum
  330.                 && theUserDataRecPtr->sfrPtr->sfFile.parID == desktopDirID)
  331.             {
  332.                 // set button to "Select Desktop"
  333.                 
  334.                 GetLabelString(desktopName, kDesktopStrNum);
  335.                 SetButtonName(theDlgPtr, kSelectItem, 
  336.                                 desktopName, kDontUseQuotes);    // false -> no quotes
  337.             }
  338.             else
  339.             {
  340.                 // get parent directory's name for the Select button
  341.                 //
  342.                 // passing an empty name string to FSMakeFSSpec gets the
  343.                 // name of the folder specified by the parID parameter
  344.                 
  345.                 (void) FSMakeFSSpec(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
  346.                     theUserDataRecPtr->sfrPtr->sfFile.parID, "\p",
  347.                     &tempSpec);
  348.                 SetButtonName(theDlgPtr, kSelectItem, 
  349.                             tempSpec.name, kUseQuotes); // true -> use quotes
  350.             }
  351.         }
  352.     }
  353.     
  354.     // save the current selection as the old selection for comparison next time
  355.     //
  356.     // it's not valid on the first call, though, or if we don't have a 
  357.     // name available from standard file
  358.     
  359.     if (item != sfHookFirstCall || theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
  360.     {
  361.         theUserDataRecPtr->oldSelectionFSSpec = theUserDataRecPtr->sfrPtr->sfFile;
  362.     }
  363.     else
  364.     {
  365.         // on first call, empty string won't set the button correctly, 
  366.         // so invalidate oldSelection
  367.         
  368.         theUserDataRecPtr->oldSelectionFSSpec.vRefNum = 999;
  369.         theUserDataRecPtr->oldSelectionFSSpec.parID = 0;
  370.     }
  371.  
  372.     SetA4(lSaveA4);
  373.  
  374.     return item;
  375. }
  376.  
  377. static void StandardGetFolder(FileFilterYDUPP fileFilter, StandardFileReply *theSFR)
  378. {
  379.     Point                 thePt;
  380.     SFTypeList            mySFTypeList;
  381.     UserDataRec            myData;
  382.     FSSpec                tempSpec;
  383.     Boolean                folderFlag;
  384.     Boolean                wasAliasedFlag;
  385.     DlgHookYDUPP        dlgHookUPP;
  386.     ModalFilterYDUPP    myModalFilterUPP;
  387.     OSErr                err;
  388.     
  389.     // presumably we're running System 7 or later so CustomGetFile is
  390.     // available
  391.     
  392.     // set initial contents of Select button to a space
  393.     
  394.     CopyPStr("\p ", theSFR->sfFile.name);
  395.     
  396.     // point the user data parameter at the reply record so we can get to it later
  397.     
  398.     myData.sfrPtr = theSFR;
  399.  
  400.     myData.lUseA4 = GetCurrentA4();
  401.  
  402.     // display the dialog
  403.     
  404.     dlgHookUPP = NewDlgHookYDProc(SFGetFolderDialogHook);
  405.     myModalFilterUPP = NewModalFilterYDProc(SFGetFolderModalDialogFilter);
  406.     
  407.     thePt.h = thePt.v = -1;    // center dialog
  408.     
  409.     CustomGetFile(fileFilter, 
  410.                     -1,                    // show all types
  411.                     mySFTypeList,
  412.                     theSFR,
  413.                     kSFGetFolderDlgID,
  414.                     thePt,                // top left point
  415.                     dlgHookUPP,
  416.                     myModalFilterUPP,
  417.                     nil,                // activate list
  418.                     nil,                // activate proc
  419.                     &myData);
  420.                     
  421.     DisposeRoutineDescriptor(dlgHookUPP);
  422.     DisposeRoutineDescriptor(myModalFilterUPP);
  423.     
  424.     // if cancel wasn't pressed and no fatal error occurred...
  425.     
  426.     if (theSFR->sfGood)
  427.     {
  428.         // if no name is in the reply record file spec,
  429.         // use the file spec of the parent folder
  430.         
  431.         if (theSFR->sfFile.name[0] == '\0')
  432.         {
  433.             err = FSMakeFSSpec(theSFR->sfFile.vRefNum, theSFR->sfFile.parID,
  434.                                 "\p", &tempSpec);
  435.             if (err == noErr)
  436.             {
  437.                 theSFR->sfFile = tempSpec;
  438.             }
  439.             else
  440.             {
  441.                 // no name to return, forget it
  442.                 
  443.                 theSFR->sfGood = false;
  444.             }
  445.         }
  446.         
  447.         // if there is now a name in the file spec, check if it's
  448.         // for a folder or a volume
  449.         
  450.         if (theSFR->sfFile.name[0] != '\0')
  451.         {
  452.             // the parID of the root of a disk is always fsRtParID == 1
  453.             
  454.             if (theSFR->sfFile.parID == fsRtParID)
  455.             {
  456.                 theSFR->sfIsVolume = true;
  457.                 theSFR->sfIsFolder = false;    // it would be reasonable for this to be true, too
  458.             }
  459.             
  460.             // we have a valid FSSpec, now let's make sure it's not for an alias file
  461.             
  462.             err = ResolveAliasFile(&theSFR->sfFile, true, &folderFlag, &wasAliasedFlag);
  463.             if (err != noErr)
  464.             {
  465.                 theSFR->sfGood = false;
  466.             }
  467.             
  468.             // did the alias resolve to a folder?
  469.             
  470.             if (folderFlag  && ! theSFR->sfIsVolume)
  471.             {
  472.                 theSFR->sfIsFolder = true;
  473.             }
  474.         }
  475.     }
  476. }
  477.  
  478. static pascal Boolean OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr, Ptr dataPtr)
  479. {
  480. #pragma unused (dataPtr)
  481.  
  482.     // return true if this item is invisible or a file
  483.  
  484.     Boolean visibleFlag;
  485.     Boolean folderFlag;
  486.     
  487.     visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible);
  488.     folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10);
  489.     
  490.     // because the semantics of the filter proc are "true means don't show
  491.     // it" we need to invert the result that we return
  492.     
  493.     return !(visibleFlag && folderFlag);
  494. }
  495.  
  496. static REALfolderItem SelectFolder(void)
  497. {
  498.     StandardFileReply    mySFReply;
  499.  
  500. #ifdef powerc
  501.     StandardGetFolder(NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter), &mySFReply);
  502. #else
  503.     StandardGetFolder((FileFilterYDUPP) OnlyVisibleFoldersCustomFileFilter, &mySFReply);
  504. #endif
  505.     if (mySFReply.sfGood)
  506.         return REALFolderItemFromFSSpec(&mySFReply.sfFile);
  507.     return nil;
  508. }
  509. #endif
  510.  
  511. REALmethodDefinition SelectFolderDefn = {
  512.     (REALproc) SelectFolder,
  513.     REALnoImplementation,
  514.     "SelectFolder as FolderItem"
  515. };
  516.  
  517. void PluginEntry(void)
  518. {
  519.     REALRegisterMethod(&SelectFolderDefn);
  520. }
  521.