1// Copyright (c) 2015 Sergio Gonzalez. All rights reserved.
2// License: https://github.com/serge-rgb/milton#license
3
4#include <mach-o/dyld.h>
5
6#define Rect MacRect
7// #define ALIGN MacALIGN
8#include <Cocoa/Cocoa.h>
9#undef Rect
10#undef ALIGN
11
12#include <errno.h>
13#include <limits.h>
14#include <string.h>
15#include <time.h>
16#include <sys/stat.h>
17#include <sys/time.h>
18#include "platform.h"
19#include "memory.h"
20
21#include <dlfcn.h>
22
23
24#define MAX_PATH PATH_MAX
25
26
27#include <AppKit/AppKit.h>
28
29void
30platform_init(PlatformState* platform, SDL_SysWMinfo* sysinfo)
31{
32
33}
34
35void
36platform_deinit(PlatformState* platform)
37{
38
39}
40
41void
42platform_event_tick()
43{
44}
45
46void
47platform_setup_cursor(Arena* arena, PlatformState* platform)
48{
49
50}
51
52void
53platform_cursor_set_position(PlatformState* platform, v2i pos)
54{
55    // TODO: Implement
56}
57
58v2i
59platform_cursor_get_position(PlatformState* platform)
60{
61    // TODO: Implement
62    return v2i{};
63}
64
65
66void*
67platform_get_gl_proc(char* name)
68{
69    static void* image = NULL;
70
71    if (NULL == image) {
72        image = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_LAZY);
73    }
74    return (image ? dlsym(image, (const char *)name) : NULL);
75
76}
77
78char*
79mac_panel(NSSavePanel *panel, FileKind kind)
80{
81    switch (kind) {
82        case FileKind_IMAGE:
83            panel.allowedFileTypes = @[(NSString*)kUTTypeJPEG, (NSString*)kUTTypePNG];
84            break;
85        case FileKind_MILTON_CANVAS:
86            panel.allowedFileTypes = @[@"mlt"];
87            break;
88        default:
89            break;
90    }
91    if ([panel runModal] != NSModalResponseOK) {
92        return nullptr;
93    }
94    return strdup(panel.URL.path.fileSystemRepresentation);
95}
96
97NSAlert*
98mac_alert(const char* info, const char* title)
99{
100    NSAlert *alert = [[NSAlert alloc] init];
101    alert.messageText = [NSString stringWithUTF8String:title ? title : ""];
102    alert.informativeText = [NSString stringWithUTF8String:info ? info : ""];
103    return alert;
104}
105
106void
107platform_dialog_mac(char* info, char* title)
108{
109    @autoreleasepool {
110        [mac_alert(info, title) runModal];
111    }
112}
113
114int32_t
115platform_dialog_yesno_mac(char* info, char* title)
116{
117    @autoreleasepool {
118        NSAlert *alert = mac_alert(info, title);
119        [alert addButtonWithTitle:NSLocalizedString(@"Yes", nil)];
120        [alert addButtonWithTitle:NSLocalizedString(@"No", nil)];
121        return [alert runModal] == NSAlertFirstButtonReturn;
122    }
123}
124
125YesNoCancelAnswer
126platform_dialog_yesnocancel(char* info, char* title)
127{
128    @autoreleasepool {
129        NSAlert *alert = mac_alert(info, title);
130        [alert addButtonWithTitle:NSLocalizedString(@"Yes", nil)];
131        [alert addButtonWithTitle:NSLocalizedString(@"No", nil)];
132        [alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];
133        NSModalResponse result = [alert runModal];
134        if (result == NSAlertFirstButtonReturn) {
135          return YesNoCancelAnswer::YES_;
136        }
137        else if (result == NSAlertSecondButtonReturn) {
138          return YesNoCancelAnswer::NO_;
139        }
140        return YesNoCancelAnswer::CANCEL_;
141    }
142}
143
144char*
145platform_open_dialog_mac(FileKind kind)
146{
147    @autoreleasepool {
148        return mac_panel([NSOpenPanel openPanel], kind);
149    }
150}
151
152char*
153platform_save_dialog_mac(FileKind kind)
154{
155    @autoreleasepool {
156        return mac_panel([NSSavePanel savePanel], kind);
157    }
158}
159
160EasyTabResult
161platform_handle_sysevent(PlatformState* platform, SDL_SysWMEvent* sysevent)
162{
163    mlt_assert(sysevent->msg->subsystem == SDL_SYSWM_COCOA);
164    return EASYTAB_EVENT_NOT_HANDLED;
165}
166
167void
168platform_open_link_mac(char* link)
169{
170    @autoreleasepool {
171        NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:link]];
172        [[NSWorkspace sharedWorkspace] openURL:url];
173    }
174}
175
176NSWindow*
177macos_get_window(PlatformState* ps)
178{
179    NSWindow* result = NULL;
180    SDL_Window* window = ps->window;
181    if (window) {
182        SDL_SysWMinfo info = {};
183        if (SDL_GetWindowWMInfo(window, &info)) {
184            if (info.subsystem == SDL_SYSWM_COCOA) {
185                NSWindow* nsw = info.info.cocoa.window;
186                result = nsw;
187            }
188        }
189    }
190    return result;
191
192}
193
194void
195platform_point_to_pixel(PlatformState* ps, v2l* inout)
196{
197    NSRect rect;
198    rect.origin = {};
199    rect.size.width = inout->w;
200    rect.size.height = inout->h;
201    auto* window = macos_get_window(ps);
202    NSRect backing = [window convertRectToBacking:rect];
203
204    inout->x = backing.size.width;
205    inout->y = backing.size.height;
206}
207
208void
209platform_point_to_pixel_i(PlatformState* ps, v2i* inout)
210{
211    v2l long_inout = { inout->x, inout->y };
212    platform_point_to_pixel(ps, &long_inout);
213    *inout = (v2i){(int)long_inout.x, (int)long_inout.y};
214}
215
216void
217platform_pixel_to_point(PlatformState* ps, v2l* inout)
218{
219    NSRect rect;
220    rect.origin = {};
221    rect.size.width = inout->w;
222    rect.size.height = inout->h;
223    auto* window = macos_get_window(ps);
224    NSRect pointrect = [window convertRectFromBacking:rect];
225
226    inout->x = pointrect.size.width;
227    inout->y = pointrect.size.height;
228
229}
230
231// IMPLEMENT ====
232float
233perf_count_to_sec(u64 counter)
234{
235    // Input as nanoseconds
236    return (float)counter * 1e-9;
237}
238u64
239perf_counter()
240{
241    // clock_gettime() on macOS is only supported on macOS Sierra and later.
242    // For older macOS operating systems, mach_absolute_time() will be need to be used.
243    timespec tp;
244    int res = clock_gettime(CLOCK_REALTIME, &tp);
245
246    // TODO: Check errno and provide more informations
247    if ( res ) {
248        milton_log("Something went wrong with clock_gettime\n");
249    }
250
251    return tp.tv_nsec;
252}
253
254b32
255platform_delete_file_at_config(PATH_CHAR* fname, int error_tolerance)
256{
257    char fname_at_config[MAX_PATH];
258    strncpy(fname_at_config, fname, MAX_PATH);
259    platform_fname_at_config(fname_at_config, MAX_PATH*sizeof(char));
260    int res = remove(fname_at_config);
261    b32 result = true;
262    if (res != 0)
263    {
264        result = false;
265        // Delete failed for some reason.
266        if ((error_tolerance == DeleteErrorTolerance_OK_NOT_EXIST) &&
267            (errno == EEXIST || errno == ENOENT))
268        {
269            result = true;
270        }
271    }
272
273    return result;
274}
275
276void
277platform_dialog(char* info, char* title)
278{
279    extern void platform_dialog_mac(char*, char*);
280    platform_dialog_mac(info, title);
281    return;
282}
283
284b32
285platform_dialog_yesno(char* info, char* title)
286{
287    extern b32 platform_dialog_yesno_mac(char*, char*);
288    platform_dialog_yesno_mac(info, title);
289    return false;
290}
291
292void
293platform_fname_at_config(PATH_CHAR* fname, size_t len)
294{
295    NSBundle* bundle = [NSBundle mainBundle];
296    const char* respath = [[bundle resourcePath] UTF8String];
297
298    char* string_copy = (char*)mlt_calloc(1, len, "Strings");
299    if ( string_copy ) {
300        strncpy(string_copy, fname, len);
301
302        b32 respath_failed = false;
303        if (respath) {
304            // Create the Resources directory if it doesn't exist.
305            int mkerr = mkdir(respath, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
306            if (mkerr == 0) {
307                milton_log("Created Resources path.\n");
308            }
309            else if (mkerr == -1) {
310                int err = errno;
311                if (err != EEXIST) {
312                    milton_log("mkdir failed with unexpected error %d\n", mkerr);
313                    respath_failed = true;
314                }
315            }
316        }
317        if (!respath || respath_failed) {
318            char* home = getenv("HOME");
319            snprintf(fname, len,  "%s/.milton", home);
320            mkdir(fname, S_IRWXU);
321            snprintf(fname, len,  "%s/%s", fname, string_copy);
322        }
323        else {
324            snprintf(fname, len, "%s/%s", respath, string_copy);
325        }
326
327        mlt_free(string_copy, "Strings");
328    }
329}
330
331void
332platform_fname_at_exe(PATH_CHAR* fname, size_t len)
333{
334    u32 bufsize = (u32)len;
335    char buffer[MAX_PATH] = {};
336    strncpy(buffer, fname, MAX_PATH);
337    _NSGetExecutablePath(fname, &bufsize);
338    {  // Remove the executable name
339        PATH_CHAR* last_slash = fname;
340        for(PATH_CHAR* iter = fname;
341            *iter != '\0';
342            ++iter)
343        {
344            if (*iter == '/')
345            {
346                last_slash = iter;
347            }
348        }
349        *(last_slash+1) = '\0';
350    }
351    strncat(fname, "/", len);
352    strncat(fname, buffer, len);
353    return;
354}
355
356FILE*
357platform_fopen(const PATH_CHAR* fname, const PATH_CHAR* mode)
358{
359    FILE* fd = fopen(fname, mode);
360    return fd;
361}
362
363
364b32
365platform_move_file(PATH_CHAR* src, PATH_CHAR* dest)
366{
367    int res = rename(src, dest);
368
369    return res == 0;
370}
371
372float
373platform_ui_scale(PlatformState* p)
374{
375    int foo = 0;
376
377    int display_w = 0;
378    int framebuffer_w = 0;
379
380    SDL_GetWindowSize(p->window, &display_w, &foo);
381    SDL_GL_GetDrawableSize(p->window, &framebuffer_w, &foo);
382
383    float scale = display_w > 0 ? framebuffer_w / display_w : 1;
384
385    return scale;
386}
387
388PATH_CHAR*
389platform_open_dialog(FileKind kind)
390{
391    extern PATH_CHAR* platform_open_dialog_mac(FileKind);
392    return platform_open_dialog_mac(kind);
393}
394void
395platform_open_link(char* link)
396{
397    extern void platform_open_link_mac(char*);
398    platform_open_link_mac(link);
399}
400PATH_CHAR*
401platform_save_dialog(FileKind kind)
402{
403    extern PATH_CHAR* platform_save_dialog_mac(FileKind);
404    return platform_save_dialog_mac(kind);
405}
406//  ====
407
408WallTime
409platform_get_walltime()
410{
411    WallTime wt = {};
412    struct timeval tv;
413    gettimeofday(&tv, NULL);
414    struct tm *time;
415    time = localtime(&tv.tv_sec);
416    wt.h = time->tm_hour;
417    wt.m = time->tm_min;
418    wt.s = time->tm_sec;
419    wt.ms = tv.tv_usec / 1000;
420    return wt;
421}
422