562 lines
19 KiB
C++
562 lines
19 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "shader.h"
|
|
#include "shape.h"
|
|
#include "texture.h"
|
|
#include "image.h"
|
|
#include "app.h"
|
|
#include "keymap.h"
|
|
#include "main.h"
|
|
#include "settings.h"
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <Cocoa/Cocoa.h>
|
|
#include <CoreVideo/CoreVideo.h>
|
|
#include <OpenGL/OpenGL.h>
|
|
#import "objc_utils.h"
|
|
#import <HockeySDK/HockeySDK.h>
|
|
|
|
#include <deque>
|
|
#include <chrono>
|
|
|
|
@implementation View
|
|
{
|
|
NSSharingService *airdrop_service;
|
|
}
|
|
- (std::string)clipboard_get_string
|
|
{
|
|
NSString* ns = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
|
|
const char* ptr = [ns cStringUsingEncoding:NSUTF8StringEncoding];
|
|
return std::string(ptr);
|
|
}
|
|
- (bool)clipboard_set_string:(const std::string &)s
|
|
{
|
|
NSString* ns = [NSString stringWithUTF8String:s.c_str()];
|
|
[[NSPasteboard generalPasteboard] setString:ns forType:NSPasteboardTypeString];
|
|
return true;
|
|
}
|
|
- (void)hockeyapp_crash
|
|
{
|
|
[[[BITHockeyManager sharedHockeyManager] crashManager] generateTestCrash];
|
|
}
|
|
- (void)async_lock
|
|
{
|
|
CGLLockContext([glctx CGLContextObj]);
|
|
[glctx makeCurrentContext];
|
|
}
|
|
- (void)async_unlock
|
|
{
|
|
CGLUnlockContext([glctx CGLContextObj]);
|
|
}
|
|
- (void)async_swap
|
|
{
|
|
CGLFlushDrawable([glctx CGLContextObj]);
|
|
}
|
|
- (void)close
|
|
{
|
|
[[NSApplication sharedApplication] terminate:nil];
|
|
}
|
|
- (void)show_cursor:(bool)visible
|
|
{
|
|
if (cursor_visible == visible)
|
|
return;
|
|
cursor_visible = visible;
|
|
if (cursor_inside && !cursor_ignore)
|
|
visible ? [NSCursor unhide] : [NSCursor hide];
|
|
}
|
|
- (std::string)pick_file:(NSArray<NSString*>*)types
|
|
{
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
[panel setCanChooseFiles:YES];
|
|
[panel setCanChooseDirectories:NO];
|
|
[panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed
|
|
|
|
//NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
|
|
[panel setAllowedFileTypes:types];
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
if (!cursor_visible)
|
|
[NSCursor unhide];
|
|
|
|
NSInteger clicked = [panel runModal];
|
|
|
|
[self addTrackingArea:trackingArea];
|
|
if (!cursor_visible)
|
|
[NSCursor hide];
|
|
|
|
std::string ret;
|
|
if (clicked == NSFileHandlingPanelOKButton)
|
|
{
|
|
for (NSURL *url in [panel URLs])
|
|
{
|
|
LOG("selected file: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]);
|
|
ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
- (std::string)pick_dir
|
|
{
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
[panel setCanChooseFiles:NO];
|
|
[panel setCanChooseDirectories:YES];
|
|
[panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
if (!cursor_visible)
|
|
[NSCursor unhide];
|
|
|
|
NSInteger clicked = [panel runModal];
|
|
|
|
[self addTrackingArea:trackingArea];
|
|
if (!cursor_visible)
|
|
[NSCursor hide];
|
|
|
|
std::string ret;
|
|
if (clicked == NSFileHandlingPanelOKButton)
|
|
{
|
|
for (NSURL *url in [panel URLs])
|
|
{
|
|
LOG("selected path: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]);
|
|
ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
- (void)share_file:(NSString*)file_path
|
|
{
|
|
NSURL *url = [NSURL fileURLWithPath:file_path];
|
|
NSArray *objectsToShare = @[url];
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
cursor_ignore = true;
|
|
if (!cursor_visible)
|
|
[NSCursor unhide];
|
|
|
|
[airdrop_service performWithItems:objectsToShare];
|
|
}
|
|
- (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
|
|
{
|
|
return self.window;
|
|
}
|
|
- (NSView *)anchoringViewForSharingService:(NSSharingService *)sharingService showRelativeToRect:(NSRect *)positioningRect preferredEdge:(NSRectEdge *)preferredEdge
|
|
{
|
|
return self;
|
|
}
|
|
-(void)sharingService:(NSSharingService *)sharingService willShareItems:(NSArray *)items
|
|
{
|
|
}
|
|
- (void)sharingService:(NSSharingService *)sharingService didFailToShareItems:(NSArray *)items error:(NSError *)error
|
|
{
|
|
[self addTrackingArea:trackingArea];
|
|
cursor_ignore = false;
|
|
if (!cursor_visible)
|
|
[NSCursor hide];
|
|
}
|
|
- (void)sharingService:(NSSharingService *)sharingService didShareItems:(NSArray *)items
|
|
{
|
|
[self addTrackingArea:trackingArea];
|
|
cursor_ignore = false;
|
|
if (!cursor_visible)
|
|
[NSCursor hide];
|
|
}
|
|
- (instancetype)initWithFrame:(NSRect)frameRect
|
|
{
|
|
airdrop_service = [NSSharingService sharingServiceNamed:NSSharingServiceNameSendViaAirDrop];
|
|
airdrop_service.delegate = self;
|
|
cursor_visible = CGCursorIsVisible();
|
|
cursor_inside = false;
|
|
cursor_ignore = false;
|
|
NSOpenGLPixelFormatAttribute attrs[] =
|
|
{
|
|
NSOpenGLPFADoubleBuffer,
|
|
NSOpenGLPFADepthSize, 24,
|
|
// Must specify the 3.2 Core Profile to use OpenGL 3.2
|
|
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
|
// Multisample
|
|
NSOpenGLPFAMultisample,
|
|
NSOpenGLPFASamples, 2,
|
|
NSOpenGLPFASampleBuffers, 1,
|
|
0
|
|
};
|
|
NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
|
|
NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
|
|
self = [super initWithFrame:frameRect pixelFormat:pf];
|
|
[self setPixelFormat:pf];
|
|
[self setOpenGLContext:context];
|
|
|
|
[self updateTrackingAreas];
|
|
return self;
|
|
}
|
|
- (void)prepareOpenGL
|
|
{
|
|
[super prepareOpenGL];
|
|
|
|
NSLog(@"prepare");
|
|
|
|
// Synchronize buffer swaps with vertical refresh rate
|
|
GLint swapInt = 1;
|
|
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
|
|
|
|
glctx = [self openGLContext];
|
|
|
|
App::I->render_thread_start();
|
|
App::I->render_sync();
|
|
}
|
|
|
|
- (void)terminateGL
|
|
{
|
|
App::I->ui_thread_stop();
|
|
App::I->render_thread_stop();
|
|
App::I->terminate();
|
|
delete App::I;
|
|
App::I = nullptr;
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect
|
|
{
|
|
return;
|
|
}
|
|
- (void)updateTrackingAreas
|
|
{
|
|
if(trackingArea != nil)
|
|
[self removeTrackingArea:trackingArea];
|
|
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
|
options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
|
|
owner:self userInfo:nil];
|
|
[self addTrackingArea:trackingArea];
|
|
}
|
|
- (void)reshape
|
|
{
|
|
[super reshape];
|
|
|
|
// Get the view size in Points
|
|
NSRect viewRectPoints = [self bounds];
|
|
|
|
// We draw on a secondary thread through the display link. However, when
|
|
// resizing the view, -drawRect is called on the main thread.
|
|
// Add a mutex around to avoid the threads accessing the context
|
|
// simultaneously when resizing.
|
|
|
|
#if SUPPORT_RETINA_RESOLUTION
|
|
|
|
// Rendering at retina resolutions will reduce aliasing, but at the potential
|
|
// cost of framerate and battery life due to the GPU needing to render more
|
|
// pixels.
|
|
|
|
// Any calculations the renderer does which use pixel dimentions, must be
|
|
// in "retina" space. [NSView convertRectToBacking] converts point sizes
|
|
// to pixel sizes. Thus the renderer gets the size in pixels, not points,
|
|
// so that it can set it's viewport and perform and other pixel based
|
|
// calculations appropriately.
|
|
// viewRectPixels will be larger than viewRectPoints for retina displays.
|
|
// viewRectPixels will be the same as viewRectPoints for non-retina displays
|
|
NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints];
|
|
|
|
#else //if !SUPPORT_RETINA_RESOLUTION
|
|
|
|
// App will typically render faster and use less power rendering at
|
|
// non-retina resolutions since the GPU needs to render less pixels.
|
|
// There is the cost of more aliasing, but it will be no-worse than
|
|
// on a Mac without a retina display.
|
|
|
|
// Points:Pixels is always 1:1 when not supporting retina resolutions
|
|
NSRect viewRectPixels = viewRectPoints;
|
|
|
|
#endif // !SUPPORT_RETINA_RESOLUTION
|
|
App::I->ui_task_async([=]
|
|
{
|
|
App::I->resize(viewRectPixels.size.width, viewRectPixels.size.height);
|
|
});
|
|
}
|
|
- (void)renewGState
|
|
{
|
|
// Called whenever graphics state updated (such as window resize)
|
|
|
|
// OpenGL rendering is not synchronous with other rendering on the OSX.
|
|
// Therefore, call disableScreenUpdatesUntilFlush so the window server
|
|
// doesn't render non-OpenGL content in the window asynchronously from
|
|
// OpenGL content, which could cause flickering. (non-OpenGL content
|
|
// includes the title bar and drawing done by the app with other APIs)
|
|
[[self window] disableScreenUpdatesUntilFlush];
|
|
|
|
[super renewGState];
|
|
}
|
|
- (void)mouseDown:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,p=theEvent.pressure] {
|
|
App::I->mouse_down(0, mouseLoc.x, App::I->height - mouseLoc.y - 1, p, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
- (void)rightMouseDown:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,p=theEvent.pressure] {
|
|
App::I->mouse_down(1, mouseLoc.x, App::I->height - mouseLoc.y - 1, p, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
- (void)mouseUp:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc] {
|
|
App::I->mouse_up(0, mouseLoc.x, App::I->height - mouseLoc.y - 1, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
- (void)rightMouseUp:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc] {
|
|
App::I->mouse_up(1, mouseLoc.x, App::I->height - mouseLoc.y - 1, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
- (void)mouseMoved:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,p=theEvent.pressure] {
|
|
App::I->mouse_move(mouseLoc.x, App::I->height - mouseLoc.y - 1, p, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
-(void)mouseDragged:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,p=theEvent.pressure] {
|
|
App::I->mouse_move(mouseLoc.x, App::I->height - mouseLoc.y - 1, p, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
- (void)rightMouseDragged:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,p=theEvent.pressure] {
|
|
App::I->mouse_move(mouseLoc.x, App::I->height - mouseLoc.y - 1, p, kEventSource::Mouse, 0);
|
|
});
|
|
}
|
|
-(void)mouseExited:(NSEvent *)event
|
|
{
|
|
if (!cursor_visible)
|
|
[NSCursor unhide];
|
|
cursor_inside = false;
|
|
}
|
|
-(void)mouseEntered:(NSEvent *)event
|
|
{
|
|
if (!cursor_visible)
|
|
[NSCursor hide];
|
|
cursor_inside = true;
|
|
}
|
|
- (void)scrollWheel:(NSEvent *)theEvent
|
|
{
|
|
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
App::I->ui_task_async([mouseLoc,d=[theEvent deltaY]] {
|
|
App::I->mouse_scroll(mouseLoc.x, App::I->height - mouseLoc.y - 1, d);
|
|
});
|
|
}
|
|
- (void)keyDown:(NSEvent *)theEvent
|
|
{
|
|
auto keyCode = [theEvent keyCode];
|
|
auto chars = [theEvent characters];
|
|
const char* c_str = [chars cStringUsingEncoding:NSASCIIStringEncoding];
|
|
std::string s = c_str ? c_str : "";
|
|
App::I->ui_task_async([keyCode, s] {
|
|
if (App::I->keys[(int)kKey::KeyCtrl])
|
|
{
|
|
App::I->key_down(convert_key(keyCode));
|
|
App::I->key_up(convert_key(keyCode));
|
|
}
|
|
else
|
|
{
|
|
App::I->key_down(convert_key(keyCode));
|
|
if (!s.empty())
|
|
App::I->key_char(s[0]);
|
|
}
|
|
});
|
|
}
|
|
- (void)keyUp:(NSEvent *)theEvent
|
|
{
|
|
auto keyCode = [theEvent keyCode];
|
|
App::I->ui_task_async([keyCode] {
|
|
App::I->key_up(convert_key(keyCode));
|
|
});
|
|
}
|
|
- (void)flagsChanged:(NSEvent *)event
|
|
{
|
|
[super flagsChanged:event];
|
|
|
|
auto flags = [event modifierFlags];
|
|
App::I->ui_task_async([flags] {
|
|
static bool pressed_shift = false;
|
|
if (pressed_shift != (flags & NSShiftKeyMask))
|
|
{
|
|
pressed_shift = (flags & NSShiftKeyMask);
|
|
pressed_shift ? App::I->key_down(kKey::KeyShift) : App::I->key_up(kKey::KeyShift);
|
|
}
|
|
static bool pressed_alt = false;
|
|
if (pressed_alt != (flags & NSAlternateKeyMask))
|
|
{
|
|
pressed_alt = (flags & NSAlternateKeyMask);
|
|
pressed_alt ? App::I->key_down(kKey::KeyAlt) : App::I->key_up(kKey::KeyAlt);
|
|
}
|
|
static bool pressed_cmd = false;
|
|
if (pressed_cmd != (flags & NSCommandKeyMask))
|
|
{
|
|
pressed_cmd = (flags & NSCommandKeyMask);
|
|
pressed_cmd ? App::I->key_down(kKey::KeyCtrl) : App::I->key_up(kKey::KeyCtrl);
|
|
}
|
|
});
|
|
}
|
|
@end
|
|
|
|
@implementation Window
|
|
@end
|
|
|
|
@implementation Controller
|
|
- (void)windowWillClose:(NSNotification *)notification
|
|
{
|
|
[[NSApplication sharedApplication] terminate:nil];
|
|
}
|
|
-(BOOL)windowShouldClose:(NSWindow *)sender
|
|
{
|
|
return App::I->request_close();
|
|
}
|
|
@end
|
|
|
|
@implementation AppOSX
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
[self setActivationPolicy:NSApplicationActivationPolicyRegular]; // make it to the front
|
|
[self setDelegate:self];
|
|
return self;
|
|
}
|
|
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
|
|
{
|
|
LOG("open file %s", [filename UTF8String]);
|
|
if (App::I)
|
|
{
|
|
std::string path = [filename UTF8String];
|
|
App::I->ui_task_async([=]
|
|
{
|
|
App::I->open_document(path);
|
|
});
|
|
}
|
|
return YES;
|
|
}
|
|
- (void)applicationWillTerminate:(NSNotification *)notification
|
|
{
|
|
[view terminateGL];
|
|
[window close];
|
|
}
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
|
{
|
|
//[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];
|
|
|
|
[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"1500ff4a32eb4076be3b5147e2d8e27e"];
|
|
// Do some additional configuration if needed here
|
|
[[BITHockeyManager sharedHockeyManager] startManager];
|
|
|
|
App::I->initLog();
|
|
App::I->create();
|
|
NSRect r = NSMakeRect(0, 0, App::I->width, App::I->height);
|
|
if (Settings::has("window-rect"))
|
|
{
|
|
glm::ivec4 wr = Settings::value<Serializer::IVec4>("window-rect");
|
|
r.origin = CGPointMake(wr.x, wr.y);
|
|
r.size = CGSizeMake(wr.z, wr.w);
|
|
}
|
|
|
|
view = [[View alloc] initWithFrame:r];
|
|
controller = [[Controller alloc] initWithWindow:window];
|
|
App::I->osx_view = view;
|
|
App::I->zoom = Settings::value_or<Serializer::Float>("ui-scale", (float)view.layer.contentsScale);
|
|
|
|
auto style = NSTitledWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask|NSClosableWindowMask;
|
|
window = [[Window alloc] initWithContentRect:r styleMask:style backing:NSBackingStoreBuffered defer:NO];
|
|
[window setDelegate:controller];
|
|
[window setTitle:[NSString stringWithUTF8String:g_window_title]];
|
|
[window center];
|
|
[window makeKeyAndOrderFront:controller];
|
|
[window setContentView:view];
|
|
[window setAcceptsMouseMovedEvents:true];
|
|
[window makeFirstResponder:view];
|
|
view->wnd = window;
|
|
|
|
auto menubar = [NSMenu new];
|
|
auto appMenuItem = [NSMenuItem new];
|
|
[menubar addItem:appMenuItem];
|
|
[self setMainMenu:menubar];
|
|
|
|
auto appMenu = [NSMenu new];
|
|
auto appName = [[NSProcessInfo processInfo] processName];
|
|
auto quitTitle = [@"Quit " stringByAppendingString:appName];
|
|
auto quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle
|
|
action:@selector(terminate:) keyEquivalent:@"q"];
|
|
[appMenu addItem:quitMenuItem];
|
|
[appMenuItem setSubmenu:appMenu];
|
|
|
|
LOG("app launched");
|
|
App::I->ui_thread_start();
|
|
}
|
|
- (void)save_ui_state
|
|
{
|
|
Settings::set("window-rect", Serializer::IVec4({window.frame.origin.x,
|
|
window.frame.origin.y, window.frame.size.width, window.frame.size.height}));
|
|
}
|
|
- (void)init_dirs
|
|
{
|
|
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES);
|
|
NSString* docpath = [(NSString*)[paths objectAtIndex:0] stringByAppendingString:@"/PanoPainter"];
|
|
|
|
NSError* err = nil;
|
|
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:docpath withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
App::I->data_path = [docpath cStringUsingEncoding:NSASCIIStringEncoding];
|
|
|
|
NSString* recpath = [docpath stringByAppendingString:@"/rec"];
|
|
App::I->rec_path = [recpath cStringUsingEncoding:NSASCIIStringEncoding];
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:recpath withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
// brushes
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes"] withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating brushes path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating brushes thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
// textures
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns"] withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating patterns path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating patterns thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
// settings
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/settings"] withIntermediateDirectories:YES attributes:nil error:&err])
|
|
{
|
|
LOG("error creating settings path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
}
|
|
@end
|
|
|
|
int main(int argc, const char * argv[])
|
|
{
|
|
install_global_handlers();
|
|
|
|
App::I = new App;
|
|
|
|
if (!App::I->check_license())
|
|
return 0;
|
|
|
|
AppOSX* app = [AppOSX sharedApplication];
|
|
App::I->osx_app = app;
|
|
[app run];
|
|
|
|
return 0;
|
|
}
|