solvespace/src/platform/cocoamain.mm

1223 lines
38 KiB
Plaintext
Raw Normal View History

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 platform/... should be standard C++ and OpenGL.
2015-03-24 14:45:53 +08:00
//
// 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 "gloffscreen.h"
2015-03-24 14:45:53 +08:00
#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 {
void CnfFreezeInt(uint32_t val, const std::string &key) {
2015-03-24 14:45:53 +08:00
[[NSUserDefaults standardUserDefaults]
setInteger:val forKey:[NSString stringWithUTF8String:key.c_str()]];
2015-03-24 14:45:53 +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;
}
void CnfFreezeFloat(float val, const std::string &key) {
2015-03-24 14:45:53 +08:00
[[NSUserDefaults standardUserDefaults]
setFloat:val forKey:[NSString stringWithUTF8String:key.c_str()]];
2015-03-24 14:45:53 +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;
}
void CnfFreezeString(const std::string &val, const std::string &key) {
2015-03-24 14:45:53 +08:00
[[NSUserDefaults standardUserDefaults]
setObject:[NSString stringWithUTF8String:val.c_str()]
forKey:[NSString stringWithUTF8String:key.c_str()]];
2015-03-24 14:45:53 +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]) {
NSString *nsNewVal = [[NSUserDefaults standardUserDefaults] stringForKey:nsKey];
return [nsNewVal UTF8String];
2015-03-24 14:45:53 +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;
+ (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();
}
+ (void) doAutosave {
SolveSpace::SS.Autosave();
}
2015-03-24 14:45:53 +08:00
@end
static void Schedule(SEL selector, double interval) {
2015-03-24 14:45:53 +08:00
NSMethodSignature *signature = [[DeferredHandler class]
methodSignatureForSelector:selector];
2015-03-24 14:45:53 +08:00
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
2015-03-24 14:45:53 +08:00
[invocation setTarget:[DeferredHandler class]];
[NSTimer scheduledTimerWithTimeInterval:interval
2015-03-24 14:45:53 +08:00
invocation:invocation repeats:NO];
}
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;
- (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;
}
2016-05-25 15:15:13 +08:00
- (id)initWithFrame:(NSRect)frameRect {
2015-03-24 14:45:53 +08:00
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];
[[editor cell] setWraps:NO];
[[editor cell] setScrollable:YES];
[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;
2016-05-25 15:15:13 +08:00
NSSize size = [self convertSizeToBacking:[self bounds].size];
int width = (int)size.width,
height = (int)size.height;
offscreen->begin(width, height);
2015-03-24 14:45:53 +08:00
[self drawGL];
GL_CHECK();
uint8_t *pixels = offscreen->end(![self isFlipped]);
CGDataProviderRef provider = CGDataProviderCreateWithData(
2016-05-25 15:15:13 +08:00
NULL, pixels, width * height * 4, NULL);
2015-03-24 14:45:53 +08:00
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
2016-05-25 15:15:13 +08:00
CGImageRef image = CGImageCreate(width, height, 8, 32,
width * 4, colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
2015-03-24 14:45:53 +08:00
provider, NULL, true, kCGRenderingIntentDefault);
CGContextDrawImage((CGContextRef) [[NSGraphicsContext currentContext] graphicsPort],
[self bounds], image);
2016-05-18 22:20:43 +08:00
CGImageRelease(image);
CGDataProviderRelease(provider);
2015-03-24 14:45:53 +08:00
}
- (void)drawGL {
}
@synthesize editing;
- (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;
}
NSFont *font;
if(isMonospace)
font = [NSFont fontWithName:@"Monaco" size:fontHeight];
else
font = [NSFont controlContentFontOfSize:fontHeight];
[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]];
2016-05-25 15:15:13 +08:00
SolveSpace::SS.GW.MouseScroll(point.x, point.y, (int)-[event deltaY]);
2015-03-24 14:45:53 +08:00
}
- (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];
}
- (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,
.y = xy.y - size.height / 2
2015-03-24 14:45:53 +08:00
};
[[self window] becomeKeyWindow];
[super startEditing:text at:[self convertPointFromBacking:point]
withHeight:fontHeight usingMonospace:FALSE];
[self prepareEditorWithMinWidthInChars:minWidthChars];
}
- (void)prepareEditorWithMinWidthInChars:(int)minWidthChars {
NSFont *font = [editor font];
NSGlyph glyphA = [font glyphWithName:@"a"];
2016-05-25 15:15:13 +08:00
ssassert(glyphA != (NSGlyph)-1, "Expected font to have U+0061");
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]);
[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];
2016-05-25 15:15:13 +08:00
*w = (int)size.width;
*h = (int)size.height;
2015-03-24 14:45:53 +08:00
}
void InvalidateGraphics(void) {
[GWView setNeedsDisplay:YES];
}
void PaintGraphics(void) {
[GWView setNeedsDisplay:YES];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
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];
}
void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars,
const std::string &str) {
[GWView startEditing:[NSString stringWithUTF8String:str.c_str()]
at:(NSPoint){(CGFloat)x, (CGFloat)y}
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 */
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
static SolveSpace::ContextCommand contextMenuChoice;
2015-03-24 14:45:53 +08:00
@interface ContextMenuResponder : NSObject
+ (void)handleClick:(id)sender;
@end
@implementation ContextMenuResponder
+ (void)handleClick:(id)sender {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
contextMenuChoice = (SolveSpace::ContextCommand)[sender tag];
2015-03-24 14:45:53 +08:00
}
@end
namespace SolveSpace {
NSMenu *contextMenu, *contextSubmenu;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void AddContextMenuItem(const char *label, ContextCommand cmd) {
2015-03-24 14:45:53 +08:00
NSMenuItem *menuItem;
if(label) {
menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label]
action:@selector(handleClick:) keyEquivalent:@""];
[menuItem setTarget:[ContextMenuResponder class]];
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
[menuItem setTag:(NSInteger)cmd];
2015-03-24 14:45:53 +08:00
} else {
menuItem = [NSMenuItem separatorItem];
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
if(cmd == SolveSpace::ContextCommand::SUBMENU) {
2015-03-24 14:45:53 +08:00
[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) {
ssassert(!contextSubmenu, "Unexpected nested submenu");
2015-03-24 14:45:53 +08:00
contextSubmenu = [[NSMenu alloc] initWithTitle:@""];
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextCommand ShowContextMenu(void) {
2015-03-24 14:45:53 +08:00
if(!contextMenu)
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
return ContextCommand::CANCELLED;
2015-03-24 14:45:53 +08:00
[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 {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
uint32_t cmd = [sender tag];
if(cmd >= (uint32_t)SolveSpace::Command::RECENT_OPEN &&
cmd < ((uint32_t)SolveSpace::Command::RECENT_OPEN + SolveSpace::MAX_RECENT)) {
SolveSpace::SolveSpaceUI::MenuFile((SolveSpace::Command)cmd);
} else if(cmd >= (uint32_t)SolveSpace::Command::RECENT_LINK &&
cmd < ((uint32_t)SolveSpace::Command::RECENT_LINK + SolveSpace::MAX_RECENT)) {
SolveSpace::Group::MenuGroup((SolveSpace::Command)cmd);
}
2015-03-24 14:45:53 +08:00
}
@end
namespace SolveSpace {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
std::map<uint32_t, NSMenuItem*> mainMenuItems;
2015-03-24 14:45:53 +08:00
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];
ssassert((unsigned)entry->level < sizeof(levels) / sizeof(levels[0]),
"Unexpected depth of menu nesting");
2015-03-24 14:45:53 +08:00
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]];
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
mainMenuItems[(uint32_t)entry->id] = menuItem;
2015-03-24 14:45:53 +08:00
++entry;
}
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void EnableMenuByCmd(SolveSpace::Command cmd, bool enabled) {
[mainMenuItems[(uint32_t)cmd] setEnabled:enabled];
2015-03-24 14:45:53 +08:00
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void CheckMenuByCmd(SolveSpace::Command cmd, bool checked) {
[mainMenuItems[(uint32_t)cmd] setState:(checked ? NSOnState : NSOffState)];
2015-03-24 14:45:53 +08:00
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void RadioMenuByCmd(SolveSpace::Command cmd, bool selected) {
CheckMenuByCmd(cmd, selected);
2015-03-24 14:45:53 +08:00
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
static void RefreshRecentMenu(SolveSpace::Command cmd, SolveSpace::Command base) {
NSMenuItem *recent = mainMenuItems[(uint32_t)cmd];
2015-03-24 14:45:53 +08:00
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 {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
for(size_t i = 0; i < MAX_RECENT; i++) {
2015-03-24 14:45:53 +08:00
if(std::string(RecentFile[i]).empty())
break;
NSMenuItem *item = [[NSMenuItem alloc]
initWithTitle:[[NSString stringWithUTF8String:RecentFile[i].c_str()]
2015-03-24 14:45:53 +08:00
stringByAbbreviatingWithTildeInPath]
action:nil keyEquivalent:@""];
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
[item setTag:((uint32_t)base + i)];
2015-03-24 14:45:53 +08:00
[item setAction:@selector(handleRecent:)];
[item setTarget:[MainMenuResponder class]];
[menu addItem:item];
}
}
}
void RefreshRecentMenus(void) {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
RefreshRecentMenu(Command::OPEN_RECENT, Command::RECENT_OPEN);
RefreshRecentMenu(Command::GROUP_RECENT, Command::RECENT_LINK);
2015-03-24 14:45:53 +08:00
}
void ToggleMenuBar(void) {
[NSMenu setMenuBarVisible:![NSMenu menuBarVisible]];
}
bool MenuBarIsVisible(void) {
return [NSMenu menuBarVisible];
}
}
/* Save/load */
bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension,
const FileFilter ssFilters[]) {
2015-03-24 14:45:53 +08:00
NSOpenPanel *panel = [NSOpenPanel openPanel];
NSMutableArray *filters = [[NSMutableArray alloc] init];
for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) {
for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) {
[filters addObject:[NSString stringWithUTF8String:*ssPattern]];
}
2015-03-24 14:45:53 +08:00
}
[filters removeObjectIdenticalTo:@"*"];
[panel setAllowedFileTypes:filters];
if([panel runModal] == NSFileHandlingPanelOKButton) {
*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
bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension,
const FileFilter ssFilters[]) {
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(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) {
std::string desc;
for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) {
if(desc == "") {
desc = *ssPattern;
} else {
desc += ", ";
desc += *ssPattern;
}
}
std::string title = std::string(ssFilter->name) + " (" + desc + ")";
[button addItemWithTitle:[NSString stringWithUTF8String:title.c_str()]];
[extensions addObject:[NSString stringWithUTF8String:ssFilter->patterns[0]]];
2015-03-24 14:45:53 +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) {
*file = [[NSFileManager defaultManager]
fileSystemRepresentationWithPath:[[panel URL] path]];
2015-03-24 14:45:53 +08:00
return true;
} else {
return false;
}
}
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:
[[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:
return DIALOG_YES;
2015-03-24 14:45:53 +08:00
case NSAlertSecondButtonReturn:
default:
return DIALOG_CANCEL;
2015-03-24 14:45:53 +08:00
case NSAlertThirdButtonReturn:
return DIALOG_NO;
2015-03-24 14:45:53 +08:00
}
}
SolveSpace::DialogChoice SolveSpace::LoadAutosaveYesNo(void) {
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:
return DIALOG_YES;
case NSAlertSecondButtonReturn:
default:
return DIALOG_NO;
}
}
SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel(
const std::string &filename, bool canCancel) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithUTF8String:
("The linked 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-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];
point.y = -point.y + 2;
[[self window] makeKeyWindow];
[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 {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
SolveSpace::GraphicsWindow::MenuView(SolveSpace::Command::SHOW_TEXT_WND);
2015-03-24 14:45:53 +08:00
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:@"Property Browser"];
2015-03-24 14:45:53 +08:00
[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];
2016-05-25 15:15:13 +08:00
*w = (int)size.width;
*h = (int)size.height;
2015-03-24 14:45:53 +08:00
}
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];
}
void ShowTextEditControl(int x, int y, const std::string &str) {
return [TWView startEditing:[NSString stringWithUTF8String:str.c_str()]
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)];
[[alert addButtonWithTitle:@"OK"] setKeyEquivalent: @"\033"];
2015-03-24 14:45:53 +08:00
/* 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]]];
}
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]];
fonts.push_back([[NSFileManager defaultManager]
fileSystemRepresentationWithPath:fontPath]);
2015-03-24 14:45:53 +08:00
}
return fonts;
2015-03-24 14:45:53 +08:00
}
Implement a resource system. Currently, icons, fonts, etc are converted to C structures at compile time and are hardcoded to the binary. This presents several problems: * Cross-compilation is complicated. Right now, it is necessary to be able to run executables for the target platform; this happens to work with wine-binfmt installed, but is rather ugly. * Icons can only have one resolution. On OS X, modern software is expected to take advantage of high-DPI ("Retina") screens and use so-called @2x assets when ran in high-DPI mode. * Localization is complicated. Win32 and OS X provide built-in support for loading the resource appropriate for the user's locale. * Embedding strings can only be done as raw strings, using C++'s R"(...)" literals. This precludes embedding sizable strings, e.g. JavaScript libraries as used in Three.js export, and makes git history less useful. Not embedding the libraries means we have to rely on external CDNs, which requires an Internet connection and adds a glaring point of failure. * Linux distribution guidelines are violated. All architecture- independent data, especially large data such as fonts, is expected to be in /usr/share, not in the binary. * Customization is impossible without recompilation. Minor modifications like adding a few missing vector font characters or adjusting localization require a complete development environment, which is unreasonable to expect from users of a mechanical CAD. As such, this commit adds a resource system that bundles (and sometimes builds) resources with the executable. Where they go is platform-dependent: * on Win32: into resources of the executable, which allows us to keep distributing one file; * on OS X: into the app bundle; * on other *nix: into /usr/share/solvespace/ or ../res/ (relative to the executable path), the latter allowing us to run freshly built executables without installation. It also subsides the platform-specific resources that are in src/. The resource system is not yet used for anything; this will be added in later commits.
2016-04-21 23:54:18 +08:00
const void *SolveSpace::LoadResource(const std::string &name, size_t *size) {
static NSMutableDictionary *cache;
if(cache == nil) {
cache = [[NSMutableDictionary alloc] init];
}
NSString *key = [NSString stringWithUTF8String:name.c_str()];
NSData *data = [cache objectForKey:key];
if(data == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:key ofType:nil];
data = [[NSFileHandle fileHandleForReadingAtPath:path] readDataToEndOfFile];
ssassert(data != nil, "Cannot find resource");
Implement a resource system. Currently, icons, fonts, etc are converted to C structures at compile time and are hardcoded to the binary. This presents several problems: * Cross-compilation is complicated. Right now, it is necessary to be able to run executables for the target platform; this happens to work with wine-binfmt installed, but is rather ugly. * Icons can only have one resolution. On OS X, modern software is expected to take advantage of high-DPI ("Retina") screens and use so-called @2x assets when ran in high-DPI mode. * Localization is complicated. Win32 and OS X provide built-in support for loading the resource appropriate for the user's locale. * Embedding strings can only be done as raw strings, using C++'s R"(...)" literals. This precludes embedding sizable strings, e.g. JavaScript libraries as used in Three.js export, and makes git history less useful. Not embedding the libraries means we have to rely on external CDNs, which requires an Internet connection and adds a glaring point of failure. * Linux distribution guidelines are violated. All architecture- independent data, especially large data such as fonts, is expected to be in /usr/share, not in the binary. * Customization is impossible without recompilation. Minor modifications like adding a few missing vector font characters or adjusting localization require a complete development environment, which is unreasonable to expect from users of a mechanical CAD. As such, this commit adds a resource system that bundles (and sometimes builds) resources with the executable. Where they go is platform-dependent: * on Win32: into resources of the executable, which allows us to keep distributing one file; * on OS X: into the app bundle; * on other *nix: into /usr/share/solvespace/ or ../res/ (relative to the executable path), the latter allowing us to run freshly built executables without installation. It also subsides the platform-specific resources that are in src/. The resource system is not yet used for anything; this will be added in later commits.
2016-04-21 23:54:18 +08:00
[cache setObject:data forKey:key];
}
*size = [data length];
return [data bytes];
}
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::SS.Exit();
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
return SolveSpace::SS.OpenFile([filename UTF8String]);
}
- (IBAction)preferences:(id)sender {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
SolveSpace::SS.TW.GoToScreen(SolveSpace::TextWindow::Screen::CONFIGURATION);
2015-03-24 14:45:53 +08:00
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];
SolveSpace::SK.Clear();
SolveSpace::SS.Clear();
2015-03-24 14:45:53 +08:00
return 0;
}