2011-11-20 13:23:52 +00:00
|
|
|
#include "FGCocoaMenuBar.hxx"
|
|
|
|
|
|
|
|
#include <Cocoa/Cocoa.h>
|
|
|
|
|
|
|
|
#include <boost/foreach.hpp>
|
|
|
|
|
|
|
|
#include <simgear/props/props.hxx>
|
|
|
|
#include <simgear/props/props_io.hxx>
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
|
|
|
#include <simgear/structure/SGBinding.hxx>
|
2012-01-06 23:46:35 +00:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
2011-11-20 13:23:52 +00:00
|
|
|
|
|
|
|
#include <Main/fg_props.hxx>
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using std::string;
|
|
|
|
using std::map;
|
|
|
|
using std::cout;
|
2012-01-06 23:46:35 +00:00
|
|
|
using namespace simgear;
|
2011-11-20 13:23:52 +00:00
|
|
|
|
|
|
|
typedef std::map<NSMenuItem*, SGBindingList> MenuItemBindings;
|
|
|
|
|
|
|
|
@class CocoaMenuDelegate;
|
|
|
|
|
|
|
|
class FGCocoaMenuBar::CocoaMenuBarPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CocoaMenuBarPrivate();
|
|
|
|
~CocoaMenuBarPrivate();
|
|
|
|
|
|
|
|
void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
|
|
|
|
|
|
|
|
void fireBindingsForItem(NSMenuItem* item);
|
|
|
|
|
|
|
|
public:
|
|
|
|
CocoaMenuDelegate* delegate;
|
|
|
|
|
|
|
|
MenuItemBindings itemBindings;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
|
|
|
|
@private
|
|
|
|
FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
|
|
|
|
}
|
|
|
|
|
|
|
|
@property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation CocoaMenuDelegate
|
|
|
|
|
|
|
|
@synthesize peer;
|
|
|
|
|
|
|
|
- (void) itemAction:(id) sender
|
|
|
|
{
|
|
|
|
peer->fireBindingsForItem((NSMenuItem*) sender);
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
static NSString* stdStringToCocoa(const string& s)
|
|
|
|
{
|
|
|
|
return [NSString stringWithUTF8String:s.c_str()];
|
|
|
|
}
|
|
|
|
|
2012-01-06 23:46:35 +00:00
|
|
|
static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut)
|
|
|
|
{
|
|
|
|
unichar ch[1];
|
|
|
|
ch[0] = shortcut;
|
|
|
|
[item setKeyEquivalentModifierMask:NSFunctionKeyMask];
|
|
|
|
[item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setItemShortcutFromString(NSMenuItem* item, const string& s)
|
|
|
|
{
|
|
|
|
const char* shortcut = "";
|
|
|
|
|
|
|
|
bool hasCtrl = strutils::starts_with(s, "Ctrl-");
|
|
|
|
bool hasShift = strutils::starts_with(s, "Shift-");
|
|
|
|
bool hasAlt = strutils::starts_with(s, "Alt-");
|
|
|
|
|
|
|
|
int offset = 0; // character offset from start of string
|
|
|
|
if (hasShift) offset += 6;
|
|
|
|
if (hasCtrl) offset += 5;
|
|
|
|
if (hasAlt) offset += 4;
|
|
|
|
|
|
|
|
shortcut = s.c_str() + offset;
|
|
|
|
if (!strcmp(shortcut, "Esc"))
|
|
|
|
shortcut = "\e";
|
|
|
|
|
|
|
|
if (!strcmp(shortcut, "F11")) {
|
|
|
|
setFunctionKeyShortcut(item, NSF11FunctionKey);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(shortcut, "F12")) {
|
|
|
|
setFunctionKeyShortcut(item, NSF12FunctionKey);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
[item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]];
|
|
|
|
NSUInteger modifiers = 0;
|
|
|
|
if (hasCtrl) modifiers |= NSControlKeyMask;
|
|
|
|
if (hasShift) modifiers |= NSShiftKeyMask;
|
|
|
|
if (hasAlt) modifiers |= NSAlternateKeyMask;
|
|
|
|
|
|
|
|
[item setKeyEquivalentModifierMask:modifiers];
|
|
|
|
}
|
|
|
|
|
2011-11-20 13:23:52 +00:00
|
|
|
class EnabledListener : public SGPropertyChangeListener
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
EnabledListener(NSMenuItem* i) :
|
|
|
|
item(i)
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
virtual void valueChanged(SGPropertyNode *node)
|
|
|
|
{
|
|
|
|
BOOL b = node->getBoolValue();
|
|
|
|
[item setEnabled:b];
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
NSMenuItem* item;
|
|
|
|
};
|
|
|
|
|
|
|
|
FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
|
|
|
|
{
|
|
|
|
delegate = [[CocoaMenuDelegate alloc] init];
|
|
|
|
delegate.peer = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
|
|
|
|
{
|
|
|
|
[delegate release];
|
|
|
|
}
|
|
|
|
|
2012-01-06 23:46:35 +00:00
|
|
|
static bool labelIsSeparator(NSString* s)
|
2011-11-20 13:23:52 +00:00
|
|
|
{
|
2012-01-06 23:46:35 +00:00
|
|
|
return [s hasPrefix:@"---"];
|
2011-11-20 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
|
|
|
|
if (!n->hasValue("enabled")) {
|
|
|
|
n->setBoolValue("enabled", true);
|
|
|
|
}
|
|
|
|
|
2012-01-06 23:46:35 +00:00
|
|
|
string shortcut;
|
2011-11-20 13:23:52 +00:00
|
|
|
string l = n->getStringValue("label");
|
|
|
|
string::size_type pos = l.find("(");
|
|
|
|
if (pos != string::npos) {
|
2012-01-06 23:46:35 +00:00
|
|
|
string full(l);
|
|
|
|
l = full.substr(0, pos);
|
|
|
|
shortcut = full.substr(pos + 1, full.size() - (pos + 2));
|
2011-11-20 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
2012-01-06 23:46:35 +00:00
|
|
|
NSString* label = stdStringToCocoa(strutils::simplify(l));
|
|
|
|
|
2011-11-20 13:23:52 +00:00
|
|
|
NSMenuItem* item;
|
|
|
|
if (index >= [menu numberOfItems]) {
|
2012-01-06 23:46:35 +00:00
|
|
|
if (labelIsSeparator(label)) {
|
2011-11-20 13:23:52 +00:00
|
|
|
item = [NSMenuItem separatorItem];
|
|
|
|
[menu addItem:item];
|
|
|
|
} else {
|
2012-01-06 23:46:35 +00:00
|
|
|
item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
|
|
|
|
if (!shortcut.empty()) {
|
|
|
|
setItemShortcutFromString(item, shortcut);
|
|
|
|
}
|
|
|
|
|
2011-11-20 13:23:52 +00:00
|
|
|
n->getNode("enabled")->addChangeListener(new EnabledListener(item));
|
|
|
|
[item setTarget:delegate];
|
|
|
|
[item setAction:@selector(itemAction:)];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
item = [menu itemAtIndex:index];
|
|
|
|
[item setTitle:label];
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL enabled = n->getBoolValue("enabled");
|
|
|
|
[item setEnabled:enabled];
|
|
|
|
|
|
|
|
SGBindingList bl;
|
|
|
|
BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
|
|
|
|
// have to clone the bindings, since SGBinding takes ownership of the
|
|
|
|
// passed in node. Seems like something is wrong here, but following the
|
|
|
|
// PUI code for the moment.
|
|
|
|
SGPropertyNode* cloned(new SGPropertyNode);
|
|
|
|
copyProperties(binding, cloned);
|
|
|
|
bl.push_back(new SGBinding(cloned, globals->get_props()));
|
|
|
|
}
|
|
|
|
|
|
|
|
itemBindings[item] = bl;
|
|
|
|
++index;
|
|
|
|
} // of item iteration
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
|
|
|
|
{
|
|
|
|
MenuItemBindings::iterator it = itemBindings.find(item);
|
|
|
|
if (it == itemBindings.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
|
|
|
|
b->fire();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGCocoaMenuBar::FGCocoaMenuBar() :
|
|
|
|
p(new CocoaMenuBarPrivate)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
FGCocoaMenuBar::~FGCocoaMenuBar()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGCocoaMenuBar::init()
|
|
|
|
{
|
|
|
|
NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
|
|
|
|
SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
|
|
|
|
if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
|
|
|
|
[previousMenu setTitle:@"FlightGear"];
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
|
|
|
|
NSString* label = stdStringToCocoa(n->getStringValue("label"));
|
|
|
|
NSMenuItem* item = [mainBar itemWithTitle:label];
|
|
|
|
NSMenu* menu;
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
|
|
|
|
item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
|
|
|
|
item.tag = index + 400;
|
|
|
|
|
|
|
|
menu = [[NSMenu alloc] init];
|
|
|
|
menu.title = label;
|
|
|
|
[menu setAutoenablesItems:NO];
|
|
|
|
[mainBar setSubmenu:menu forItem:item];
|
|
|
|
[menu autorelease];
|
|
|
|
} else {
|
|
|
|
menu = item.submenu;
|
|
|
|
}
|
|
|
|
|
|
|
|
// synchronise menu with properties
|
|
|
|
p->menuFromProps(menu, n);
|
|
|
|
++index;
|
|
|
|
previousMenu = item;
|
2012-01-25 18:56:51 +00:00
|
|
|
|
|
|
|
// track menu enable/disable state
|
|
|
|
if (!n->hasValue("enabled")) {
|
|
|
|
n->setBoolValue("enabled", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
n->getNode("enabled")->addChangeListener(new EnabledListener(item));
|
2011-11-20 13:23:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FGCocoaMenuBar::isVisible() const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGCocoaMenuBar::show()
|
|
|
|
{
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGCocoaMenuBar::hide()
|
|
|
|
{
|
|
|
|
// no-op
|
|
|
|
}
|