Skip to content
This repository has been archived by the owner on Oct 27, 2019. It is now read-only.

Commit

Permalink
new version with fullscreen support and totally reworked window manag…
Browse files Browse the repository at this point in the history
…ement
  • Loading branch information
rsms committed Sep 14, 2009
1 parent 40a22a4 commit 5104f2e
Show file tree
Hide file tree
Showing 21 changed files with 579 additions and 92 deletions.
10 changes: 10 additions & 0 deletions cocui.xcodeproj/project.pbxproj
Expand Up @@ -32,6 +32,8 @@
3A4BEF8410513E6400534481 /* EVWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4BEF8310513E6400534481 /* EVWebView.m */; };
3A6A0B4C105D17590035640D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
3A6A0B6F105D183A0035640D /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A6A0B6D105D183A0035640D /* MainMenu.xib */; };
3AEE4417105E943500E89C47 /* CUWin.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AEE4416105E943500E89C47 /* CUWin.m */; };
3AEE44F0105EADA300E89C47 /* jsbridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AEE44EF105EADA300E89C47 /* jsbridge.m */; };
3AF3B962105BEA1100B3CBCC /* default.css in Resources */ = {isa = PBXBuildFile; fileRef = 3AF3B961105BEA1100B3CBCC /* default.css */; };
3AF3B9CD105BF88700B3CBCC /* NSApplication+EVJS.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AF3B9CC105BF88700B3CBCC /* NSApplication+EVJS.m */; };
3AF3B9E5105BFA4D00B3CBCC /* NSMutableDictionary+EVJS.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AF3B9E4105BFA4D00B3CBCC /* NSMutableDictionary+EVJS.m */; };
Expand Down Expand Up @@ -103,6 +105,9 @@
3A4BEF8310513E6400534481 /* EVWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EVWebView.m; path = src/EVWebView.m; sourceTree = "<group>"; };
3A6A0B52105D17590035640D /* Cocui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cocui.app; sourceTree = BUILT_PRODUCTS_DIR; };
3A6A0B6E105D183A0035640D /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = resources/English.lproj/MainMenu.xib; sourceTree = "<group>"; };
3AEE4415105E943500E89C47 /* CUWin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CUWin.h; path = src/CUWin.h; sourceTree = "<group>"; };
3AEE4416105E943500E89C47 /* CUWin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CUWin.m; path = src/CUWin.m; sourceTree = "<group>"; };
3AEE44EF105EADA300E89C47 /* jsbridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = jsbridge.m; path = src/jsbridge/jsbridge.m; sourceTree = "<group>"; };
3AF3B961105BEA1100B3CBCC /* default.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = default.css; path = resources/default.css; sourceTree = "<group>"; };
3AF3B9CC105BF88700B3CBCC /* NSApplication+EVJS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSApplication+EVJS.m"; path = "src/jsbridge/NSApplication+EVJS.m"; sourceTree = "<group>"; };
3AF3B9E4105BFA4D00B3CBCC /* NSMutableDictionary+EVJS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableDictionary+EVJS.m"; path = "src/jsbridge/NSMutableDictionary+EVJS.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -152,6 +157,8 @@
3A114FB7105D81BA00921886 /* CUApp.m */,
3A114FB3105D7CEA00921886 /* CUWindow.h */,
3A114FB4105D7CEA00921886 /* CUWindow.m */,
3AEE4415105E943500E89C47 /* CUWin.h */,
3AEE4416105E943500E89C47 /* CUWin.m */,
3A4BEF8210513E6400534481 /* EVWebView.h */,
3A4BEF8310513E6400534481 /* EVWebView.m */,
3A115151105DB08200921886 /* NSString+CUAdditions.h */,
Expand Down Expand Up @@ -256,6 +263,7 @@
isa = PBXGroup;
children = (
3A1BFA331051630C00FE3E7D /* jsbridge.h */,
3AEE44EF105EADA300E89C47 /* jsbridge.m */,
3AF3B9CC105BF88700B3CBCC /* NSApplication+EVJS.m */,
3A1BFA761051654B00FE3E7D /* NSWindow+EVJS.m */,
3A1BFB0710516BCD00FE3E7D /* NSDictionary+EVJS.m */,
Expand Down Expand Up @@ -399,6 +407,8 @@
3A114FB8105D81BA00921886 /* CUApp.m in Sources */,
3A115153105DB08200921886 /* NSString+CUAdditions.m in Sources */,
3A115248105DC6DC00921886 /* NSDictionary+CUAdditions.m in Sources */,
3AEE4417105E943500E89C47 /* CUWin.m in Sources */,
3AEE44F0105EADA300E89C47 /* jsbridge.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
2 changes: 1 addition & 1 deletion manager/CUImageView.h
Expand Up @@ -4,6 +4,6 @@
NSURL *imageURL;
}

@property(assign) NSURL *imageURL;
@property NSURL *imageURL;

@end
31 changes: 24 additions & 7 deletions resources/main.js
@@ -1,12 +1,29 @@
// test JSON
// Load index.html in a regular window and display it
App.createWindow({
uri: 'index.html',
name: 'main',
rect: { size: { width: 500, height: 400 } }
}).makeKeyAndOrderFront();

// Alternative minimal example of opening a window
/*App.createWindow('index.html').makeKeyAndOrderFront();
*/

// Example of a fullscreen window (quits after 4 seconds)
/*App.createWindow({ uri: 'index.html', fullscreen: true }).makeKeyAndOrderFront();
setTimeout(function(){ App.terminate() }, 4000);
*/

// Alternative example of a fullscreen window (exits fullscreen after 4 seconds)
/*var win = App.createWindow({uri: 'index.html'}); // add style:{borderless:true} to remove ui chrome
win.makeKeyAndOrderFront();
win.fullscreen = true;
setTimeout(function(){ win.fullscreen = false }, 4000);
*/

// Example of encoding and decoding JSON
/*var s = App.encodeJSON({uri: 'index.html', moset: {0:1,1:2,9:3,4:undefined,98:56}});
console.log(s);
var o = App.decodeJSON(s);
console.log(o.uri);
*/

// Load index.html in a window and display the window
App.loadWindow({
uri: 'index.html',
rect: { size: { width: 500, height: 400 } }
}).makeKeyAndOrderFront();
14 changes: 14 additions & 0 deletions src/CUApp.h
@@ -1,7 +1,10 @@
@class CUWin;

@interface CUApp : NSObject {
NSString *version;
NSUserDefaultsController *defaultsController;
NSUserDefaults *defaults;
CGDirectDisplayID fullscreen; // -1 when not in fullscreen mode

// Callbacks
WebScriptObject *onOpenFiles;
Expand All @@ -16,6 +19,7 @@
@property(assign) NSString *version;
@property(assign) NSUserDefaultsController *defaultsController;
@property(assign) NSUserDefaults *defaults;
@property(assign) BOOL fullscreen;

// void onOpenFiles([string filename[, ..]])
@property(assign) WebScriptObject *onOpenFiles;
Expand All @@ -26,4 +30,14 @@
-(id)evaluateWebScript:(NSString *)js errorDesc:(NSString **)errdesc;
-(void)terminate;

-(CUWin *)createWindow:(WebScriptObject *)jsargs;


// Enter/exit fullscreen:
// mostly here for other objc classes to use for getting success status.
// Scripts should use the "fullscreen" property.
-(BOOL)enterFullscreen:(CGDirectDisplayID)screenID;
-(BOOL)exitFullscreen:(CGDirectDisplayID)screenID;
-(CGDirectDisplayID)exitFullscreen;

@end
203 changes: 173 additions & 30 deletions src/CUApp.m
Expand Up @@ -2,6 +2,8 @@
#import "WebScriptObject+EVJS.h"
#import "NSDictionary+CUAdditions.h"

#import <ApplicationServices/ApplicationServices.h>

#import "EVApp.h"
#import "CUApp.h"
#import "CUWindow.h"
Expand All @@ -14,7 +16,7 @@ @implementation CUApp
@synthesize onOpenFiles;

// some:thing -> some_thing()
EVJS_TRANSPOND_NAMES_PLAIN
CUJS_TRANSPOND_NAMES_PLAIN

// allow access to all properties
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name { return NO; }
Expand All @@ -31,6 +33,7 @@ + (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
-(id)initWithWebPreferences:(WebPreferences *)preferences {
self = [super init];
_webPrefs = preferences;
fullscreen = -1;
webView = [[WebView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 10.0, 10.0) frameName:@"main" groupName:@"app"];
[webView setFrameLoadDelegate:self];
[webView setUIDelegate:self];
Expand Down Expand Up @@ -74,56 +77,135 @@ -(NSString *)decodeJSON:(WebScriptObject *)obj {
Arguments:
{
uri: string // e.g. "index.html" or "http://some.thing/"
name: string // causes the window frame to be saved in defaults as "nameWindow"
rect: {
origin: {x: float, y: float}
size: {width: float, height: float}
}
style: {
style: {
titled: bool (true)
closable: bool (true)
miniaturizable: bool (true)
resizable: bool (true)
borderless: bool (false)
textured: bool (false)
borderless: bool (false) // if set, the only valid styles are textured and shadow
shadow: bool (true)
}
fullscreen: bool (false) // if set, no default style is set (style can still be explicitly set)
defer: bool (false)
level: string|int ("normal")
}
Considered future options:
fullscreen: bool (false)
Window level names: (starting from lowest to highest in the z dimension)
Minimum, Desktop, BackstopMenu, Normal, Floating, TornOffMenu, Dock, MainMenu,
Status, ModalPanel, PopUpMenu, Dragging, ScreenSaver, Maximum, Overlay, Help,
Utility, DesktopIcon, Cursor
*/
-(id)loadWindow:(WebScriptObject *)jsargs {
NSURL *url;
NSString *uri;
-(CUWin *)createWindow:(WebScriptObject *)jsargs {
NSURL *url = nil;
NSString *uri = nil, *name = nil;
NSRect visibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect contentRect = NSMakeRect(-1.0, -1.0, 300.0, 400.0);
NSUInteger styleMask = 0;
//NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
BOOL defer = NO;
NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
BOOL defer = NO, shadow = YES, _fullscreen = NO;
JSContextRef ctx = [[webView mainFrame] globalContext];
CGWindowLevel windowLevel = kCGNormalWindowLevel;
id args = nil;

id args = [jsargs cocoaRepresentationInContext:ctx];
if ([jsargs respondsToSelector:@selector(cocoaRepresentationInContext:)])
args = [jsargs cocoaRepresentationInContext:ctx];

if (![[args class] isSubclassOfClass:[NSDictionary class]]) {
if (!args || ![[args class] isSubclassOfClass:[NSDictionary class]]) {
// simple string input (or something else that's not a dict)
uri = args;
uri = (NSString *)jsargs;
}
else {
// keyword arguments
NSNumber *n;
NSString *s;
NSDictionary *d;

//#define BOPT(key, assignto) do { if((n = [args objectForKey:key])) defer = [n boolValue]; } while(0)

uri = [args objectForKey:@"uri"];
name = [args objectForKey:@"name"];

if (!(uri = [args objectForKey:@"uri"])) {
/*if (!uri) {
[jsargs setException:@"missing \"uri\" argument"];
return nil;
}
}*/

// update rect
NSDictionary *rect = [args objectForKey:@"rect"];
if (rect && [rect respondsToSelector:@selector(objectForKey:)])
contentRect = [rect updateRect:contentRect];
d = [args objectForKey:@"rect"];
if (d && [d respondsToSelector:@selector(objectForKey:)])
contentRect = [d updateRect:contentRect];

// todo: style
styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
// fullscreen
_fullscreen = ((n = [args objectForKey:@"fullscreen"]) && [n boolValue]);
if (_fullscreen)
shadow = NO; // shadow defaults to NO

// style
/*
titled: bool (true)
closable: bool (true)
miniaturizable: bool (true)
resizable: bool (true)
textured: bool (false)
*/
d = [args objectForKey:@"style"];
if (d && [d respondsToSelector:@selector(objectForKey:)]) {
#define OPT_DEFAULT_TRUE(dict, key, flag) \
( (!(n = [dict objectForKey:key]) || [n boolValue]) ? flag : 0 )
#define OPT_DEFAULT_FALSE(dict, key, flag) \
( ((n = [dict objectForKey:key]) && [n boolValue]) ? flag : 0 )

/*
Discussion:
borderless is special -- if defined, others (except from textured) should
not be set. Also, NSBorderlessWindowMask is 0 (null) which makes things a
bit more complicated and might case problems in the future if the value of
NSBorderlessWindowMask is changed.
*/

styleMask = 0;

if (!(n = [d objectForKey:@"borderless"]) || ![n boolValue]) {
// only eval these if not borderless
styleMask |= OPT_DEFAULT_TRUE(d, @"titled", NSTitledWindowMask);
styleMask |= OPT_DEFAULT_TRUE(d, @"closable", NSClosableWindowMask);
styleMask |= OPT_DEFAULT_TRUE(d, @"miniaturizable", NSMiniaturizableWindowMask);
styleMask |= OPT_DEFAULT_TRUE(d, @"resizable", NSResizableWindowMask);
}
styleMask |= OPT_DEFAULT_FALSE(d, @"textured", NSTexturedBackgroundWindowMask);

if ((n = [d objectForKey:@"shadow"]))
shadow = [n boolValue];

#undef OPT_DEFAULT_TRUE
#undef OPT_DEFAULT_FALSE
}
else if (_fullscreen) {
// no style feats by default when requesting fullscreen
styleMask = 0;
}

// defer
if ((n = [args objectForKey:@"defer"]))
defer = [n boolValue];

// level
if ((s = [args objectForKey:@"level"])) {
CGWindowLevelKey d = [CUWindow windowLevelKeyFromNameOrNumber:s];
if (windowLevel != -1)
windowLevel = CGWindowLevelForKey(d);
}
}

// adjust rect origin if needed (center on screen)
Expand All @@ -134,21 +216,82 @@ -(id)loadWindow:(WebScriptObject *)jsargs {
}

// set url
url = [NSURL alloc];
uri = [uri description];
if ([uri rangeOfString:@"://"].length)
url = [url initWithString:uri];
else
url = [url initFileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:uri]];

if (uri) {
url = [NSURL alloc];
uri = [uri description];
if ([uri rangeOfString:@"://"].length)
url = [url initWithString:uri];
else
url = [url initFileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:uri]];
}

// create window
CUWindow *win = [[CUWindow alloc] initWithContentRect:contentRect styleMask:styleMask defer:defer preferences:_webPrefs app:self];
[win loadURL:url];

// xxx
//[win makeKeyAndOrderFront:self];
// set autosave name & possibly apply saved state
if (name)
[win setFrameAutosaveName:name];

// set level
if (windowLevel != kCGNormalWindowLevel)
[win setLevel:windowLevel];

return win;
// set shadow
if (!shadow)
[win setHasShadow:NO];

// load url
if (url)
[win loadURL:url];

// fullscreen
if (_fullscreen)
[win.win setFullscreen:YES];

return win.win;
}


-(BOOL)fullscreen {
return fullscreen != -1 ? YES : NO;
}


-(void)setFullscreen:(BOOL)b {
if (b)
[self enterFullscreen:CGMainDisplayID()];
else
[self exitFullscreen];
}


-(BOOL)enterFullscreen:(CGDirectDisplayID)screenID {
if (fullscreen == -1) {
fullscreen = screenID;
if (CGDisplayCapture(fullscreen) == kCGErrorSuccess)
return YES;
fullscreen = -1;
CUJS_THROW(@"Failed to capture screen (enter fullscreen)");
}
return NO;
}

-(CGDirectDisplayID)exitFullscreen {
if (fullscreen != -1 && [self exitFullscreen:fullscreen]) {
CGDirectDisplayID sid = fullscreen;
fullscreen = -1;
return sid;
}
return -1;
}

-(BOOL)exitFullscreen:(CGDirectDisplayID)screenID {
if (CGDisplayRelease(screenID) == kCGErrorSuccess)
return YES;
NSLog(@"Failed to release screen %d (exit fullscreen) -- terminating application", fullscreen);
CUJS_THROW(@"Failed to release screen %d (exit fullscreen) -- terminating application", fullscreen);
[NSApp terminate:self];
return NO;
}


Expand Down

0 comments on commit 5104f2e

Please sign in to comment.