]> git.saurik.com Git - wxWidgets.git/blobdiff - src/mac/carbon/dirdlg.cpp
started macosx compliant implementation
[wxWidgets.git] / src / mac / carbon / dirdlg.cpp
index 095621b978ebe12a862622298ae8d5f1f48db38d..83533f1236711365c82828ecadfabb3b500e8d2c 100644 (file)
 
 #include "wx/cmndata.h"
 
 
 #include "wx/cmndata.h"
 
+#ifdef __DARWIN__
+  #include <Carbon/Carbon.h>
+#else
+  #include <Navigation.h>
+#endif
+
 #if !USE_SHARED_LIBRARY
 IMPLEMENT_CLASS(wxDirDialog, wxDialog)
 #endif
 
 #if !USE_SHARED_LIBRARY
 IMPLEMENT_CLASS(wxDirDialog, wxDialog)
 #endif
 
+bool gUseNavServices = NavServicesAvailable() ;
+
+// the data we need to pass to our standard file hook routine
+// includes a pointer to the dialog, a pointer to the standard
+// file reply record (so we can inspect the current selection)
+// and a copy of the "previous" file spec of the reply record
+// so we can see if the selection has changed
+
+#if !TARGET_CARBON
+
+struct UserDataRec {
+       StandardFileReply       *sfrPtr;
+       FSSpec                          oldSelectionFSSpec;
+       DialogPtr                       theDlgPtr;
+};
+typedef struct UserDataRec
+       UserDataRec, *UserDataRecPtr;
+
+enum {
+       kSelectItem = 10,                       // select button item number
+       kSFGetFolderDlgID = 250,        // dialog resource number
+       kStrListID = 250,                       // our strings
+       kSelectStrNum = 1,                      // word 'Select: ' for button
+       kDesktopStrNum = 2,                     // word 'Desktop' for button
+       kSelectNoQuoteStrNum = 3,       // word 'Select: ' for button
+       
+       kUseQuotes = true,                      // parameter for SetButtonName
+       kDontUseQuotes = false
+};
+
+
+static void GetLabelString(StringPtr theStr, short stringNum)
+{
+       GetIndString(theStr, kStrListID, stringNum);
+}
+
+static void CopyPStr(StringPtr src, StringPtr dest)
+{
+       BlockMoveData(src, dest, 1 + src[0]);
+}
+
+static char GetSelectKey(void)
+{
+       // this is the key used to trigger the select button
+       
+       // NOT INTERNATIONAL SAVVY; should at least grab it from resources
+       
+       return 's';
+}
+
+
+// SetButtonName sets the name of the Select button in the dialog
+//
+// To do this, we need to call the Script Manager to truncate the
+// label in the middle to fit the button and to merge the button
+// name with the word Select (possibly followed by quotes).  Using
+// the Script Manager avoids all sorts of problems internationally.
+//
+// buttonName is the name to appear following the word Select
+// quoteFlag should be true if the name is to appear in quotes
+
+static void SetButtonName(DialogPtr theDlgPtr, short buttonID, StringPtr buttonName,
+                                       Boolean quoteFlag)
+{
+       short   buttonType;
+       Handle  buttonHandle;
+       Rect    buttonRect;
+       short   textWidth;
+       Handle  labelHandle;
+       Handle  nameHandle;
+       Str15   keyStr;
+       Str255  labelStr;
+       OSErr   err;
+       
+       nameHandle = nil;
+       labelHandle = nil;
+       
+       // get the details of the button from the dialog
+       
+       GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
+       
+       // get the string for the select button label, "Select ^0" or "Select Ò^0Ó"
+       
+       GetLabelString(labelStr, (quoteFlag == kUseQuotes) ? kSelectStrNum : kSelectNoQuoteStrNum);
+       
+       // make string handles containing the select button label and the
+       // file name to be stuffed into the button
+       
+       err = PtrToHand(&labelStr[1], &labelHandle, labelStr[0]);
+       if (err != noErr) goto Bail;
+       
+       // cut out the middle of the file name to fit the button
+       //
+       // we'll temporarily use labelStr here to hold the modified button name
+       // since we don't own the buttonName string storage space
+       
+       textWidth = (buttonRect.right - buttonRect.left) - StringWidth(labelStr);
+
+       CopyPStr(buttonName, labelStr);
+       (void) TruncString(textWidth, labelStr, smTruncMiddle);
+       
+       err = PtrToHand(&labelStr[1], &nameHandle, labelStr[0]);
+       if (err != noErr) goto Bail;
+       
+       // replace the ^0 in the Select string with the file name
+       
+       CopyPStr("\p^0", keyStr);
+       
+       (void) ReplaceText(labelHandle, nameHandle, keyStr);
+       
+       labelStr[0] = (unsigned char) GetHandleSize(labelHandle);
+       BlockMoveData(*labelHandle, &labelStr[1], labelStr[0]);
+       
+       // now set the control title, and re-validate the area
+       // above the control to avoid a needless redraw
+       
+       SetControlTitle((ControlHandle) buttonHandle, labelStr);
+       
+       ValidRect(&buttonRect);
+
+Bail:
+       if (nameHandle)         DisposeHandle(nameHandle);
+       if (labelHandle)        DisposeHandle(labelHandle);
+       
+}
+
+// FlashButton briefly highlights the dialog button 
+// as feedback for key equivalents
+
+static void FlashButton(DialogPtr theDlgPtr, short buttonID)
+{
+       short   buttonType;
+       Handle  buttonHandle;
+       Rect    buttonRect;
+       unsigned long   finalTicks;
+       
+       GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
+       HiliteControl((ControlHandle) buttonHandle, kControlButtonPart);
+       Delay(10, &finalTicks);
+       HiliteControl((ControlHandle) buttonHandle, 0);
+}
+
+static Boolean SameFSSpec(FSSpecPtr spec1, FSSpecPtr spec2)
+{
+       return (spec1->vRefNum == spec2->vRefNum
+                       && spec1->parID == spec2->parID
+                       && EqualString(spec1->name, spec2->name, false, false));
+}
+// MyModalDialogFilter maps a key to the Select button, and handles
+// flashing of the button when the key is hit
+
+static pascal Boolean SFGetFolderModalDialogFilter(DialogPtr theDlgPtr, EventRecord *eventRec,
+                                                                                       short *item, void *dataPtr)
+{
+#pragma unused (dataPtr)
+
+       // make certain the proper dialog is showing, 'cause standard file
+       // can nest dialogs but calls the same filter for each
+       
+       if (((WindowPeek) theDlgPtr)->refCon == sfMainDialogRefCon)
+       {
+               // check if the select button was hit
+               
+               if ((eventRec->what == keyDown)
+                       && (eventRec->modifiers & cmdKey) 
+                       && ((eventRec->message & charCodeMask) == GetSelectKey()))
+               {
+                       *item = kSelectItem;
+                       FlashButton(theDlgPtr, kSelectItem);
+                       return true;
+               }
+       }
+               
+       return false;
+}
+
+
+// MyDlgHook is a hook routine that maps the select button to Open
+// and sets the Select button name
+
+static pascal short SFGetFolderDialogHook(short item, DialogPtr theDlgPtr, void *dataPtr)
+{
+       UserDataRecPtr  theUserDataRecPtr;
+       long                    desktopDirID;
+       short                   desktopVRefNum;
+       FSSpec                  tempSpec;
+       Str63                   desktopName;
+       OSErr                   err;
+       
+       // be sure Std File is really showing us the intended dialog,
+       // not a nested modal dialog
+       
+       if (((WindowPeek) theDlgPtr)->refCon != sfMainDialogRefCon)
+       {
+               return item;
+       }
+       
+       theUserDataRecPtr = (UserDataRecPtr) dataPtr;
+       
+       // map the Select button to Open
+       
+       if (item == kSelectItem)
+       {
+               item = sfItemOpenButton;
+       }
+       
+       // find the desktop folder
+       
+       err = FindFolder(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
+                                       kDesktopFolderType, kDontCreateFolder,
+                                       &desktopVRefNum, &desktopDirID);
+       
+       if (err != noErr)
+       {
+               // for errors, get value that won't match any real vRefNum/dirID
+               desktopVRefNum = 0;
+               desktopDirID = 0;
+       }
+       
+       // change the Select button label if the selection has changed or
+       // if this is the first call to the hook
+       
+       if (item == sfHookFirstCall
+               || item == sfHookChangeSelection
+               || item == sfHookRebuildList
+               || ! SameFSSpec(&theUserDataRecPtr->sfrPtr->sfFile,
+                                       &theUserDataRecPtr->oldSelectionFSSpec))
+       {
+               // be sure there is a file name selected
+               
+               if (theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
+               {
+                       SetButtonName(theDlgPtr, kSelectItem, 
+                                                       theUserDataRecPtr->sfrPtr->sfFile.name, 
+                                                       kUseQuotes);    // true -> use quotes
+               }
+               else
+               {
+                       // is the desktop selected?
+                       
+                       if (theUserDataRecPtr->sfrPtr->sfFile.vRefNum == desktopVRefNum
+                               && theUserDataRecPtr->sfrPtr->sfFile.parID == desktopDirID)
+                       {
+                               // set button to "Select Desktop"
+                               
+                               GetLabelString(desktopName, kDesktopStrNum);
+                               SetButtonName(theDlgPtr, kSelectItem, 
+                                                               desktopName, kDontUseQuotes);   // false -> no quotes
+                       }
+                       else
+                       {
+                               // get parent directory's name for the Select button
+                               //
+                               // passing an empty name string to FSMakeFSSpec gets the
+                               // name of the folder specified by the parID parameter
+                               
+                               (void) FSMakeFSSpec(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
+                                       theUserDataRecPtr->sfrPtr->sfFile.parID, "\p",
+                                       &tempSpec);
+                               SetButtonName(theDlgPtr, kSelectItem, 
+                                                       tempSpec.name, kUseQuotes); // true -> use quotes
+                       }
+               }
+       }
+       
+       // save the current selection as the old selection for comparison next time
+       //
+       // it's not valid on the first call, though, or if we don't have a 
+       // name available from standard file
+       
+       if (item != sfHookFirstCall || theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
+       {
+               theUserDataRecPtr->oldSelectionFSSpec = theUserDataRecPtr->sfrPtr->sfFile;
+       }
+       else
+       {
+               // on first call, empty string won't set the button correctly, 
+               // so invalidate oldSelection
+               
+               theUserDataRecPtr->oldSelectionFSSpec.vRefNum = 999;
+               theUserDataRecPtr->oldSelectionFSSpec.parID = 0;
+       }
+       
+       return item;
+}
+
+void StandardGetFolder( ConstStr255Param message , ConstStr255Param path , FileFilterYDUPP fileFilter, StandardFileReply *theSFR)
+{
+       Point                           thePt;
+       SFTypeList                      mySFTypeList;
+       UserDataRec                     myData;
+       FSSpec                          tempSpec;
+       Boolean                         folderFlag;
+       Boolean                         wasAliasedFlag;
+       DlgHookYDUPP            dlgHookUPP;
+       ModalFilterYDUPP        myModalFilterUPP;
+       OSErr                           err;
+       
+       
+       // presumably we're running System 7 or later so CustomGetFile is
+       // available
+       
+       // set initial contents of Select button to a space
+       
+       memcpy(theSFR->sfFile.name, "\p ", 2);
+       
+       // point the user data parameter at the reply record so we can get to it later
+       
+       myData.sfrPtr = theSFR;
+       
+       // display the dialog
+       
+       #if !TARGET_CARBON
+       
+       dlgHookUPP = NewDlgHookYDProc(SFGetFolderDialogHook);
+       myModalFilterUPP = NewModalFilterYDProc(SFGetFolderModalDialogFilter);
+       
+       thePt.h = thePt.v = -1; // center dialog
+       
+       ParamText( message , NULL , NULL , NULL ) ;
+       
+       CustomGetFile(  fileFilter, 
+                                       -1,                                     // show all types
+                                       mySFTypeList,
+                                       theSFR,
+                                       kSFGetFolderDlgID,
+                                       thePt,                          // top left point
+                                       dlgHookUPP,
+                                       myModalFilterUPP,
+                                       nil,                            // activate list
+                                       nil,                            // activate proc
+                                       &myData);
+                                       
+       DisposeRoutineDescriptor(dlgHookUPP);
+       DisposeRoutineDescriptor(myModalFilterUPP);
+       #else
+       #endif
+       
+       // if cancel wasn't pressed and no fatal error occurred...
+       
+       if (theSFR->sfGood)
+       {
+               // if no name is in the reply record file spec,
+               // use the file spec of the parent folder
+               
+               if (theSFR->sfFile.name[0] == '\0')
+               {
+                       err = FSMakeFSSpec(theSFR->sfFile.vRefNum, theSFR->sfFile.parID,
+                                                               "\p", &tempSpec);
+                       if (err == noErr)
+                       {
+                               theSFR->sfFile = tempSpec;
+                       }
+                       else
+                       {
+                               // no name to return, forget it
+                               
+                               theSFR->sfGood = false;
+                       }
+               }
+               
+               // if there is now a name in the file spec, check if it's
+               // for a folder or a volume
+               
+               if (theSFR->sfFile.name[0] != '\0')
+               {
+                       // the parID of the root of a disk is always fsRtParID == 1
+                       
+                       if (theSFR->sfFile.parID == fsRtParID)
+                       {
+                               theSFR->sfIsVolume = true;
+                               theSFR->sfIsFolder = false;     // it would be reasonable for this to be true, too
+                       }
+                       
+                       // we have a valid FSSpec, now let's make sure it's not for an alias file
+                       
+                       err = ResolveAliasFile(&theSFR->sfFile, true, &folderFlag, &wasAliasedFlag);
+                       if (err != noErr)
+                       {
+                               theSFR->sfGood = false;
+                       }
+                       
+                       // did the alias resolve to a folder?
+                       
+                       if (folderFlag  && ! theSFR->sfIsVolume)
+                       {
+                               theSFR->sfIsFolder = true;
+                       }
+               }
+       }
+}
+
+static pascal Boolean OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr, void *dataPtr)
+{
+#pragma unused (dataPtr)
+
+       // return true if this item is invisible or a file
+
+       Boolean visibleFlag;
+       Boolean folderFlag;
+       
+       visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible);
+       folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10);
+       
+       // because the semantics of the filter proc are "true means don't show
+       // it" we need to invert the result that we return
+       
+       return !(visibleFlag && folderFlag);
+}
+
+#endif
+
+
 wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
         const wxString& defaultPath,
         long style, const wxPoint& pos)
 wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
         const wxString& defaultPath,
         long style, const wxPoint& pos)
@@ -36,7 +455,145 @@ wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
 
 int wxDirDialog::ShowModal()
 {
 
 int wxDirDialog::ShowModal()
 {
-    // TODO
-       return wxID_CANCEL;
+       #if !TARGET_CARBON
+       if ( !gUseNavServices )
+       {
+               Str255                          prompt ;
+               Str255                          path ;
+
+#if TARGET_CARBON
+               c2pstrcpy((StringPtr)prompt, m_message) ;
+#else
+               strcpy((char *)prompt, m_message) ;
+               c2pstr((char *)prompt ) ;
+#endif
+#if TARGET_CARBON
+               c2pstrcpy((StringPtr)path, m_path ) ;
+#else
+               strcpy((char *)path, m_path ) ;
+               c2pstr((char *)path ) ;
+#endif
+
+               StandardFileReply       reply ;
+               FileFilterYDUPP         invisiblesExcludedCustomFilterUPP = 0 ;
+               invisiblesExcludedCustomFilterUPP = 
+                       NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter);
+
+
+               StandardGetFolder( prompt , path , invisiblesExcludedCustomFilterUPP, &reply);
+       
+
+               DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP);
+
+               if ( reply.sfGood == false )
+               {
+                       m_path = "" ;
+                       return wxID_CANCEL ;
+               }
+               else
+               {
+                       m_path = wxMacFSSpec2MacFilename( &reply.sfFile ) ;
+                       return wxID_OK ;
+               }
+               return wxID_CANCEL;
+       }
+       else
+       #endif
+       {
+               NavDialogOptions                mNavOptions;
+               NavObjectFilterUPP              mNavFilterUPP = NULL;
+               NavPreviewUPP                   mNavPreviewUPP = NULL ;
+               NavReplyRecord                  mNavReply;
+               AEDesc*                                 mDefaultLocation = NULL ;
+               bool                                    mSelectDefault = false ;
+               
+               ::NavGetDefaultDialogOptions(&mNavOptions);
+       
+               mNavFilterUPP   = nil;
+               mNavPreviewUPP  = nil;
+               mSelectDefault  = false;
+               mNavReply.validRecord                           = false;
+               mNavReply.replacing                                     = false;
+               mNavReply.isStationery                          = false;
+               mNavReply.translationNeeded                     = false;
+               mNavReply.selection.descriptorType = typeNull;
+               mNavReply.selection.dataHandle          = nil;
+               mNavReply.keyScript                                     = smSystemScript;
+               mNavReply.fileTranslation                       = nil;
+               
+               // Set default location, the location
+               //   that's displayed when the dialog
+               //   first appears
+               
+               if ( mDefaultLocation ) {
+                       
+                       if (mSelectDefault) {
+                               mNavOptions.dialogOptionFlags |= kNavSelectDefaultLocation;
+                       } else {
+                               mNavOptions.dialogOptionFlags &= ~kNavSelectDefaultLocation;
+                       }
+               }
+               
+               OSErr err = ::NavChooseFolder(
+                                                       mDefaultLocation,
+                                                       &mNavReply,
+                                                       &mNavOptions,
+                                                       NULL,
+                                                       mNavFilterUPP,
+                                                       0L);                                                    // User Data
+               
+               if ( (err != noErr) && (err != userCanceledErr) ) {
+                       m_path = "" ;
+                       return wxID_CANCEL ;
+               }
+
+               if (mNavReply.validRecord) {            // User chose a folder
+               
+                       FSSpec  folderInfo;
+                       FSSpec  outFileSpec ;
+                       AEDesc specDesc ;
+                       
+                       OSErr err = ::AECoerceDesc( &mNavReply.selection , typeFSS, &specDesc);
+                       if ( err != noErr ) {
+                               m_path = "" ;
+                               return wxID_CANCEL ;
+                       }                       
+                       folderInfo = **(FSSpec**) specDesc.dataHandle;
+                       if (specDesc.dataHandle != nil) {
+                               ::AEDisposeDesc(&specDesc);
+                       }
+
+//                     mNavReply.GetFileSpec(folderInfo);
+                       
+                               // The FSSpec from NavChooseFolder is NOT the file spec
+                               // for the folder. The parID field is actually the DirID
+                               // of the folder itself, not the folder's parent, and
+                               // the name field is empty. We must call PBGetCatInfo
+                               // to get the parent DirID and folder name
+                       
+                       Str255          name;
+                       CInfoPBRec      thePB;                  // Directory Info Parameter Block
+                       thePB.dirInfo.ioCompletion      = nil;
+                       thePB.dirInfo.ioVRefNum         = folderInfo.vRefNum;   // Volume is right
+                       thePB.dirInfo.ioDrDirID         = folderInfo.parID;             // Folder's DirID
+                       thePB.dirInfo.ioNamePtr         = name;
+                       thePB.dirInfo.ioFDirIndex       = -1;   // Lookup using Volume and DirID
+                       
+                       err = ::PBGetCatInfoSync(&thePB);
+                       if ( err != noErr ) {
+                               m_path = "" ;
+                               return wxID_CANCEL ;
+                       }                       
+                                                                                               // Create cannonical FSSpec
+                       ::FSMakeFSSpec(thePB.dirInfo.ioVRefNum, thePB.dirInfo.ioDrParID,
+                                                  name, &outFileSpec);
+                                                       
+                       // outFolderDirID = thePB.dirInfo.ioDrDirID;
+                       m_path = wxMacFSSpec2MacFilename( &outFileSpec ) ;
+                       return wxID_OK ;
+               }
+               return wxID_CANCEL;
+
+       }
 }
 
 }