Skip to content

Commit

Permalink
Implement FileOpen dialog in UserInteractions (#839)
Browse files Browse the repository at this point in the history
There are increasingly cases where we want to prompt users for a file.
Rather than write this in VSTGUI I'm using the OS specific APIs behind
a UserInteraction common point. As of this writing Mac and Windows work but
ignore the options for starting directory and filter; and Linux returns a
UserError.

Since this move required me to move to cocoa for Mac UserInteractions,
also set up the .clang-format file to work properly with objective c

Closes #829 (but will require us to open another issue for linux and the
undone parts of the API). Pushing this now so I can use it for scale file
opening in the tuning issue.
  • Loading branch information
baconpaul authored Apr 14, 2019
1 parent fb1f9ea commit fcc8c43
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 82 deletions.
36 changes: 34 additions & 2 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

---
Language: Cpp
BasedOnStyle: LLVM
IndentWidth: 3
Expand Down Expand Up @@ -29,4 +29,36 @@ BinPackParameters: false
AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AlwaysBreakAfterReturnType: None
BreakBeforeTernaryOperators: true
BreakBeforeTernaryOperators: true
---
Language: ObjC
BasedOnStyle: LLVM
IndentWidth: 3
CompactNamespaces: true
FixNamespaceComments: true
PointerAlignment: Left
SortIncludes: false
Standard: Cpp11
IndentCaseLabels: false
MaxEmptyLinesToKeep: 1
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterFunction: true
AfterControlStatement: true
AfterEnum: true
AfterStruct: true
AfterNamespace: true
SplitEmptyFunction: false
AfterUnion: true
BeforeCatch: true
BeforeElse: true

AccessModifierOffset: -3
ColumnLimit: 100
BinPackParameters: false

AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AlwaysBreakAfterReturnType: None
BreakBeforeTernaryOperators: true
6 changes: 6 additions & 0 deletions src/common/UserInteractions.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <string>
#include <atomic>
#include <functional>
#include "SurgeError.h"

class SurgeGUIEditor;
Expand Down Expand Up @@ -48,6 +49,11 @@ void openURL(const std::string &url);
// etc)
void openFolderInFileBrowser(const std::string& folder);

// Prompt for a file to open; call the callback if you pick one
void promptFileOpenDialog(const std::string& initialDirectory,
const std::string& filterSuffix,
std::function<void(std::string)> callbackOnOpen,
SurgeGUIEditor* guiEditor = nullptr);
};

};
8 changes: 8 additions & 0 deletions src/headless/UserInteractionsHeadless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ void openURL(const std::string &url)
{
}

void promptFileOpenDialog(const std::string& initialDirectory,
const std::string& filterSuffix,
std::function<void(std::string)> callbackOnOpen,
SurgeGUIEditor* guiEditor)
{
UserInteractions::promptError("OpenFileDialog is unimplemented in this version of Surge. Sorry!",
"Unimplemented Function", guiEditor);
}
};

};
8 changes: 8 additions & 0 deletions src/linux/UserInteractionsLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ void openFolderInFileBrowser(const std::string& folder)
UserInteractions::openURL(url);
}

void promptFileOpenDialog(const std::string& initialDirectory,
const std::string& filterSuffix,
std::function<void(std::string)> callbackOnOpen,
SurgeGUIEditor* guiEditor)
{
UserInteractions::promptError("OpenFileDialog is unimplemented in this version of Surge. Sorry!",
"Unimplemented Function", guiEditor);
}
};

};
80 changes: 0 additions & 80 deletions src/mac/UserInteractionsMac.cpp

This file was deleted.

100 changes: 100 additions & 0 deletions src/mac/UserInteractionsMac.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "UserInteractions.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <ApplicationServices/ApplicationServices.h>
#include <AppKit/AppKit.h>

namespace Surge
{

namespace UserInteractions
{

void promptError(const std::string& message, const std::string& title, SurgeGUIEditor* guiEditor)
{
CFStringRef cfT =
CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8);
CFStringRef cfM =
CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8);

SInt32 nRes = 0;
CFUserNotificationRef pDlg = NULL;
const void* keys[] = {kCFUserNotificationAlertHeaderKey, kCFUserNotificationAlertMessageKey};
const void* vals[] = {cfT, cfM};

CFDictionaryRef dict =
CFDictionaryCreate(0, keys, vals, sizeof(keys) / sizeof(*keys),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

pDlg = CFUserNotificationCreate(kCFAllocatorDefault, 0, kCFUserNotificationStopAlertLevel, &nRes,
dict);

CFRelease(cfT);
CFRelease(cfM);
}

void promptError(const Surge::Error& error, SurgeGUIEditor* guiEditor)
{
promptError(error.getMessage(), error.getTitle());
}

MessageResult
promptOKCancel(const std::string& message, const std::string& title, SurgeGUIEditor* guiEditor)
{
CFStringRef cfT =
CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8);
CFStringRef cfM =
CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8);

CFOptionFlags responseFlags;
CFUserNotificationDisplayAlert(0, kCFUserNotificationPlainAlertLevel, 0, 0, 0, cfT, cfM,
CFSTR("OK"), CFSTR("Cancel"), 0, &responseFlags);

CFRelease(cfT);
CFRelease(cfM);

if ((responseFlags & 0x3) != kCFUserNotificationDefaultResponse)
return UserInteractions::CANCEL;
return UserInteractions::OK;
}

void openURL(const std::string& url_str)
{
CFURLRef url = CFURLCreateWithBytes(NULL, // allocator
(UInt8*)url_str.c_str(), // URLBytes
url_str.length(), // length
kCFStringEncodingASCII, // encoding
NULL // baseURL
);
LSOpenCFURLRef(url, 0);
CFRelease(url);
}

void openFolderInFileBrowser(const std::string& folder)
{
std::string url = "file://" + folder;
UserInteractions::openURL(url);
}

void promptFileOpenDialog(const std::string& initialDirectory,
const std::string& filterSuffix,
std::function<void(std::string)> callbackOnOpen,
SurgeGUIEditor* guiEditor)
{
// FIXME TODO - support the filterSuffix and initialDirectory
NSOpenPanel* panel = [NSOpenPanel openPanel];

[panel beginWithCompletionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
NSURL* theDoc = [[panel URLs] objectAtIndex:0];
NSString* path = [theDoc path];
std::string pstring([path UTF8String]);
callbackOnOpen(pstring);
}
}];
}

};

};
28 changes: 28 additions & 0 deletions src/windows/UserInteractionsWin.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <Windows.h>
#include <Commdlg.h>
#include <string>
#include "UserInteractions.h"
#include "SurgeGUIEditor.h"
Expand Down Expand Up @@ -46,6 +47,33 @@ void openFolderInFileBrowser(const std::string& folder)
UserInteractions::openURL(url);
}

void promptFileOpenDialog(const std::string& initialDirectory,
const std::string& filterSuffix,
std::function<void(std::string)> callbackOnOpen,
SurgeGUIEditor* guiEditor)
{
// With many thanks to
// https://www.daniweb.com/programming/software-development/code/217307/a-simple-getopenfilename-example
char szFile[1024];
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "All\0*.*\0";
ofn.nFilterIndex = 0;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

if (GetOpenFileName(&ofn))
{
callbackOnOpen(ofn.lpstrFile);
}
}
};

};
Expand Down

0 comments on commit fcc8c43

Please sign in to comment.