/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2024 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#import "MainWindow.h"
#import "Container.h"
#import "GridLayout.h"
#import "BoxContainer.h"
#import "../common/object.h"
#import "../ui/properties.h"
#import <objc/runtime.h>
#import "EventData.h"
#import "menu.h"
#import "Toolbar.h"
@implementation MainWindow
- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview{
NSRect frame = NSMakeRect(300, 200, 600, 500);
self = [self initWithContentRect:frame
styleMask:NSWindowStyleMaskTitled |
NSWindowStyleMaskResizable |
NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable
backing:NSBackingStoreBuffered
defer:false];
_obj = obj;
int top = 4;
NSView *content = self.contentView;
// A sidebar or splitview window need a NSSplitView
NSSplitView *splitview;
if(hasSidebar || hasSplitview) {
self.styleMask |= NSWindowStyleMaskFullSizeContentView;
self.titleVisibility = NSWindowTitleHidden;
self.titlebarAppearsTransparent = YES;
splitview = [[NSSplitView alloc]init];
splitview.vertical = YES;
splitview.dividerStyle = NSSplitViewDividerStyleThin;
splitview.translatesAutoresizingMaskIntoConstraints = false;
[self.contentView addSubview:splitview];
_splitview = splitview;
[NSLayoutConstraint activateConstraints:@[
[splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0],
[splitview.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[splitview.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
]];
top = 34;
}
if(hasSidebar) {
// add the sidebar
const char *sidebarMaterialProperty = ui_get_property("ui.cocoa.sidebar.usematerial");
BOOL useMaterial = YES;
if(sidebarMaterialProperty && (sidebarMaterialProperty[0] == 'f' || sidebarMaterialProperty[0] == 'F')) {
useMaterial = NO;
}
if(useMaterial) {
NSVisualEffectView *v = [[NSVisualEffectView alloc] initWithFrame:NSMakeRect(0,0,0,0)];
v.material = NSVisualEffectMaterialSidebar;
v.blendingMode = NSVisualEffectBlendingModeBehindWindow;
v.state = NSVisualEffectStateActive;
_sidebar = v;
} else {
_sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,0,0)];
}
_sidebar.translatesAutoresizingMaskIntoConstraints = NO;
[splitview addArrangedSubview:_sidebar];
[_sidebar.widthAnchor constraintGreaterThanOrEqualToConstant:250].active = YES;
}
if(hasSplitview) {
// add the splitview window left/right panels
_leftPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
[splitview addArrangedSubview:_leftPanel];
_rightPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
[splitview addArrangedSubview:_rightPanel];
} else if(hasSidebar) {
// sidebar only window: add content view
content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
[splitview addArrangedSubview:content];
}
// normal or sidebar-only windows get a container
if(!hasSplitview) {
// create a vertical stackview as default container
BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
//GridLayout *vbox = [[GridLayout alloc] init];
vbox.translatesAutoresizingMaskIntoConstraints = false;
[content addSubview:vbox];
[NSLayoutConstraint activateConstraints:@[
[vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top],
[vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
[vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
[vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor],
]];
UiContainerX *container = ui_create_container(obj, vbox);
vbox.container = container;
uic_object_push_container(obj, container);
}
_topOffset = top;
if(uic_toolbar_isenabled()) {
UiToolbar *toolbar = [[UiToolbar alloc]initWithWindow:self];
[self setToolbar:toolbar];
}
return self;
}
- (BOOL) getIsVisible {
return [self isVisible];
}
- (void) setVisible:(BOOL)visible {
if(visible) {
[self makeKeyAndOrderFront:nil];
} else {
[self close];
}
}
@end
@implementation MainWindowController
- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window {
self = [super initWithWindow:window];
_uiobj = obj;
self.checkItemStates = [[NSMutableDictionary alloc] init];
self.radioItems = [[NSMutableDictionary alloc] init];
// bind all stateful menu items (checkbox, radiobuttons, lists)
NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding
for(MenuItem *item in menuBindItems) {
if(item.checkItem || item.radioItem) {
// simple check item (ui_menu_toggleitem_create)
UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem ? item.checkItem->varname : item.radioItem->varname, UI_VAR_INTEGER);
// create the state object for this item/window
MenuItemState *state = [[MenuItemState alloc] init];
state.mainWindow = self;
state.var = var;
if(var) {
UiInteger *i = var->value;
if(item.checkItem) {
// bind toggle item
state.state = (int)i->value;
i->obj = (__bridge void*)state;
i->get = ui_menu_check_item_get;
i->set = ui_menu_check_item_set;
} else {
// bind radio item
NSMutableArray *rgroup = nil;
if(i->obj) {
rgroup = (__bridge NSMutableArray*)i->obj;
} else {
// create a new rgroup array and register it in the window
rgroup = [[NSMutableArray alloc] init];
NSString *varname = [[NSString alloc] initWithUTF8String:item.radioItem->varname];
[_radioItems setObject:rgroup forKey:varname];
i->obj = (__bridge void*)rgroup;
}
i->get = ui_menu_radio_item_get;
i->set = ui_menu_radio_item_set;
[rgroup addObject:state]; // add this item state to the radio group
// i->value can contain a non-zero value, which means a specific radiobutton
// should be pre-selected
if(i->value == rgroup.count) {
state.state = NSControlStateValueOn;
}
}
} else {
state.state = 0;
}
[_checkItemStates setObject:state forKey:item.itemId];
}
}
return self;
}
- (void) windowDidLoad {
[self.window setNextResponder:self];
}
- (void)menuItemAction:(id)sender {
EventData *event = objc_getAssociatedObject(sender, "eventdata");
if(event) {
if(event.obj) {
[event handleEvent:sender];
} else {
event.obj = self.uiobj;
[event handleEvent:sender];
event.obj = NULL;
}
}
}
- (void)menuCheckItemAction:(id)sender {
NSMenuItem *menuItem = sender;
MenuItem *item = objc_getAssociatedObject(sender, "menuitem");
if(!item || !item.checkItem) {
return;
}
MenuItemState *state = [_checkItemStates objectForKey:item.itemId];
state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff;
menuItem.state = state.state;
UiMenuCheckItem *it = item.checkItem;
if(it->callback) {
UiEvent event;
event.obj = _uiobj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = state.var ? state.var->value : NULL;
event.intval = state.state;
it->callback(&event, it->userdata);
}
}
- (void)menuRadioItemAction:(id)sender {
NSMenuItem *menuItem = sender;
MenuItem *item = objc_getAssociatedObject(sender, "menuitem");
if(!item || !item.radioItem) {
return;
}
UiMenuRadioItem *it = item.radioItem;
if(!it->varname) {
return;
}
MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; // current state of this menu item
NSString *varname = [[NSString alloc] initWithUTF8String:it->varname];
NSArray *radioGroup = [_radioItems objectForKey:varname];
if(!radioGroup) {
return;
}
int index = 1;
int value = 0;
for(MenuItemState *g in radioGroup) {
if(g == state) {
menuItem.state = NSControlStateValueOn;
g.state = NSControlStateValueOn;
value = index;
} else {
menuItem.state = NSControlStateValueOff;
g.state = NSControlStateValueOff;
}
}
if(it->callback) {
UiEvent event;
event.obj = _uiobj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = state.var ? state.var->value : NULL;
event.intval = value;
it->callback(&event, it->userdata);
}
}
- (BOOL) validateMenuItem:(NSMenuItem *) menuItem {
MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem");
if(item) {
MenuItemState *state = [_checkItemStates objectForKey:item.itemId];
if(state) {
menuItem.state = state.state;
} else {
menuItem.state = NSControlStateValueOff;
}
}
return YES;
}
@end
@implementation MenuItemState
@end
int64_t ui_menu_check_item_get(UiInteger *i) {
MenuItemState *state = (__bridge MenuItemState*)i->obj;
i->value = state.state;
return i->value;
}
void ui_menu_check_item_set(UiInteger *i, int64_t value) {
MenuItemState *state = (__bridge MenuItemState*)i->obj;
i->value = value;
state.state = (int)value;
}
int64_t ui_menu_radio_item_get(UiInteger *i) {
NSArray *rgroup = (__bridge NSArray*)i->obj;
i->value = 0;
int index = 1;
for(MenuItemState *state in rgroup) {
if(state.state == NSControlStateValueOn) {
i->value = index;
break;
}
index++;
}
return i->value;
}
void ui_menu_radio_item_set(UiInteger *i, int64_t value) {
NSArray *rgroup = (__bridge NSArray*)i->obj;
i->value = 0;
int index = 1;
for(MenuItemState *state in rgroup) {
state.state = value == index;
index++;
}
}
UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) {
MainWindow *window = (__bridge MainWindow*)obj->wobj;
if(window.sidebar == nil) {
return NULL;
}
NSView *sidebar = window.sidebar;
// create a vertical stackview as default container
BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing];
vbox.container = ui_create_container(obj, vbox);
//GridLayout *vbox = [[GridLayout alloc] init];
vbox.translatesAutoresizingMaskIntoConstraints = false;
[sidebar addSubview:vbox];
[NSLayoutConstraint activateConstraints:@[
[vbox.topAnchor constraintEqualToAnchor:sidebar.topAnchor constant:34],
[vbox.leadingAnchor constraintEqualToAnchor:sidebar.leadingAnchor],
[vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor],
[vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor]
]];
uic_object_push_container(obj, vbox.container);
return NULL;
}
static UIWIDGET splitview_window_add_panel(UiObject *obj, NSView *panel, UiSidebarArgs *args) {
MainWindow *window = (__bridge MainWindow*)obj->wobj;
BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
//GridLayout *vbox = [[GridLayout alloc] init];
vbox.container = ui_create_container(obj, vbox);
vbox.translatesAutoresizingMaskIntoConstraints = false;
[panel addSubview:vbox];
[NSLayoutConstraint activateConstraints:@[
[vbox.topAnchor constraintEqualToAnchor:panel.topAnchor constant:window.topOffset],
[vbox.leadingAnchor constraintEqualToAnchor:panel.leadingAnchor],
[vbox.trailingAnchor constraintEqualToAnchor:panel.trailingAnchor],
[vbox.bottomAnchor constraintEqualToAnchor:panel.bottomAnchor],
]];
uic_object_push_container(obj, vbox.container);
return (__bridge void*)vbox;
}
UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) {
MainWindow *window = (__bridge MainWindow*)obj->wobj;
if(window.leftPanel == nil) {
return NULL;
}
return splitview_window_add_panel(obj, window.leftPanel, args);
}
UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) {
MainWindow *window = (__bridge MainWindow*)obj->wobj;
if(window.rightPanel == nil) {
return NULL;
}
return splitview_window_add_panel(obj, window.rightPanel, args);
}