Implement native open/save dialog on OS X (issue #321)

There were some problems detecting the release of Cmd+O keys after
opening a NSSavePanel. We fixed those problems calling
osx_keyboard_focused/modifiers(0) when we receive a windowDidResignKey
notification.
This commit is contained in:
David Capello 2015-05-28 16:29:01 -03:00
parent 7826e38382
commit 744fc67b28
9 changed files with 235 additions and 18 deletions

View File

@ -244,6 +244,9 @@ static void prepare_window_for_animation(int refresh_view)
_unix_lock_mutex(osx_skip_events_processing_mutex);
osx_skip_events_processing = FALSE;
_unix_unlock_mutex(osx_skip_events_processing_mutex);
if (_keyboard_installed)
osx_keyboard_focused(TRUE, 0);
}
@ -257,6 +260,11 @@ static void prepare_window_for_animation(int refresh_view)
_unix_lock_mutex(osx_skip_events_processing_mutex);
osx_skip_events_processing = TRUE;
_unix_unlock_mutex(osx_skip_events_processing_mutex);
if (_keyboard_installed) {
osx_keyboard_focused(FALSE, 0);
osx_keyboard_modifiers(0);
}
}
@end

View File

@ -44,16 +44,8 @@ std::string show_file_selector(const std::string& title,
std::vector<std::string> tokens;
base::split_string(showExtensions, tokens, ",");
std::string known;
for (const auto& tok : tokens) {
if (!known.empty())
known.push_back(';');
known += "*." + tok;
}
dlg->addFilter(known, "Known file types (" + known + ")");
for (const auto& tok : tokens)
dlg->addFilter("*." + tok, tok + " files (*." + tok + ")");
dlg->addFilter("*.*", "All files (*.*)");
dlg->addFilter(tok, tok + " files (*." + tok + ")");
if (dlg->show(she::instance()->defaultDisplay()))
res = dlg->getFileName();

View File

@ -1,11 +1,11 @@
// SHE library
// Copyright (C) 2012-2014 David Capello
// Copyright (C) 2012-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#import <AppKit/AppKit.h>
#import <IOKit/hid/IOHIDLib.h>
#include <AppKit/AppKit.h>
#include <IOKit/hid/IOHIDLib.h>
#include <allegro.h>
#include <allegro/platform/alosx.h>
@ -16,6 +16,11 @@
#include "she/event.h"
#include "she/system.h"
void* get_osx_window()
{
return osx_window;
}
@interface SheAppDelegate : AllegroAppDelegate
- (BOOL)application: (NSApplication *)theApplication openFile: (NSString *)filename;
@end

View File

@ -41,7 +41,7 @@
#define WM_MOUSEHWHEEL 0x020E
#endif
#elif defined(ALLEGRO_UNIX)
#elif defined ALLEGRO_UNIX
#include <xalleg.h>
#ifdef None
@ -97,6 +97,8 @@ static void resize_callback(RESIZE_DISPLAY_EVENT* ev)
static she::System* g_instance = nullptr;
void* get_osx_window();
namespace she {
class Alleg4EventQueue : public EventQueue {
@ -446,7 +448,8 @@ class Alleg4Display : public Display {
public:
Alleg4Display(int width, int height, int scale)
: m_surface(NULL)
, m_scale(0) {
, m_scale(0)
, m_nativeCursor(kNoCursor) {
unique_display = this;
if (install_mouse() < 0) throw DisplayCreationException(allegro_error);
@ -607,6 +610,10 @@ public:
return m_queue;
}
NativeCursor nativeMouseCursor() override {
return m_nativeCursor;
}
bool setNativeMouseCursor(NativeCursor cursor) override {
int newCursor = MOUSE_CURSOR_NONE;
@ -645,6 +652,7 @@ public:
return false;
}
m_nativeCursor = cursor;
return (show_os_cursor(newCursor) == 0);
}
@ -684,8 +692,10 @@ public:
void* nativeHandle() override {
#ifdef _WIN32
return reinterpret_cast<void*>(win_get_window());
#elif defined __APPLE__
return get_osx_window();
#else
return NULL;
return nullptr;
#endif
}
@ -693,6 +703,7 @@ private:
Surface* m_surface;
int m_scale;
Alleg4EventQueue* m_queue;
NativeCursor m_nativeCursor;
};
class Alleg4System : public CommonSystem {

View File

@ -57,6 +57,7 @@ namespace she {
virtual EventQueue* getEventQueue() = 0;
virtual NativeCursor nativeMouseCursor() = 0;
virtual bool setNativeMouseCursor(NativeCursor cursor) = 0;
virtual void setMousePosition(const gfx::Point& position) = 0;
virtual void captureMouse() = 0;

View File

@ -6,18 +6,185 @@
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include <vector>
#include "she/display.h"
#include "she/keys.h"
#include "she/native_cursor.h"
#include "she/osx/native_dialogs.h"
#include "base/path.h"
@interface OpenSaveHelper : NSObject
{
NSSavePanel* panel;
she::Display* display;
int result;
}
- (id)init;
- (void)setPanel:(NSSavePanel*)panel;
- (void)setDisplay:(she::Display*)display;
- (void)runModal;
- (int)result;
@end
@implementation OpenSaveHelper
- (id)init
{
if (self = [super init]) {
result = NSFileHandlingPanelCancelButton;
}
return self;
}
- (void)setPanel:(NSSavePanel*)newPanel
{
panel = newPanel;
}
- (void)setDisplay:(she::Display*)newDisplay
{
display = newDisplay;
}
// This is executed in the main thread.
- (void)runModal
{
she::NativeCursor oldCursor = display->nativeMouseCursor();
display->setNativeMouseCursor(she::kArrowCursor);
if ([panel isKindOfClass:[NSOpenPanel class]]) {
// As we're using OS X 10.4 framework, it looks like runModal
// doesn't recognize the allowedFileTypes property. So we force it
// using runModalForTypes: selector.
result = [panel runModalForTypes:[panel allowedFileTypes]];
}
else {
result = [panel runModal];
}
display->setNativeMouseCursor(oldCursor);
}
- (int)result
{
return result;
}
@end
namespace she {
class FileDialogOSX : public FileDialog {
public:
FileDialogOSX()
: m_save(false)
{
}
void dispose() override {
delete this;
}
void toOpenFile() override {
m_save = false;
}
void toSaveFile() override {
m_save = true;
}
void setTitle(const std::string& title) override {
m_title = title;
}
void setDefaultExtension(const std::string& extension) override {
m_defExtension = extension;
}
void addFilter(const std::string& extension, const std::string& description) override {
if (m_defExtension.empty())
m_defExtension = extension;
m_filters.push_back(std::make_pair(description, extension));
}
std::string getFileName() override {
return m_filename;
}
void setFileName(const std::string& filename) override {
m_filename = filename;
}
bool show(Display* display) override {
NSSavePanel* panel = nil;
if (m_save) {
panel = [NSSavePanel savePanel];
}
else {
panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
}
[panel setTitle:[NSString stringWithUTF8String:m_title.c_str()]];
[panel setCanCreateDirectories:YES];
[panel setCanChooseDirectories:NO];
std::string defPath = base::get_file_path(m_filename);
std::string defName = base::get_file_name(m_filename);
if (!defPath.empty())
[panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:defPath.c_str()]]];
if (!defName.empty())
[panel setNameFieldStringValue:[NSString stringWithUTF8String:defName.c_str()]];
NSMutableArray* types = [[NSMutableArray alloc] init];
// The first extension in the array is used as the default one.
if (!m_defExtension.empty())
[types addObject:[NSString stringWithUTF8String:m_defExtension.c_str()]];
for (const auto& filter : m_filters)
[types addObject:[NSString stringWithUTF8String:filter.second.c_str()]];
[panel setAllowedFileTypes:types];
OpenSaveHelper* helper = [[OpenSaveHelper alloc] init];
[helper setPanel:panel];
[helper setDisplay:display];
[helper performSelectorOnMainThread:@selector(runModal) withObject:nil waitUntilDone:YES];
bool retValue;
if ([helper result] == NSFileHandlingPanelOKButton) {
NSURL* url = [panel URL];
m_filename = [[url path] UTF8String];
retValue = true;
}
else
retValue = false;
[helper release];
[types release];
return retValue;
}
private:
std::vector<std::pair<std::string, std::string>> m_filters;
std::string m_defExtension;
std::string m_filename;
std::string m_title;
bool m_save;
};
NativeDialogsOSX::NativeDialogsOSX()
{
}
FileDialog* NativeDialogsOSX::createFileDialog()
{
return nullptr;
return new FileDialogOSX();
}
} // namespace she

View File

@ -17,6 +17,7 @@ SkiaDisplay::SkiaDisplay(EventQueue* queue, int width, int height, int scale)
, m_window(m_queue, this)
, m_surface(new SkiaSurface)
, m_customSurface(false)
, m_nativeCursor(kArrowCursor)
{
m_surface->create(width, height);
m_window.setScale(scale);
@ -116,8 +117,14 @@ EventQueue* SkiaDisplay::getEventQueue()
return m_queue;
}
NativeCursor SkiaDisplay::nativeMouseCursor()
{
return m_nativeCursor;
}
bool SkiaDisplay::setNativeMouseCursor(NativeCursor cursor)
{
m_nativeCursor = cursor;
m_window.setNativeMouseCursor(cursor);
return true;
}

View File

@ -52,6 +52,7 @@ public:
bool isMaximized() const override;
void setTitleBar(const std::string& title) override;
EventQueue* getEventQueue() override;
NativeCursor nativeMouseCursor() override;
bool setNativeMouseCursor(NativeCursor cursor) override;
void setMousePosition(const gfx::Point& position) override;
void captureMouse() override;

View File

@ -57,7 +57,7 @@ public:
m_defExtension = base::from_utf8(extension);
m_defFilter = 0;
}
m_filters.push_back(std::make_pair(description, extension));
m_filters.push_back(std::make_pair(extension, description));
}
std::string getFileName() override {
@ -119,12 +119,37 @@ private:
std::wstring getFiltersForGetOpenFileName() const {
std::wstring filters;
// A filter for all known types
filters.append(L"All known file types");
filters.push_back('\0');
bool first = true;
for (const auto& filter : m_filters) {
if (first)
first = false;
else
filters.push_back(';');
filters.append(L"*.");
filters.append(base::from_utf8(filter.first));
filters.push_back('\0');
}
filters.push_back('\0');
// A specific filter for each type
for (const auto& filter : m_filters) {
filters.append(base::from_utf8(filter.second));
filters.push_back('\0');
filters.append(L"*.");
filters.append(base::from_utf8(filter.first));
filters.push_back('\0');
}
// A filter for all files
filters.append(L"All files");
filters.push_back('\0');
filters.append(L"*.*");
filters.push_back('\0');
// End of filter string (two zeros at the end)
filters.push_back('\0');
return filters;
}