2015-03-24 14:45:53 +08:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Our main() function, and Cocoa-specific stuff to set up our windows and
|
|
|
|
// otherwise handle our interface to the operating system. Everything
|
|
|
|
// outside gtk/... should be standard C++ and OpenGL.
|
|
|
|
//
|
|
|
|
// Copyright 2015 <whitequark@whitequark.org>
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <mach/mach.h>
|
|
|
|
#include <mach/clock.h>
|
|
|
|
|
|
|
|
#import <AppKit/AppKit.h>
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
#include "solvespace.h"
|
|
|
|
#include "../unix/gloffscreen.h"
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
using SolveSpace::dbp;
|
|
|
|
|
|
|
|
#define GL_CHECK() \
|
|
|
|
do { \
|
|
|
|
int err = (int)glGetError(); \
|
|
|
|
if(err) dbp("%s:%d: glGetError() == 0x%X", __FILE__, __LINE__, err); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* Settings */
|
|
|
|
|
|
|
|
namespace SolveSpace {
|
2015-12-26 23:54:26 +08:00
|
|
|
void CnfFreezeInt(uint32_t val, const std::string &key) {
|
2015-03-24 14:45:53 +08:00
|
|
|
[[NSUserDefaults standardUserDefaults]
|
2015-12-26 23:54:26 +08:00
|
|
|
setInteger:val forKey:[NSString stringWithUTF8String:key.c_str()]];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
2015-12-26 23:54:26 +08:00
|
|
|
uint32_t CnfThawInt(uint32_t val, const std::string &key) {
|
|
|
|
NSString *nsKey = [NSString stringWithUTF8String:key.c_str()];
|
2015-03-24 14:45:53 +08:00
|
|
|
if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey])
|
|
|
|
return [[NSUserDefaults standardUserDefaults] integerForKey:nsKey];
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2015-12-26 23:54:26 +08:00
|
|
|
void CnfFreezeFloat(float val, const std::string &key) {
|
2015-03-24 14:45:53 +08:00
|
|
|
[[NSUserDefaults standardUserDefaults]
|
2015-12-26 23:54:26 +08:00
|
|
|
setFloat:val forKey:[NSString stringWithUTF8String:key.c_str()]];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
2015-12-26 23:54:26 +08:00
|
|
|
float CnfThawFloat(float val, const std::string &key) {
|
|
|
|
NSString *nsKey = [NSString stringWithUTF8String:key.c_str()];
|
2015-03-24 14:45:53 +08:00
|
|
|
if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey])
|
|
|
|
return [[NSUserDefaults standardUserDefaults] floatForKey:nsKey];
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2015-12-26 23:54:26 +08:00
|
|
|
void CnfFreezeString(const std::string &val, const std::string &key) {
|
2015-03-24 14:45:53 +08:00
|
|
|
[[NSUserDefaults standardUserDefaults]
|
2015-12-26 23:54:26 +08:00
|
|
|
setObject:[NSString stringWithUTF8String:val.c_str()]
|
|
|
|
forKey:[NSString stringWithUTF8String:key.c_str()]];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
2015-12-26 23:54:26 +08:00
|
|
|
std::string CnfThawString(const std::string &val, const std::string &key) {
|
|
|
|
NSString *nsKey = [NSString stringWithUTF8String:key.c_str()];
|
2015-03-24 14:45:53 +08:00
|
|
|
if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey]) {
|
2015-12-26 23:54:26 +08:00
|
|
|
NSString *nsNewVal = [[NSUserDefaults standardUserDefaults] stringForKey:nsKey];
|
|
|
|
return [nsNewVal UTF8String];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
2015-12-26 23:54:26 +08:00
|
|
|
return val;
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Timer */
|
|
|
|
|
|
|
|
int64_t SolveSpace::GetMilliseconds(void) {
|
|
|
|
clock_serv_t cclock;
|
|
|
|
mach_timespec_t mts;
|
|
|
|
|
|
|
|
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
|
|
|
|
clock_get_time(cclock, &mts);
|
|
|
|
mach_port_deallocate(mach_task_self(), cclock);
|
|
|
|
|
|
|
|
return mts.tv_sec * 1000 + mts.tv_nsec / 1000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
@interface DeferredHandler : NSObject
|
|
|
|
+ (void) runLater:(id)dummy;
|
|
|
|
+ (void) runCallback;
|
2015-03-29 12:46:57 +08:00
|
|
|
+ (void) doAutosave;
|
2015-03-24 14:45:53 +08:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation DeferredHandler
|
|
|
|
+ (void) runLater:(id)dummy {
|
|
|
|
SolveSpace::SS.DoLater();
|
|
|
|
}
|
|
|
|
+ (void) runCallback {
|
|
|
|
SolveSpace::SS.GW.TimerCallback();
|
|
|
|
SolveSpace::SS.TW.TimerCallback();
|
|
|
|
}
|
2015-03-29 12:46:57 +08:00
|
|
|
+ (void) doAutosave {
|
|
|
|
SolveSpace::SS.Autosave();
|
|
|
|
}
|
2015-03-24 14:45:53 +08:00
|
|
|
@end
|
|
|
|
|
2015-03-29 12:46:57 +08:00
|
|
|
static void Schedule(SEL selector, double interval) {
|
2015-03-24 14:45:53 +08:00
|
|
|
NSMethodSignature *signature = [[DeferredHandler class]
|
2015-03-29 12:46:57 +08:00
|
|
|
methodSignatureForSelector:selector];
|
2015-03-24 14:45:53 +08:00
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
2015-03-29 12:46:57 +08:00
|
|
|
[invocation setSelector:selector];
|
2015-03-24 14:45:53 +08:00
|
|
|
[invocation setTarget:[DeferredHandler class]];
|
2015-03-29 12:46:57 +08:00
|
|
|
[NSTimer scheduledTimerWithTimeInterval:interval
|
2015-03-24 14:45:53 +08:00
|
|
|
invocation:invocation repeats:NO];
|
|
|
|
}
|
|
|
|
|
2015-03-29 12:46:57 +08:00
|
|
|
void SolveSpace::SetTimerFor(int milliseconds) {
|
|
|
|
Schedule(@selector(runCallback), milliseconds / 1000.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SolveSpace::SetAutosaveTimerFor(int minutes) {
|
|
|
|
Schedule(@selector(doAutosave), minutes * 60.0);
|
|
|
|
}
|
|
|
|
|
2015-03-24 14:45:53 +08:00
|
|
|
void SolveSpace::ScheduleLater() {
|
|
|
|
[[NSRunLoop currentRunLoop]
|
|
|
|
performSelector:@selector(runLater:)
|
|
|
|
target:[DeferredHandler class] argument:nil
|
|
|
|
order:0 modes:@[NSDefaultRunLoopMode]];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OpenGL view */
|
|
|
|
|
|
|
|
@interface GLViewWithEditor : NSView
|
|
|
|
- (void)drawGL;
|
|
|
|
|
|
|
|
@property BOOL wantsBackingStoreScaling;
|
|
|
|
|
|
|
|
@property(readonly, getter=isEditing) BOOL editing;
|
2016-04-16 08:10:32 +08:00
|
|
|
- (void)startEditing:(NSString*)text at:(NSPoint)origin withHeight:(double)fontHeight
|
|
|
|
usingMonospace:(BOOL)isMonospace;
|
2015-03-24 14:45:53 +08:00
|
|
|
- (void)stopEditing;
|
|
|
|
- (void)didEdit:(NSString*)text;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GLViewWithEditor
|
|
|
|
{
|
|
|
|
GLOffscreen *offscreen;
|
|
|
|
NSOpenGLContext *glContext;
|
|
|
|
@protected
|
|
|
|
NSTextField *editor;
|
|
|
|
}
|
|
|
|
|
|
|
|
- initWithFrame:(NSRect)frameRect {
|
|
|
|
self = [super initWithFrame:frameRect];
|
|
|
|
[self setWantsLayer:YES];
|
|
|
|
|
|
|
|
NSOpenGLPixelFormatAttribute attrs[] = {
|
|
|
|
NSOpenGLPFAColorSize, 24,
|
|
|
|
NSOpenGLPFADepthSize, 24,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
|
|
|
|
glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:NULL];
|
|
|
|
|
|
|
|
editor = [[NSTextField alloc] init];
|
|
|
|
[editor setEditable:YES];
|
2016-04-16 08:10:32 +08:00
|
|
|
[[editor cell] setWraps:NO];
|
|
|
|
[[editor cell] setScrollable:YES];
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
[editor setBezeled:NO];
|
2015-03-24 14:45:53 +08:00
|
|
|
[editor setTarget:self];
|
|
|
|
[editor setAction:@selector(editorAction:)];
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
delete offscreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CONVERT1(name, to_from) \
|
|
|
|
- (NS##name)convert##name##to_from##Backing:(NS##name)input { \
|
|
|
|
return _wantsBackingStoreScaling ? [super convert##name##to_from##Backing:input] : input; }
|
|
|
|
#define CONVERT(name) CONVERT1(name, To) CONVERT1(name, From)
|
|
|
|
CONVERT(Size)
|
|
|
|
CONVERT(Rect)
|
|
|
|
#undef CONVERT
|
|
|
|
#undef CONVERT1
|
|
|
|
|
|
|
|
- (NSPoint)convertPointToBacking:(NSPoint)input {
|
|
|
|
if(_wantsBackingStoreScaling) return [super convertPointToBacking:input];
|
|
|
|
else {
|
|
|
|
input.y *= -1;
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSPoint)convertPointFromBacking:(NSPoint)input {
|
|
|
|
if(_wantsBackingStoreScaling) return [super convertPointFromBacking:input];
|
|
|
|
else {
|
|
|
|
input.y *= -1;
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawRect:(NSRect)aRect {
|
|
|
|
[glContext makeCurrentContext];
|
|
|
|
|
|
|
|
if(!offscreen)
|
|
|
|
offscreen = new GLOffscreen;
|
|
|
|
|
|
|
|
NSSize size = [self convertSizeToBacking:[self bounds].size];
|
|
|
|
offscreen->begin(size.width, size.height);
|
|
|
|
|
|
|
|
[self drawGL];
|
|
|
|
GL_CHECK();
|
|
|
|
|
|
|
|
uint8_t *pixels = offscreen->end(![self isFlipped]);
|
|
|
|
CGDataProviderRef provider = CGDataProviderCreateWithData(
|
|
|
|
NULL, pixels, size.width * size.height * 4, NULL);
|
|
|
|
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
|
|
|
|
CGImageRef image = CGImageCreate(size.width, size.height, 8, 32,
|
|
|
|
size.width * 4, colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
|
|
|
|
provider, NULL, true, kCGRenderingIntentDefault);
|
|
|
|
|
|
|
|
CGContextDrawImage((CGContextRef) [[NSGraphicsContext currentContext] graphicsPort],
|
|
|
|
[self bounds], image);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawGL {
|
|
|
|
}
|
|
|
|
|
|
|
|
@synthesize editing;
|
|
|
|
|
2016-04-16 08:10:32 +08:00
|
|
|
- (void)startEditing:(NSString*)text at:(NSPoint)origin withHeight:(double)fontHeight
|
|
|
|
usingMonospace:(BOOL)isMonospace {
|
2015-03-24 14:45:53 +08:00
|
|
|
if(!self->editing) {
|
|
|
|
[self addSubview:editor];
|
|
|
|
self->editing = YES;
|
|
|
|
}
|
|
|
|
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
NSFont *font;
|
|
|
|
if(isMonospace)
|
2016-04-16 08:10:32 +08:00
|
|
|
font = [NSFont fontWithName:@"Monaco" size:fontHeight];
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
else
|
2016-04-16 08:10:32 +08:00
|
|
|
font = [NSFont controlContentFontOfSize:fontHeight];
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
[editor setFont:font];
|
|
|
|
|
|
|
|
origin.x -= 3; /* left padding; no way to get it from NSTextField */
|
|
|
|
origin.y -= [editor intrinsicContentSize].height;
|
|
|
|
origin.y += [editor baselineOffsetFromBottom];
|
|
|
|
|
2015-03-24 14:45:53 +08:00
|
|
|
[editor setFrameOrigin:origin];
|
|
|
|
[editor setStringValue:text];
|
|
|
|
[[self window] makeFirstResponder:editor];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)stopEditing {
|
|
|
|
if(self->editing) {
|
|
|
|
[editor removeFromSuperview];
|
|
|
|
self->editing = NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)editorAction:(id)sender {
|
|
|
|
[self didEdit:[editor stringValue]];
|
|
|
|
[self stopEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)didEdit:(NSString*)text {
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
/* Graphics window */
|
|
|
|
|
|
|
|
@interface GraphicsWindowView : GLViewWithEditor
|
|
|
|
{
|
|
|
|
NSTrackingArea *trackingArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
@property(readonly) NSEvent *lastContextMenuEvent;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GraphicsWindowView
|
|
|
|
- (BOOL)isFlipped {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawGL {
|
|
|
|
SolveSpace::SS.GW.Paint();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)acceptsFirstResponder {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) createTrackingArea {
|
|
|
|
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
|
|
|
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
|
|
|
|
NSTrackingActiveInKeyWindow)
|
|
|
|
owner:self userInfo:nil];
|
|
|
|
[self addTrackingArea:trackingArea];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) updateTrackingAreas
|
|
|
|
{
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
|
|
[self createTrackingArea];
|
|
|
|
[super updateTrackingAreas];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseMoved:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
NSUInteger flags = [event modifierFlags];
|
|
|
|
NSUInteger buttons = [NSEvent pressedMouseButtons];
|
|
|
|
SolveSpace::SS.GW.MouseMoved(point.x, point.y,
|
|
|
|
buttons & (1 << 0),
|
|
|
|
buttons & (1 << 2),
|
|
|
|
buttons & (1 << 1),
|
|
|
|
flags & NSShiftKeyMask,
|
|
|
|
flags & NSCommandKeyMask);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseDragged:(NSEvent*)event {
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseDragged:(NSEvent*)event {
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)otherMouseDragged:(NSEvent*)event {
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseDown:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
if([event clickCount] == 1)
|
|
|
|
SolveSpace::SS.GW.MouseLeftDown(point.x, point.y);
|
|
|
|
else if([event clickCount] == 2)
|
|
|
|
SolveSpace::SS.GW.MouseLeftDoubleClick(point.x, point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseDown:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.GW.MouseMiddleOrRightDown(point.x, point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)otherMouseDown:(NSEvent*)event {
|
|
|
|
[self rightMouseDown:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseUp:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.GW.MouseLeftUp(point.x, point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseUp:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
self->_lastContextMenuEvent = event;
|
|
|
|
SolveSpace::SS.GW.MouseRightUp(point.x, point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)scrollWheel:(NSEvent*)event {
|
|
|
|
NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.GW.MouseScroll(point.x, point.y, -[event deltaY]);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseExited:(NSEvent*)event {
|
|
|
|
SolveSpace::SS.GW.MouseLeave();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)keyDown:(NSEvent*)event {
|
|
|
|
int chr = 0;
|
|
|
|
if(NSString *nsChr = [event charactersIgnoringModifiers])
|
|
|
|
chr = [nsChr characterAtIndex:0];
|
|
|
|
|
|
|
|
if(chr == NSDeleteCharacter) /* map delete back to backspace */
|
|
|
|
chr = '\b';
|
|
|
|
if(chr >= NSF1FunctionKey && chr <= NSF12FunctionKey)
|
|
|
|
chr = SolveSpace::GraphicsWindow::FUNCTION_KEY_BASE + (chr - NSF1FunctionKey);
|
|
|
|
|
|
|
|
NSUInteger flags = [event modifierFlags];
|
|
|
|
if(flags & NSShiftKeyMask)
|
|
|
|
chr |= SolveSpace::GraphicsWindow::SHIFT_MASK;
|
|
|
|
if(flags & NSCommandKeyMask)
|
|
|
|
chr |= SolveSpace::GraphicsWindow::CTRL_MASK;
|
|
|
|
|
|
|
|
// override builtin behavior: "focus on next cell", "close window"
|
|
|
|
if(chr == '\t' || chr == '\x1b')
|
|
|
|
[[NSApp mainMenu] performKeyEquivalent:event];
|
|
|
|
else if(!chr || !SolveSpace::SS.GW.KeyDown(chr))
|
|
|
|
[super keyDown:event];
|
|
|
|
}
|
|
|
|
|
2016-04-16 08:10:32 +08:00
|
|
|
- (void)startEditing:(NSString*)text at:(NSPoint)xy withHeight:(double)fontHeight
|
|
|
|
withMinWidthInChars:(int)minWidthChars {
|
2015-03-24 14:45:53 +08:00
|
|
|
// Convert to ij (vs. xy) style coordinates
|
|
|
|
NSSize size = [self convertSizeToBacking:[self bounds].size];
|
|
|
|
NSPoint point = {
|
|
|
|
.x = xy.x + size.width / 2,
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
.y = xy.y - size.height / 2
|
2015-03-24 14:45:53 +08:00
|
|
|
};
|
2016-04-22 00:23:33 +08:00
|
|
|
[[self window] becomeKeyWindow];
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
[super startEditing:text at:[self convertPointFromBacking:point]
|
2016-04-16 08:10:32 +08:00
|
|
|
withHeight:fontHeight usingMonospace:FALSE];
|
|
|
|
[self prepareEditorWithMinWidthInChars:minWidthChars];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)prepareEditorWithMinWidthInChars:(int)minWidthChars {
|
|
|
|
NSFont *font = [editor font];
|
|
|
|
NSGlyph glyphA = [font glyphWithName:@"a"];
|
|
|
|
if(glyphA == -1) oops();
|
|
|
|
CGFloat glyphAWidth = [font advancementForGlyph:glyphA].width;
|
|
|
|
|
|
|
|
[editor sizeToFit];
|
|
|
|
|
|
|
|
NSSize frameSize = [editor frame].size;
|
|
|
|
frameSize.width = std::max(frameSize.width, glyphAWidth * minWidthChars);
|
|
|
|
[editor setFrameSize:frameSize];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)didEdit:(NSString*)text {
|
|
|
|
SolveSpace::SS.GW.EditControlDone([text UTF8String]);
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
[self setNeedsDisplay:YES];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cancelOperation:(id)sender {
|
|
|
|
[self stopEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSPoint)ij_to_xy:(NSPoint)ij {
|
|
|
|
// Convert to xy (vs. ij) style coordinates,
|
|
|
|
// with (0, 0) at center
|
|
|
|
NSSize size = [self bounds].size;
|
|
|
|
return [self convertPointToBacking:(NSPoint){
|
|
|
|
.x = ij.x - size.width / 2, .y = ij.y - size.height / 2 }];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface GraphicsWindowDelegate : NSObject<NSWindowDelegate>
|
|
|
|
- (BOOL)windowShouldClose:(id)sender;
|
|
|
|
|
|
|
|
@property(readonly, getter=isFullscreen) BOOL fullscreen;
|
|
|
|
- (void)windowDidEnterFullScreen:(NSNotification *)notification;
|
|
|
|
- (void)windowDidExitFullScreen:(NSNotification *)notification;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GraphicsWindowDelegate
|
|
|
|
- (BOOL)windowShouldClose:(id)sender {
|
|
|
|
[NSApp terminate:sender];
|
|
|
|
return FALSE; /* in case NSApp changes its mind */
|
|
|
|
}
|
|
|
|
|
|
|
|
@synthesize fullscreen;
|
|
|
|
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
|
|
|
|
fullscreen = true;
|
|
|
|
/* Update the menus */
|
|
|
|
SolveSpace::SS.GW.EnsureValidActives();
|
|
|
|
}
|
|
|
|
- (void)windowDidExitFullScreen:(NSNotification *)notification {
|
|
|
|
fullscreen = false;
|
|
|
|
/* Update the menus */
|
|
|
|
SolveSpace::SS.GW.EnsureValidActives();
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
static NSWindow *GW;
|
|
|
|
static GraphicsWindowView *GWView;
|
|
|
|
static GraphicsWindowDelegate *GWDelegate;
|
|
|
|
|
|
|
|
namespace SolveSpace {
|
|
|
|
void InitGraphicsWindow() {
|
|
|
|
GW = [[NSWindow alloc] init];
|
|
|
|
GWDelegate = [[GraphicsWindowDelegate alloc] init];
|
|
|
|
[GW setDelegate:GWDelegate];
|
|
|
|
[GW setStyleMask:(NSTitledWindowMask | NSClosableWindowMask |
|
|
|
|
NSMiniaturizableWindowMask | NSResizableWindowMask)];
|
|
|
|
[GW setFrameAutosaveName:@"GraphicsWindow"];
|
|
|
|
[GW setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
|
|
|
|
if(![GW setFrameUsingName:[GW frameAutosaveName]])
|
|
|
|
[GW setContentSize:(NSSize){ .width = 600, .height = 600 }];
|
|
|
|
GWView = [[GraphicsWindowView alloc] init];
|
|
|
|
[GW setContentView:GWView];
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetGraphicsWindowSize(int *w, int *h) {
|
|
|
|
NSSize size = [GWView convertSizeToBacking:[GWView frame].size];
|
|
|
|
*w = size.width;
|
|
|
|
*h = size.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InvalidateGraphics(void) {
|
|
|
|
[GWView setNeedsDisplay:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
void PaintGraphics(void) {
|
|
|
|
[GWView setNeedsDisplay:YES];
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
|
|
|
|
}
|
|
|
|
|
2015-12-27 09:03:24 +08:00
|
|
|
void SetCurrentFilename(const std::string &filename) {
|
|
|
|
if(!filename.empty()) {
|
|
|
|
[GW setTitleWithRepresentedFilename:[NSString stringWithUTF8String:filename.c_str()]];
|
2015-03-24 14:45:53 +08:00
|
|
|
} else {
|
|
|
|
[GW setTitle:@"(new sketch)"];
|
|
|
|
[GW setRepresentedFilename:@""];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ToggleFullScreen(void) {
|
|
|
|
[GW toggleFullScreen:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FullScreenIsActive(void) {
|
|
|
|
return [GWDelegate isFullscreen];
|
|
|
|
}
|
|
|
|
|
2016-04-16 08:10:32 +08:00
|
|
|
void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars,
|
|
|
|
const std::string &str) {
|
2015-11-06 16:40:12 +08:00
|
|
|
[GWView startEditing:[NSString stringWithUTF8String:str.c_str()]
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
at:(NSPoint){(CGFloat)x, (CGFloat)y}
|
2016-04-16 08:10:32 +08:00
|
|
|
withHeight:fontHeight
|
|
|
|
withMinWidthInChars:minWidthChars];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void HideGraphicsEditControl(void) {
|
|
|
|
[GWView stopEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GraphicsEditControlIsVisible(void) {
|
|
|
|
return [GWView isEditing];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Context menus */
|
|
|
|
|
|
|
|
static int contextMenuChoice;
|
|
|
|
|
|
|
|
@interface ContextMenuResponder : NSObject
|
|
|
|
+ (void)handleClick:(id)sender;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ContextMenuResponder
|
|
|
|
+ (void)handleClick:(id)sender {
|
|
|
|
contextMenuChoice = [sender tag];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
namespace SolveSpace {
|
|
|
|
NSMenu *contextMenu, *contextSubmenu;
|
|
|
|
|
|
|
|
void AddContextMenuItem(const char *label, int id_) {
|
|
|
|
NSMenuItem *menuItem;
|
|
|
|
if(label) {
|
|
|
|
menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label]
|
|
|
|
action:@selector(handleClick:) keyEquivalent:@""];
|
|
|
|
[menuItem setTarget:[ContextMenuResponder class]];
|
|
|
|
[menuItem setTag:id_];
|
|
|
|
} else {
|
|
|
|
menuItem = [NSMenuItem separatorItem];
|
|
|
|
}
|
|
|
|
|
|
|
|
if(id_ == CONTEXT_SUBMENU) {
|
|
|
|
[menuItem setSubmenu:contextSubmenu];
|
|
|
|
contextSubmenu = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(contextSubmenu) {
|
|
|
|
[contextSubmenu addItem:menuItem];
|
|
|
|
} else {
|
|
|
|
if(!contextMenu) {
|
|
|
|
contextMenu = [[NSMenu alloc]
|
|
|
|
initWithTitle:[NSString stringWithUTF8String:label]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[contextMenu addItem:menuItem];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CreateContextSubmenu(void) {
|
|
|
|
if(contextSubmenu) oops();
|
|
|
|
|
|
|
|
contextSubmenu = [[NSMenu alloc] initWithTitle:@""];
|
|
|
|
}
|
|
|
|
|
|
|
|
int ShowContextMenu(void) {
|
|
|
|
if(!contextMenu)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
[NSMenu popUpContextMenu:contextMenu
|
|
|
|
withEvent:[GWView lastContextMenuEvent] forView:GWView];
|
|
|
|
|
|
|
|
contextMenu = nil;
|
|
|
|
|
|
|
|
return contextMenuChoice;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Main menu */
|
|
|
|
|
|
|
|
@interface MainMenuResponder : NSObject
|
|
|
|
+ (void)handleStatic:(id)sender;
|
|
|
|
+ (void)handleRecent:(id)sender;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation MainMenuResponder
|
|
|
|
+ (void)handleStatic:(id)sender {
|
|
|
|
SolveSpace::GraphicsWindow::MenuEntry *entry =
|
|
|
|
(SolveSpace::GraphicsWindow::MenuEntry*)[sender tag];
|
|
|
|
|
|
|
|
if(entry->fn && ![(NSMenuItem*)sender hasSubmenu])
|
|
|
|
entry->fn(entry->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)handleRecent:(id)sender {
|
|
|
|
int id_ = [sender tag];
|
|
|
|
if(id_ >= RECENT_OPEN && id_ < (RECENT_OPEN + MAX_RECENT))
|
|
|
|
SolveSpace::SolveSpaceUI::MenuFile(id_);
|
|
|
|
else if(id_ >= RECENT_IMPORT && id_ < (RECENT_IMPORT + MAX_RECENT))
|
|
|
|
SolveSpace::Group::MenuGroup(id_);
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
namespace SolveSpace {
|
|
|
|
std::map<int, NSMenuItem*> mainMenuItems;
|
|
|
|
|
|
|
|
void InitMainMenu(NSMenu *mainMenu) {
|
|
|
|
NSMenuItem *menuItem = NULL;
|
|
|
|
NSMenu *levels[5] = {mainMenu, 0};
|
|
|
|
NSString *label;
|
|
|
|
|
|
|
|
const GraphicsWindow::MenuEntry *entry = &GraphicsWindow::menu[0];
|
|
|
|
int current_level = 0;
|
|
|
|
while(entry->level >= 0) {
|
|
|
|
if(entry->level > current_level) {
|
|
|
|
NSMenu *menu = [[NSMenu alloc] initWithTitle:label];
|
|
|
|
[menu setAutoenablesItems:NO];
|
|
|
|
[menuItem setSubmenu:menu];
|
|
|
|
|
|
|
|
if(entry->level >= sizeof(levels) / sizeof(levels[0]))
|
|
|
|
oops();
|
|
|
|
|
|
|
|
levels[entry->level] = menu;
|
|
|
|
}
|
|
|
|
|
|
|
|
current_level = entry->level;
|
|
|
|
|
|
|
|
if(entry->label) {
|
|
|
|
/* OS X does not support mnemonics */
|
|
|
|
label = [[NSString stringWithUTF8String:entry->label]
|
|
|
|
stringByReplacingOccurrencesOfString:@"&" withString:@""];
|
|
|
|
|
|
|
|
unichar accel_char = entry->accel &
|
|
|
|
~(GraphicsWindow::SHIFT_MASK | GraphicsWindow::CTRL_MASK);
|
|
|
|
if(accel_char > GraphicsWindow::FUNCTION_KEY_BASE &&
|
|
|
|
accel_char <= GraphicsWindow::FUNCTION_KEY_BASE + 12)
|
|
|
|
accel_char = NSF1FunctionKey + (accel_char - GraphicsWindow::FUNCTION_KEY_BASE - 1);
|
|
|
|
NSString *accel = [NSString stringWithCharacters:&accel_char length:1];
|
|
|
|
|
|
|
|
menuItem = [levels[entry->level] addItemWithTitle:label
|
|
|
|
action:NULL keyEquivalent:[accel lowercaseString]];
|
|
|
|
|
|
|
|
NSUInteger modifierMask = 0;
|
|
|
|
if(entry->accel & GraphicsWindow::SHIFT_MASK)
|
|
|
|
modifierMask |= NSShiftKeyMask;
|
|
|
|
else if(entry->accel & GraphicsWindow::CTRL_MASK)
|
|
|
|
modifierMask |= NSCommandKeyMask;
|
|
|
|
[menuItem setKeyEquivalentModifierMask:modifierMask];
|
|
|
|
|
|
|
|
[menuItem setTag:(NSInteger)entry];
|
|
|
|
[menuItem setTarget:[MainMenuResponder class]];
|
|
|
|
[menuItem setAction:@selector(handleStatic:)];
|
|
|
|
} else {
|
|
|
|
[levels[entry->level] addItem:[NSMenuItem separatorItem]];
|
|
|
|
}
|
|
|
|
|
|
|
|
mainMenuItems[entry->id] = menuItem;
|
|
|
|
|
|
|
|
++entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EnableMenuById(int id_, bool enabled) {
|
|
|
|
[mainMenuItems[id_] setEnabled:enabled];
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckMenuById(int id_, bool checked) {
|
|
|
|
[mainMenuItems[id_] setState:(checked ? NSOnState : NSOffState)];
|
|
|
|
}
|
|
|
|
|
|
|
|
void RadioMenuById(int id_, bool selected) {
|
|
|
|
CheckMenuById(id_, selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void RefreshRecentMenu(int id_, int base) {
|
|
|
|
NSMenuItem *recent = mainMenuItems[id_];
|
|
|
|
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
|
|
|
|
[recent setSubmenu:menu];
|
|
|
|
|
|
|
|
if(std::string(RecentFile[0]).empty()) {
|
|
|
|
NSMenuItem *placeholder = [[NSMenuItem alloc]
|
|
|
|
initWithTitle:@"(no recent files)" action:nil keyEquivalent:@""];
|
|
|
|
[placeholder setEnabled:NO];
|
|
|
|
[menu addItem:placeholder];
|
|
|
|
} else {
|
|
|
|
for(int i = 0; i < MAX_RECENT; i++) {
|
|
|
|
if(std::string(RecentFile[i]).empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
NSMenuItem *item = [[NSMenuItem alloc]
|
2015-12-27 09:03:24 +08:00
|
|
|
initWithTitle:[[NSString stringWithUTF8String:RecentFile[i].c_str()]
|
2015-03-24 14:45:53 +08:00
|
|
|
stringByAbbreviatingWithTildeInPath]
|
|
|
|
action:nil keyEquivalent:@""];
|
|
|
|
[item setTag:(base + i)];
|
|
|
|
[item setAction:@selector(handleRecent:)];
|
|
|
|
[item setTarget:[MainMenuResponder class]];
|
|
|
|
[menu addItem:item];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RefreshRecentMenus(void) {
|
|
|
|
RefreshRecentMenu(GraphicsWindow::MNU_OPEN_RECENT, RECENT_OPEN);
|
|
|
|
RefreshRecentMenu(GraphicsWindow::MNU_GROUP_RECENT, RECENT_IMPORT);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ToggleMenuBar(void) {
|
|
|
|
[NSMenu setMenuBarVisible:![NSMenu menuBarVisible]];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MenuBarIsVisible(void) {
|
|
|
|
return [NSMenu menuBarVisible];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Save/load */
|
|
|
|
|
2016-01-11 16:23:51 +08:00
|
|
|
bool SolveSpace::GetOpenFile(std::string &file, const std::string &defExtension,
|
|
|
|
const char *selPattern) {
|
2015-03-24 14:45:53 +08:00
|
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
|
|
NSMutableArray *filters = [[NSMutableArray alloc] init];
|
|
|
|
for(NSString *filter in [[NSString stringWithUTF8String:selPattern]
|
|
|
|
componentsSeparatedByString:@"\n"]) {
|
|
|
|
[filters addObjectsFromArray:
|
|
|
|
[[[filter componentsSeparatedByString:@"\t"] objectAtIndex:1]
|
|
|
|
componentsSeparatedByString:@","]];
|
|
|
|
}
|
|
|
|
[filters removeObjectIdenticalTo:@"*"];
|
|
|
|
[panel setAllowedFileTypes:filters];
|
|
|
|
|
|
|
|
if([panel runModal] == NSFileHandlingPanelOKButton) {
|
2015-12-27 09:03:24 +08:00
|
|
|
file = [[NSFileManager defaultManager]
|
|
|
|
fileSystemRepresentationWithPath:[[panel URL] path]];
|
2015-03-24 14:45:53 +08:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@interface SaveFormatController : NSViewController
|
|
|
|
@property NSSavePanel *panel;
|
|
|
|
@property NSArray *extensions;
|
|
|
|
@property (nonatomic) IBOutlet NSPopUpButton *button;
|
|
|
|
@property (nonatomic) NSInteger index;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation SaveFormatController
|
|
|
|
@synthesize panel, extensions, button, index;
|
|
|
|
- (void)setIndex:(NSInteger)newIndex {
|
|
|
|
self->index = newIndex;
|
|
|
|
NSString *extension = [extensions objectAtIndex:newIndex];
|
|
|
|
if(![extension isEqual:@"*"]) {
|
|
|
|
NSString *filename = [panel nameFieldStringValue];
|
|
|
|
NSString *basename = [[filename componentsSeparatedByString:@"."] objectAtIndex:0];
|
|
|
|
[panel setNameFieldStringValue:[basename stringByAppendingPathExtension:extension]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
2016-01-11 16:23:51 +08:00
|
|
|
bool SolveSpace::GetSaveFile(std::string &file, const std::string &defExtension,
|
|
|
|
const char *selPattern) {
|
2015-03-24 14:45:53 +08:00
|
|
|
NSSavePanel *panel = [NSSavePanel savePanel];
|
|
|
|
|
|
|
|
SaveFormatController *controller =
|
|
|
|
[[SaveFormatController alloc] initWithNibName:@"SaveFormatAccessory" bundle:nil];
|
|
|
|
[controller setPanel:panel];
|
|
|
|
[panel setAccessoryView:[controller view]];
|
|
|
|
|
|
|
|
NSMutableArray *extensions = [[NSMutableArray alloc] init];
|
|
|
|
[controller setExtensions:extensions];
|
|
|
|
|
|
|
|
NSPopUpButton *button = [controller button];
|
|
|
|
[button removeAllItems];
|
|
|
|
for(NSString *filter in [[NSString stringWithUTF8String:selPattern]
|
|
|
|
componentsSeparatedByString:@"\n"]) {
|
|
|
|
NSArray *filterParts = [filter componentsSeparatedByString:@"\t"];
|
|
|
|
NSString *filterName = [filterParts objectAtIndex:0];
|
|
|
|
NSArray *filterExtensions = [[filterParts objectAtIndex:1]
|
|
|
|
componentsSeparatedByString:@","];
|
|
|
|
[button addItemWithTitle:
|
|
|
|
[[NSString alloc] initWithFormat:@"%@ (%@)", filterName,
|
|
|
|
[filterExtensions componentsJoinedByString:@", "]]];
|
|
|
|
[extensions addObject:[filterExtensions objectAtIndex:0]];
|
|
|
|
}
|
2016-01-11 16:44:56 +08:00
|
|
|
|
|
|
|
int extensionIndex = 0;
|
|
|
|
if(defExtension != "") {
|
|
|
|
extensionIndex = [extensions indexOfObject:
|
|
|
|
[NSString stringWithUTF8String:defExtension.c_str()]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[button selectItemAtIndex:extensionIndex];
|
|
|
|
[panel setNameFieldStringValue:[@"untitled"
|
|
|
|
stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]];
|
2015-03-24 14:45:53 +08:00
|
|
|
|
|
|
|
if([panel runModal] == NSFileHandlingPanelOKButton) {
|
2015-12-27 09:03:24 +08:00
|
|
|
file = [[NSFileManager defaultManager]
|
|
|
|
fileSystemRepresentationWithPath:[[panel URL] path]];
|
2015-03-24 14:45:53 +08:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-11 20:18:18 +08:00
|
|
|
SolveSpace::DialogChoice SolveSpace::SaveFileYesNoCancel(void) {
|
2015-03-24 14:45:53 +08:00
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
if(!std::string(SolveSpace::SS.saveFile).empty()) {
|
|
|
|
[alert setMessageText:
|
|
|
|
[[@"Do you want to save the changes you made to the sketch “"
|
|
|
|
stringByAppendingString:
|
2015-12-27 09:03:24 +08:00
|
|
|
[[NSString stringWithUTF8String:SolveSpace::SS.saveFile.c_str()]
|
2015-03-24 14:45:53 +08:00
|
|
|
stringByAbbreviatingWithTildeInPath]]
|
|
|
|
stringByAppendingString:@"”?"]];
|
|
|
|
} else {
|
|
|
|
[alert setMessageText:@"Do you want to save the changes you made to the new sketch?"];
|
|
|
|
}
|
|
|
|
[alert setInformativeText:@"Your changes will be lost if you don't save them."];
|
|
|
|
[alert addButtonWithTitle:@"Save"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert addButtonWithTitle:@"Don't Save"];
|
|
|
|
switch([alert runModal]) {
|
|
|
|
case NSAlertFirstButtonReturn:
|
2016-01-11 20:18:18 +08:00
|
|
|
return DIALOG_YES;
|
2015-03-24 14:45:53 +08:00
|
|
|
case NSAlertSecondButtonReturn:
|
2016-01-11 20:18:18 +08:00
|
|
|
default:
|
|
|
|
return DIALOG_CANCEL;
|
2015-03-24 14:45:53 +08:00
|
|
|
case NSAlertThirdButtonReturn:
|
2016-01-11 20:18:18 +08:00
|
|
|
return DIALOG_NO;
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-11 20:18:18 +08:00
|
|
|
SolveSpace::DialogChoice SolveSpace::LoadAutosaveYesNo(void) {
|
2015-03-29 12:46:57 +08:00
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
[alert setMessageText:
|
|
|
|
@"An autosave file is availible for this project."];
|
|
|
|
[alert setInformativeText:
|
|
|
|
@"Do you want to load the autosave file instead?"];
|
|
|
|
[alert addButtonWithTitle:@"Load"];
|
|
|
|
[alert addButtonWithTitle:@"Don't Load"];
|
|
|
|
switch([alert runModal]) {
|
|
|
|
case NSAlertFirstButtonReturn:
|
2016-01-11 20:18:18 +08:00
|
|
|
return DIALOG_YES;
|
2015-03-29 12:46:57 +08:00
|
|
|
case NSAlertSecondButtonReturn:
|
2016-01-11 20:18:18 +08:00
|
|
|
default:
|
|
|
|
return DIALOG_NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel(
|
|
|
|
const std::string &filename, bool canCancel) {
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
[alert setMessageText:[NSString stringWithUTF8String:
|
|
|
|
("The imported file " + filename + " is not present.").c_str()]];
|
|
|
|
[alert setInformativeText:
|
|
|
|
@"Do you want to locate it manually?\n"
|
|
|
|
"If you select \"No\", any geometry that depends on "
|
|
|
|
"the missing file will be removed."];
|
|
|
|
[alert addButtonWithTitle:@"Yes"];
|
|
|
|
if(canCancel)
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert addButtonWithTitle:@"No"];
|
|
|
|
switch([alert runModal]) {
|
|
|
|
case NSAlertFirstButtonReturn:
|
|
|
|
return DIALOG_YES;
|
|
|
|
case NSAlertSecondButtonReturn:
|
|
|
|
default:
|
|
|
|
if(canCancel)
|
|
|
|
return DIALOG_CANCEL;
|
|
|
|
/* fallthrough */
|
|
|
|
case NSAlertThirdButtonReturn:
|
|
|
|
return DIALOG_NO;
|
2015-03-29 12:46:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 14:45:53 +08:00
|
|
|
/* Text window */
|
|
|
|
|
|
|
|
@interface TextWindowView : GLViewWithEditor
|
|
|
|
{
|
|
|
|
NSTrackingArea *trackingArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
@property (nonatomic, getter=isCursorHand) BOOL cursorHand;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation TextWindowView
|
|
|
|
- (BOOL)isFlipped {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawGL {
|
|
|
|
SolveSpace::SS.TW.Paint();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)acceptsFirstMouse:(NSEvent*)event {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) createTrackingArea {
|
|
|
|
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
|
|
|
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
|
|
|
|
NSTrackingActiveAlways)
|
|
|
|
owner:self userInfo:nil];
|
|
|
|
[self addTrackingArea:trackingArea];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) updateTrackingAreas
|
|
|
|
{
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
|
|
[self createTrackingArea];
|
|
|
|
[super updateTrackingAreas];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseMoved:(NSEvent*)event {
|
|
|
|
NSPoint point = [self convertPointToBacking:
|
|
|
|
[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.TW.MouseEvent(/*leftClick*/ false, /*leftDown*/ false,
|
|
|
|
point.x, -point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseDown:(NSEvent*)event {
|
|
|
|
NSPoint point = [self convertPointToBacking:
|
|
|
|
[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.TW.MouseEvent(/*leftClick*/ true, /*leftDown*/ true,
|
|
|
|
point.x, -point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseDragged:(NSEvent*)event {
|
|
|
|
NSPoint point = [self convertPointToBacking:
|
|
|
|
[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
|
|
SolveSpace::SS.TW.MouseEvent(/*leftClick*/ false, /*leftDown*/ true,
|
|
|
|
point.x, -point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setCursorHand:(BOOL)cursorHand {
|
|
|
|
if(_cursorHand != cursorHand) {
|
|
|
|
if(cursorHand)
|
|
|
|
[[NSCursor pointingHandCursor] push];
|
|
|
|
else
|
|
|
|
[NSCursor pop];
|
|
|
|
}
|
|
|
|
_cursorHand = cursorHand;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseExited:(NSEvent*)event {
|
|
|
|
[self setCursorHand:FALSE];
|
|
|
|
SolveSpace::SS.TW.MouseLeave();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)startEditing:(NSString*)text at:(NSPoint)point {
|
|
|
|
point = [self convertPointFromBacking:point];
|
Ensure edit control font size matches font size of text being edited.
Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:
The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.
The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.
Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.
On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.
Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
2016-04-12 22:24:09 +08:00
|
|
|
point.y = -point.y + 2;
|
2016-04-22 00:23:33 +08:00
|
|
|
[[self window] makeKeyWindow];
|
2016-04-16 08:10:32 +08:00
|
|
|
[super startEditing:text at:point withHeight:15.0 usingMonospace:TRUE];
|
|
|
|
[editor setFrameSize:(NSSize){
|
|
|
|
.width = [self bounds].size.width - [editor frame].origin.x,
|
|
|
|
.height = [editor intrinsicContentSize].height }];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)stopEditing {
|
|
|
|
[super stopEditing];
|
|
|
|
[GW makeKeyWindow];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)didEdit:(NSString*)text {
|
|
|
|
SolveSpace::SS.TW.EditControlDone([text UTF8String]);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cancelOperation:(id)sender {
|
|
|
|
[self stopEditing];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface TextWindowDelegate : NSObject<NSWindowDelegate>
|
|
|
|
- (BOOL)windowShouldClose:(id)sender;
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation TextWindowDelegate
|
|
|
|
- (BOOL)windowShouldClose:(id)sender {
|
|
|
|
SolveSpace::GraphicsWindow::MenuView(SolveSpace::GraphicsWindow::MNU_SHOW_TEXT_WND);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification {
|
|
|
|
NSClipView *view = [[[notification object] contentView] contentView];
|
|
|
|
NSView *document = [view documentView];
|
|
|
|
NSSize size = [document frame].size;
|
|
|
|
size.width = [view frame].size.width;
|
|
|
|
[document setFrameSize:size];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
static NSPanel *TW;
|
|
|
|
static TextWindowView *TWView;
|
|
|
|
static TextWindowDelegate *TWDelegate;
|
|
|
|
|
|
|
|
namespace SolveSpace {
|
|
|
|
void InitTextWindow() {
|
|
|
|
TW = [[NSPanel alloc] init];
|
|
|
|
TWDelegate = [[TextWindowDelegate alloc] init];
|
|
|
|
[TW setStyleMask:(NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
|
|
|
|
NSUtilityWindowMask)];
|
|
|
|
[[TW standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
|
|
|
|
[[TW standardWindowButton:NSWindowZoomButton] setHidden:YES];
|
|
|
|
[TW setTitle:@"Browser"];
|
|
|
|
[TW setFrameAutosaveName:@"TextWindow"];
|
|
|
|
[TW setFloatingPanel:YES];
|
|
|
|
[TW setBecomesKeyOnlyIfNeeded:YES];
|
|
|
|
[GW addChildWindow:TW ordered:NSWindowAbove];
|
|
|
|
|
|
|
|
NSScrollView *scrollView = [[NSScrollView alloc] init];
|
|
|
|
[TW setContentView:scrollView];
|
|
|
|
[scrollView setBackgroundColor:[NSColor blackColor]];
|
|
|
|
[scrollView setHasVerticalScroller:YES];
|
|
|
|
[[scrollView contentView] setCopiesOnScroll:YES];
|
|
|
|
|
|
|
|
TWView = [[TextWindowView alloc] init];
|
|
|
|
[scrollView setDocumentView:TWView];
|
|
|
|
|
|
|
|
[TW setDelegate:TWDelegate];
|
|
|
|
if(![TW setFrameUsingName:[TW frameAutosaveName]])
|
|
|
|
[TW setContentSize:(NSSize){ .width = 420, .height = 300 }];
|
|
|
|
[TWView setFrame:[[scrollView contentView] frame]];
|
|
|
|
}
|
|
|
|
|
|
|
|
void ShowTextWindow(bool visible) {
|
|
|
|
if(visible)
|
|
|
|
[TW orderFront:nil];
|
|
|
|
else
|
|
|
|
[TW close];
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetTextWindowSize(int *w, int *h) {
|
|
|
|
NSSize size = [TWView convertSizeToBacking:[TWView frame].size];
|
|
|
|
*w = size.width;
|
|
|
|
*h = size.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InvalidateText(void) {
|
|
|
|
NSSize size = [TWView convertSizeToBacking:[TWView frame].size];
|
|
|
|
size.height = (SS.TW.top[SS.TW.rows - 1] + 1) * TextWindow::LINE_HEIGHT / 2;
|
|
|
|
[TWView setFrameSize:[TWView convertSizeFromBacking:size]];
|
|
|
|
[TWView setNeedsDisplay:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
void MoveTextScrollbarTo(int pos, int maxPos, int page) {
|
|
|
|
/* unused; we draw the entire text window and scroll in Cocoa */
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetMousePointerToHand(bool is_hand) {
|
|
|
|
[TWView setCursorHand:is_hand];
|
|
|
|
}
|
|
|
|
|
2015-11-06 16:40:12 +08:00
|
|
|
void ShowTextEditControl(int x, int y, const std::string &str) {
|
|
|
|
return [TWView startEditing:[NSString stringWithUTF8String:str.c_str()]
|
2015-12-25 23:10:01 +08:00
|
|
|
at:(NSPoint){(CGFloat)x, (CGFloat)y}];
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void HideTextEditControl(void) {
|
|
|
|
return [TWView stopEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TextEditControlIsVisible(void) {
|
|
|
|
return [TWView isEditing];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Miscellanea */
|
|
|
|
|
|
|
|
void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error) {
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
[alert setAlertStyle:(error ? NSWarningAlertStyle : NSInformationalAlertStyle)];
|
|
|
|
|
|
|
|
/* do some additional formatting of the message these are
|
|
|
|
heuristics, but they are made failsafe and lead to nice results. */
|
|
|
|
NSString *input = [NSString stringWithUTF8String:str];
|
|
|
|
NSRange dot = [input rangeOfCharacterFromSet:
|
|
|
|
[NSCharacterSet characterSetWithCharactersInString:@".:"]];
|
|
|
|
if(dot.location != NSNotFound) {
|
|
|
|
[alert setMessageText:[[input substringToIndex:dot.location + 1]
|
|
|
|
stringByReplacingOccurrencesOfString:@"\n" withString:@" "]];
|
|
|
|
[alert setInformativeText:
|
|
|
|
[[input substringFromIndex:dot.location + 1]
|
|
|
|
stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]];
|
|
|
|
} else {
|
|
|
|
[alert setMessageText:[input
|
|
|
|
stringByReplacingOccurrencesOfString:@"\n" withString:@" "]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[alert runModal];
|
|
|
|
}
|
|
|
|
|
|
|
|
void SolveSpace::OpenWebsite(const char *url) {
|
|
|
|
[[NSWorkspace sharedWorkspace] openURL:
|
|
|
|
[NSURL URLWithString:[NSString stringWithUTF8String:url]]];
|
|
|
|
}
|
|
|
|
|
Rewrite TTF to Bezier conversion using Freetype.
Benefits:
* Much simpler code.
* Handles the entire TTF spec, not just a small subset that
only really worked well on Windows fonts.
* Handles all character sets as well as accented characters.
* Much faster parsing, since Freetype lazily loads and
caches glyphs.
* Support for basically every kind of font that was invented,
not just TTF.
Note that OpenType features, e.g. ligatures, are not yet supported.
This means that Arabic and Devanagari scripts, among others, will
not be rendered in their proper form.
RTL scripts are not supported either, neither in TTF nor in
the text window. Adding RTL support is comparatively easy, but
given that Arabic would not be legibly rendered anyway, this is not
done so far.
2016-01-30 09:42:44 +08:00
|
|
|
std::vector<std::string> SolveSpace::GetFontFiles() {
|
|
|
|
std::vector<std::string> fonts;
|
|
|
|
|
2015-03-24 14:45:53 +08:00
|
|
|
NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts];
|
|
|
|
for(NSString *fontName in fontNames) {
|
|
|
|
CTFontDescriptorRef fontRef =
|
|
|
|
CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0);
|
|
|
|
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute);
|
|
|
|
NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]];
|
Rewrite TTF to Bezier conversion using Freetype.
Benefits:
* Much simpler code.
* Handles the entire TTF spec, not just a small subset that
only really worked well on Windows fonts.
* Handles all character sets as well as accented characters.
* Much faster parsing, since Freetype lazily loads and
caches glyphs.
* Support for basically every kind of font that was invented,
not just TTF.
Note that OpenType features, e.g. ligatures, are not yet supported.
This means that Arabic and Devanagari scripts, among others, will
not be rendered in their proper form.
RTL scripts are not supported either, neither in TTF nor in
the text window. Adding RTL support is comparatively easy, but
given that Arabic would not be legibly rendered anyway, this is not
done so far.
2016-01-30 09:42:44 +08:00
|
|
|
fonts.push_back([[NSFileManager defaultManager]
|
|
|
|
fileSystemRepresentationWithPath:fontPath]);
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
Rewrite TTF to Bezier conversion using Freetype.
Benefits:
* Much simpler code.
* Handles the entire TTF spec, not just a small subset that
only really worked well on Windows fonts.
* Handles all character sets as well as accented characters.
* Much faster parsing, since Freetype lazily loads and
caches glyphs.
* Support for basically every kind of font that was invented,
not just TTF.
Note that OpenType features, e.g. ligatures, are not yet supported.
This means that Arabic and Devanagari scripts, among others, will
not be rendered in their proper form.
RTL scripts are not supported either, neither in TTF nor in
the text window. Adding RTL support is comparatively easy, but
given that Arabic would not be legibly rendered anyway, this is not
done so far.
2016-01-30 09:42:44 +08:00
|
|
|
|
|
|
|
return fonts;
|
2015-03-24 14:45:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Application lifecycle */
|
|
|
|
|
|
|
|
@interface ApplicationDelegate : NSObject<NSApplicationDelegate>
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication;
|
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
|
|
|
- (void)applicationWillTerminate:(NSNotification *)aNotification;
|
|
|
|
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
|
|
|
|
- (IBAction)preferences:(id)sender;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ApplicationDelegate
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
|
|
|
if(SolveSpace::SS.OkayToStartNewFile())
|
|
|
|
return NSTerminateNow;
|
|
|
|
else
|
|
|
|
return NSTerminateCancel;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
|
|
|
SolveSpace::SK.Clear();
|
|
|
|
SolveSpace::SS.Clear();
|
|
|
|
SolveSpace::SS.Exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
|
|
|
|
return SolveSpace::SS.OpenFile([filename UTF8String]);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)preferences:(id)sender {
|
|
|
|
SolveSpace::SS.TW.GoToScreen(SolveSpace::TextWindow::SCREEN_CONFIGURATION);
|
|
|
|
SolveSpace::SS.ScheduleShowTW();
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
void SolveSpace::ExitNow(void) {
|
|
|
|
[NSApp stop:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, const char *argv[]) {
|
|
|
|
[NSApplication sharedApplication];
|
|
|
|
ApplicationDelegate *delegate = [[ApplicationDelegate alloc] init];
|
|
|
|
[NSApp setDelegate:delegate];
|
|
|
|
|
|
|
|
SolveSpace::InitGraphicsWindow();
|
|
|
|
SolveSpace::InitTextWindow();
|
|
|
|
[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil];
|
|
|
|
SolveSpace::InitMainMenu([NSApp mainMenu]);
|
|
|
|
|
|
|
|
SolveSpace::SS.Init();
|
|
|
|
|
|
|
|
[GW makeKeyAndOrderFront:nil];
|
|
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
|
|
[NSApp run];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|