1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 23:59:41 +02:00
audacity/lib-src/FileDialog/mac/FileDialogPrivate.cpp
Leland Lucius e5f6a44656 Fix for bug #983
Now we know why the "automatically add extension" stuff was
commented on in the FileDialog at least.  :-)
2015-05-29 13:02:17 -05:00

796 lines
24 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: filedlg.cpp
// Purpose: wxFileDialog
// Author: Stefan Csomor
// Modified by: Leland Lucius
// Created: 1998-01-01
// RCS-ID: $Id: FileDialogPrivate.cpp,v 1.6 2009-07-26 08:58:50 llucius Exp $
// Copyright: (c) Stefan Csomor
// Licence: wxWindows licence
//
// Modified for Audacity to support an additional button on Save dialogs
//
/////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h"
#include "wx/app.h"
#include "wx/utils.h"
#include "wx/dialog.h"
#include "wx/filedlg.h"
#include "wx/intl.h"
#include "wx/tokenzr.h"
#include "wx/filename.h"
#include "../FileDialog.h"
#ifndef __DARWIN__
#include "PLStringFuncs.h"
#endif
IMPLEMENT_CLASS(FileDialog, wxFileDialogBase)
// begin wxmac
#include "wx/mac/private.h"
#ifndef __DARWIN__
#include <Navigation.h>
#endif
extern bool gUseNavServices ;
#define kCustom 'cstm'
#define kChoice 1
#define kButton 2
static ControlID kChoiceID = { kCustom, kChoice };
static ControlID kButtonID = { kCustom, kButton };
// 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
struct CustomData {
FileDialog *me;
NavDialogRef context;
WindowRef window;
Rect bounds;
ControlRef userpane;
ControlRef choice;
ControlRef button;
MenuRef menu;
wxArrayString name;
wxArrayString extensions;
wxArrayLong filtermactypes;
wxString defaultLocation;
int currentfilter;
bool saveMode;
bool showing;
};
typedef struct CustomData
CustomData, *CustomDataPtr;
static pascal void NavEventProc(
NavEventCallbackMessage inSelector,
NavCBRecPtr ioParams,
NavCallBackUserData ioUserData);
static NavEventUPP sStandardNavEventFilter = NewNavEventUPP(NavEventProc);
static void HandleAdjustRect(NavCBRecPtr callBackParms, CustomData * data)
{
Rect rect;
if (!data->userpane)
{
return;
}
GetControlBounds(data->userpane, &rect);
SInt16 w = rect.right - rect.left;
SInt16 h = rect.bottom - rect.top;
SInt16 cw = callBackParms->customRect.right - callBackParms->customRect.left;
SInt16 ch = callBackParms->customRect.bottom - callBackParms->customRect.top;
MoveControl(data->userpane,
callBackParms->customRect.left + ((cw-w) / 2),
callBackParms->customRect.top + ((ch-h) / 2));
return;
}
static void HandleCarbonCustomizeEvent(NavCBRecPtr callBackParms, CustomData * data)
{
if ((callBackParms->customRect.right == 0) && (callBackParms->customRect.bottom == 0))
{
callBackParms->customRect.right = callBackParms->customRect.left +
(data->bounds.right - data->bounds.left);
callBackParms->customRect.bottom = callBackParms->customRect.top +
(data->bounds.bottom - data->bounds.top);
}
}
static void HandleCustomMouseDown(NavCBRecPtr callBackParms, CustomData *data)
{
EventRecord *evt = callBackParms->eventData.eventDataParms.event;
Point where = evt->where;
GlobalToLocal(&where);
ControlRef control = FindControlUnderMouse(where, callBackParms->window, NULL);
if (control != NULL)
{
HandleControlClick(control, where, evt->modifiers, (ControlActionUPP)-1L);
NavCustomControl(callBackParms->context, kNavCtlBrowserRedraw, NULL);
}
}
static void HandleNormalEvents(NavCBRecPtr callBackParms, CustomData *data)
{
switch (callBackParms->eventData.eventDataParms.event->what)
{
case mouseDown:
{
HandleCustomMouseDown(callBackParms, data);
break;
}
}
}
static const EventTypeSpec namedAttrsList[] =
{
{ kEventClassAccessibility , kEventAccessibleGetNamedAttribute },
{ kEventClassControl , kEventControlHit },
};
// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
static OSStatus SetElement(wxMacCarbonEvent & event, HIObjectRef objectRef,
UInt64 id, int parm)
{
OSStatus result = eventNotHandledErr;
AXUIElementRef elem;
elem = AXUIElementCreateWithHIObjectAndIdentifier(objectRef, id);
if (elem)
{
result = event.SetParameter(parm,
typeCFTypeRef,
sizeof(elem),
&elem);
CFRelease(elem);
}
return result;
}
static pascal OSStatus HandlePaneEvents(EventHandlerCallRef handlerRef, EventRef eventRef, void *data)
{
wxMacCarbonEvent event(eventRef);
CustomData *dt = (CustomData *) data;
OSStatus result = eventNotHandledErr;
switch (event.GetClass())
{
case kEventClassControl:
{
ControlRef control;
ControlID cid;
control = event.GetParameter<ControlRef>(kEventParamDirectObject, typeControlRef);
if (control == NULL)
{
break;
}
GetControlID(control, &cid);
if (cid.signature != kCustom)
{
break;
}
switch (cid.id)
{
case kChoice:
{
MenuRef menu = GetControlPopupMenuRef(control);
UInt32 v = GetControl32BitValue(control) - 1;
const size_t numFilters = dt->extensions.GetCount();
if (v < (UInt32) dt->extensions.GetCount())
{
dt->currentfilter = v;
NavCustomControl(dt->context, kNavCtlBrowserRedraw, NULL);
}
}
break;
case kButton:
{
dt->me->ClickButton(GetControl32BitValue(dt->choice) - 1);
}
break;
}
}
break;
case kEventClassAccessibility:
{
switch (event.GetKind())
{
case kEventAccessibleGetNamedAttribute:
{
CFStringRef attr;
require_noerr(event.GetParameter(kEventParamAccessibleAttributeName,
typeCFTypeRef,
sizeof(attr),
&attr), ParameterError);
if (false)
{
}
else if (CFStringCompare(attr, kAXRoleAttribute, 0) == kCFCompareEqualTo)
{
CFStringRef role = kAXGroupRole;
result = event.SetParameter(kEventParamAccessibleAttributeValue,
typeCFStringRef,
sizeof(role),
&role);
require_noerr(result, ParameterError);
}
else if (CFStringCompare(attr, kAXRoleDescriptionAttribute, 0) == kCFCompareEqualTo)
{
CFStringRef role = kAXGroupRole;
CFStringRef desc;
desc = HICopyAccessibilityRoleDescription(role, NULL);
if (desc)
{
result = event.SetParameter(kEventParamAccessibleAttributeValue,
typeCFStringRef,
sizeof(desc),
&desc);
CFRelease(desc);
require_noerr(result, ParameterError);
}
}
else if (CFStringCompare(attr, kAXParentAttribute, 0) == kCFCompareEqualTo)
{
HIViewRef viewRef = HIViewGetSuperview(dt->userpane);
if (viewRef)
{
result = SetElement(event, (HIObjectRef) viewRef, 0,
kEventParamAccessibleAttributeValue);
require_noerr(result, ParameterError);
}
}
else if (CFStringCompare(attr, kAXWindowAttribute, 0) == kCFCompareEqualTo)
{
WindowRef winRef = HIViewGetWindow((HIViewRef) dt->userpane);
if (winRef)
{
result = SetElement(event, (HIObjectRef) winRef, 0,
kEventParamAccessibleAttributeValue);
require_noerr(result, ParameterError);
}
}
else if (CFStringCompare(attr, kAXTopLevelUIElementAttribute, 0) == kCFCompareEqualTo)
{
if (dt->window)
{
result = SetElement(event, (HIObjectRef) dt->window, 0,
kEventParamAccessibleAttributeValue);
require_noerr(result, ParameterError);
}
}
else
{
result = eventNotHandledErr;
}
}
break;
}
}
break;
}
ParameterError:
return result;
}
DEFINE_ONE_SHOT_HANDLER_GETTER(HandlePaneEvents);
static void HandleStartEvent(NavCBRecPtr callBackParms, CustomData *data)
{
data->context = callBackParms->context;
CreateUserPaneControl(callBackParms->window, &data->bounds, kControlSupportsEmbedding, &data->userpane);
InstallControlEventHandler(data->userpane,
GetHandlePaneEventsUPP(),
GetEventTypeCount(namedAttrsList),
namedAttrsList,
data,
NULL);
EmbedControl(data->choice, data->userpane);
EmbedControl(data->button, data->userpane);
NavCustomControl(callBackParms->context, kNavCtlAddControl, data->userpane);
HandleAdjustRect(callBackParms, data);
if (data && !(data->defaultLocation).IsEmpty())
{
// Set default location for the modern Navigation APIs
// Apple Technical Q&A 1151
FSSpec theFSSpec;
wxMacFilename2FSSpec(data->defaultLocation, &theFSSpec);
AEDesc theLocation = {typeNull, NULL};
if (noErr == ::AECreateDesc(typeFSS, &theFSSpec, sizeof(FSSpec), &theLocation))
::NavCustomControl(callBackParms->context, kNavCtlSetLocation, (void *) &theLocation);
}
}
static pascal void NavEventProc(NavEventCallbackMessage inSelector,
NavCBRecPtr ioParams,
NavCallBackUserData ioUserData)
{
CustomData * data = (CustomData *) ioUserData ;
switch (inSelector)
{
case kNavCBCustomize:
{
HandleCarbonCustomizeEvent(ioParams, data);
break;
}
case kNavCBStart:
{
HandleStartEvent(ioParams, data);
break;
}
case kNavCBAdjustRect:
{
HandleAdjustRect(ioParams, data);
break;
}
case kNavCBEvent:
{
HandleNormalEvents(ioParams, data);
break;
}
case kNavCBTerminate:
{
data->showing = false;
break;
}
}
}
static void MakeUserDataRec(CustomData *myData, const wxString& filter)
{
myData->currentfilter = 0 ;
if (!filter.IsEmpty())
{
wxString filter2(filter) ;
int filterIndex = 0;
bool isName = true ;
wxString current ;
for( unsigned int i = 0; i < filter2.Len() ; i++ )
{
if( filter2.GetChar(i) == wxT('|') )
{
if( isName )
{
myData->name.Add( current ) ;
}
else
{
myData->extensions.Add( current.MakeUpper() ) ;
++filterIndex ;
}
isName = !isName ;
current = wxEmptyString ;
}
else
{
current += filter2.GetChar(i) ;
}
}
// we allow for compatibility reason to have a single filter expression (like *.*) without
// an explanatory text, in that case the first part is name and extension at the same time
wxASSERT_MSG( filterIndex == 0 || !isName , wxT("incorrect format of format string") ) ;
if ( current.IsEmpty() )
myData->extensions.Add( myData->name[filterIndex] ) ;
else
myData->extensions.Add( current.MakeUpper() ) ;
if ( filterIndex == 0 || isName )
myData->name.Add( current.MakeUpper() ) ;
++filterIndex ;
const size_t extCount = myData->extensions.GetCount();
for ( size_t i = 0 ; i < extCount; i++ )
{
wxUint32 fileType;
wxUint32 creator;
wxString extension = myData->extensions[i];
if (extension.GetChar(0) == '*')
extension = extension.Mid(1); // Remove leading *
if (extension.GetChar(0) == '.')
{
extension = extension.Mid(1); // Remove leading .
}
if (wxFileName::MacFindDefaultTypeAndCreator( extension, &fileType, &creator ))
{
myData->filtermactypes.Add( (OSType)fileType );
}
else
{
myData->filtermactypes.Add( '****' ) ; // We'll fail safe if it's not recognized
}
}
}
}
static Boolean CheckFile( const wxString &filename , OSType type , CustomDataPtr data)
{
wxString file(filename) ;
file.MakeUpper() ;
if ( data->extensions.GetCount() > 0 )
{
int i = data->currentfilter ;
if ( data->extensions[i].Right(2) == wxT(".*") )
return true ;
{
if ( type == (OSType)data->filtermactypes[i] )
return true ;
wxStringTokenizer tokenizer( data->extensions[i] , wxT(";") ) ;
while( tokenizer.HasMoreTokens() )
{
wxString extension = tokenizer.GetNextToken() ;
if ( extension.GetChar(0) == '*' )
extension = extension.Mid(1) ;
if ( file.Len() >= extension.Len() && extension == file.Right(extension.Len() ) )
return true ;
}
}
return false ;
}
return true ;
}
// end wxmac
FileDialog::FileDialog(wxWindow *parent,
const wxString& message,
const wxString& defaultDir,
const wxString& defaultFileName,
const wxString& wildCard,
long style,
const wxPoint& pos)
:wxFileDialogBase(parent, message, defaultDir, defaultFileName, wildCard, style, pos)
{
wxASSERT_MSG( NavServicesAvailable() , wxT("Navigation Services are not running") ) ;
m_callback = NULL;
m_cbdata = NULL;
m_dialogStyle = style;
}
static pascal Boolean CrossPlatformFilterCallback (
AEDesc *theItem,
void *info,
void *callBackUD,
NavFilterModes filterMode
)
{
bool display = true;
CustomDataPtr data = (CustomDataPtr) callBackUD ;
if (filterMode == kNavFilteringBrowserList)
{
NavFileOrFolderInfo* theInfo = (NavFileOrFolderInfo*) info ;
if ( !theInfo->isFolder )
{
if (theItem->descriptorType == typeFSS )
{
FSSpec spec;
memcpy( &spec , *theItem->dataHandle , sizeof(FSSpec) ) ;
wxString file = wxMacMakeStringFromPascal( spec.name ) ;
display = CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType , data ) ;
}
else if ( theItem->descriptorType == typeFSRef )
{
FSRef fsref ;
memcpy( &fsref , *theItem->dataHandle , sizeof(FSRef) ) ;
wxString file = wxMacFSRefToPath( &fsref ) ;
display = CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType , data ) ;
}
}
}
return display;
}
int FileDialog::ShowModal()
{
OSErr err;
NavDialogCreationOptions dialogCreateOptions;
// set default options
::NavGetDefaultDialogCreationOptions(&dialogCreateOptions);
// this was always unset in the old code
dialogCreateOptions.optionFlags &= ~kNavSelectDefaultLocation;
wxMacCFStringHolder message(m_message, GetFont().GetEncoding());
dialogCreateOptions.windowTitle = message;
wxMacCFStringHolder defaultFileName(m_fileName, GetFont().GetEncoding());
dialogCreateOptions.saveFileName = defaultFileName;
NavDialogRef dialog;
NavObjectFilterUPP navFilterUPP = NULL;
CustomData myData;
SetRect(&myData.bounds, 0, 0, 0, 0);
myData.me = this;
myData.window = NULL;
myData.defaultLocation = m_dir;
myData.userpane = NULL;
myData.choice = NULL;
myData.button = NULL;
myData.saveMode = false;
myData.showing = true;
Rect r;
SInt16 base;
SInt16 margin = 3;
SInt16 gap = 0;
MakeUserDataRec(&myData , m_wildCard);
myData.currentfilter = m_filterIndex;
size_t numFilters = myData.extensions.GetCount();
if (numFilters)
{
CreateNewMenu(0, 0, &myData.menu);
for ( size_t i = 0 ; i < numFilters ; ++i )
{
::AppendMenuItemTextWithCFString(myData.menu,
wxMacCFStringHolder(myData.name[i],
GetFont().GetEncoding()),
4,
i,
NULL);
}
SetRect(&r, 0, margin, 0, 0);
CreatePopupButtonControl(NULL, &r, CFSTR("Format:"), -12345, true, -1, teJustLeft, normal, &myData.choice);
SetControlID(myData.choice, &kChoiceID);
SetControlPopupMenuRef(myData.choice, myData.menu);
SetControl32BitMinimum(myData.choice, 1);
SetControl32BitMaximum(myData.choice, myData.name.GetCount());
SetControl32BitValue(myData.choice, myData.currentfilter + 1);
GetBestControlRect(myData.choice, &r, &base);
SizeControl(myData.choice, r.right - r.left, r.bottom - r.top);
UnionRect(&myData.bounds, &r, &myData.bounds);
gap = 15;
HIObjectSetAuxiliaryAccessibilityAttribute((HIObjectRef)myData.choice, 0, kAXDescriptionAttribute, CFSTR("Format"));
}
if (!m_buttonlabel.IsEmpty())
{
wxMacCFStringHolder cfString(wxStripMenuCodes(m_buttonlabel).c_str(), wxFONTENCODING_DEFAULT);
SetRect(&r, myData.bounds.right + gap, margin, 0, 0);
CreatePushButtonControl(NULL, &r, cfString, &myData.button);
SetControlID(myData.button, &kButtonID);
GetBestControlRect(myData.button, &r, &base);
SizeControl(myData.button, r.right - r.left, r.bottom - r.top);
UnionRect(&myData.bounds, &r, &myData.bounds);
}
// Expand bounding rectangle to include a top and bottom margin
myData.bounds.top -= margin;
myData.bounds.bottom += margin;
dialogCreateOptions.optionFlags |= kNavNoTypePopup;
if (m_dialogStyle & wxFD_SAVE)
{
dialogCreateOptions.modality = kWindowModalityWindowModal;
dialogCreateOptions.parentWindow = (WindowRef) GetParent()->MacGetTopLevelWindowRef();
myData.saveMode = true;
if (!numFilters)
{
dialogCreateOptions.optionFlags |= kNavNoTypePopup;
}
dialogCreateOptions.optionFlags |= kNavDontAutoTranslate;
dialogCreateOptions.optionFlags |= kNavDontAddTranslateItems;
// The extension is important
if (numFilters < 2)
dialogCreateOptions.optionFlags |= kNavPreserveSaveFileExtension;
#if TARGET_API_MAC_OSX
if (!(m_dialogStyle & wxFD_OVERWRITE_PROMPT))
{
dialogCreateOptions.optionFlags |= kNavDontConfirmReplacement;
}
#endif
err = ::NavCreatePutFileDialog(&dialogCreateOptions,
// Suppresses the 'Default' (top) menu item
kNavGenericSignature, kNavGenericSignature,
sStandardNavEventFilter,
&myData, // for defaultLocation
&dialog);
}
else
{
//let people select bundles/programs in dialogs
dialogCreateOptions.optionFlags |= kNavSupportPackages;
navFilterUPP = NewNavObjectFilterUPP(CrossPlatformFilterCallback);
err = ::NavCreateGetFileDialog(&dialogCreateOptions,
NULL, // NavTypeListHandle
sStandardNavEventFilter,
NULL, // NavPreviewUPP
navFilterUPP,
(void *) &myData, // inClientData
&dialog);
}
if (err == noErr)
err = ::NavDialogRun(dialog);
if (err == noErr)
{
myData.window = NavDialogGetWindow(dialog);
Rect r;
// This creates our "fake" dialog with the same dimensions as the sheet so
// that Options dialogs will center properly on the sheet. The "fake" dialog
// is never actually seen.
GetWindowBounds(myData.window, kWindowStructureRgn, &r);
wxDialog::Create(NULL, // no parent...otherwise strange things happen
wxID_ANY,
wxEmptyString,
wxPoint(r.left, r.top),
wxSize(r.right - r.left, r.bottom - r.top));
BeginAppModalStateForWindow(myData.window);
while (myData.showing)
{
wxTheApp->MacDoOneEvent();
}
EndAppModalStateForWindow(myData.window);
}
// clean up filter related data, etc.
if (navFilterUPP)
::DisposeNavObjectFilterUPP(navFilterUPP);
if (err != noErr)
return wxID_CANCEL;
NavReplyRecord navReply;
err = ::NavDialogGetReply(dialog, &navReply);
if (err == noErr && navReply.validRecord)
{
AEKeyword theKeyword;
DescType actualType;
Size actualSize;
FSRef theFSRef;
wxString thePath ;
m_filterIndex = myData.currentfilter;
long count;
::AECountItems(&navReply.selection , &count);
for (long i = 1; i <= count; ++i)
{
err = ::AEGetNthPtr(&(navReply.selection), i, typeFSRef, &theKeyword, &actualType,
&theFSRef, sizeof(theFSRef), &actualSize);
if (err != noErr)
break;
if (m_dialogStyle & wxFD_SAVE)
thePath = wxMacFSRefToPath( &theFSRef , navReply.saveFileName ) ;
else
thePath = wxMacFSRefToPath( &theFSRef ) ;
if (!thePath)
{
::NavDisposeReply(&navReply);
return wxID_CANCEL;
}
wxFileName fn = ConvertSlashInFileName(thePath);
if (!fn.HasExt())
{
if (!(m_dialogStyle & FD_NO_ADD_EXTENSION))
{
wxStringTokenizer tokenizer( myData.extensions[m_filterIndex], wxT(";"));
if (tokenizer.HasMoreTokens())
{
wxString extension = tokenizer.GetNextToken().AfterFirst(wxT('.'));
if (extension.Right(2) == wxT("*"))
{
extension = wxEmptyString;
}
fn.SetExt(extension);
}
}
}
m_path = fn.GetFullPath();
m_paths.Add(m_path);
m_fileName = wxFileNameFromPath(m_path);
m_fileNames.Add(m_fileName);
}
// set these to the first hit
m_path = m_paths[0];
m_fileName = wxFileNameFromPath(m_path);
m_dir = wxPathOnly(m_path);
}
::NavDisposeReply(&navReply);
return (err == noErr) ? wxID_OK : wxID_CANCEL;
}
wxString FileDialog::ConvertSlashInFileName(const wxString& filePath)
{
#if TARGET_API_MAC_OSX
wxString path = filePath;
wxString filename;
wxString newPath = filePath;
int pathLen = 1;
while (!wxDirExists(wxPathOnly(newPath)) && ! path.IsEmpty())
{
path = newPath.BeforeLast('/');
filename = newPath.AfterLast('/');
newPath = path;
newPath += ':';
newPath += filename;
}
return newPath;
#else
return filePath;
#endif
}