]> git.mxchange.org Git - flightgear.git/blob - src/GUI/FGCocoaMenuBar.mm
Fix Linux compile / math dependency
[flightgear.git] / src / GUI / FGCocoaMenuBar.mm
1 #include "FGCocoaMenuBar.hxx"
2
3 #include <Cocoa/Cocoa.h>
4
5 #include <boost/foreach.hpp>
6
7 #include <simgear/props/props.hxx>
8 #include <simgear/props/props_io.hxx>
9 #include <simgear/debug/logstream.hxx>
10 #include <simgear/structure/SGBinding.hxx>
11 #include <simgear/misc/strutils.hxx>
12
13 #include <Main/fg_props.hxx>
14
15 #include <iostream>
16
17 using std::string;
18 using std::map;
19 using std::cout;
20 using namespace simgear;
21
22 typedef std::map<NSMenuItem*, SGBindingList> MenuItemBindings;
23
24 @class CocoaMenuDelegate;
25
26 class FGCocoaMenuBar::CocoaMenuBarPrivate
27 {
28 public:
29   CocoaMenuBarPrivate();
30   ~CocoaMenuBarPrivate();
31   
32   void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
33   
34   void fireBindingsForItem(NSMenuItem* item);
35   
36 public:
37   CocoaMenuDelegate* delegate;
38   
39   MenuItemBindings itemBindings;
40 };
41
42
43 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
44 @private
45   FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
46 }
47
48 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
49 @end
50
51 @implementation CocoaMenuDelegate
52
53 @synthesize peer;
54
55 - (void) itemAction:(id) sender
56 {
57   peer->fireBindingsForItem((NSMenuItem*) sender);
58 }
59
60 @end
61
62 static NSString* stdStringToCocoa(const string& s)
63 {
64   return [NSString stringWithUTF8String:s.c_str()];
65 }
66
67 static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut)
68 {
69   unichar ch[1];
70   ch[0] = shortcut;
71   [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
72   [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
73   
74 }
75
76 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
77 {
78   const char* shortcut = "";
79   
80   bool hasCtrl = strutils::starts_with(s, "Ctrl-"); 
81   bool hasShift = strutils::starts_with(s, "Shift-");
82   bool hasAlt = strutils::starts_with(s, "Alt-");
83   
84   int offset = 0; // character offset from start of string
85   if (hasShift) offset += 6;
86   if (hasCtrl) offset += 5;
87   if (hasAlt) offset += 4;
88   
89   shortcut = s.c_str() + offset;
90   if (!strcmp(shortcut, "Esc"))
91     shortcut = "\e";    
92   
93   if (!strcmp(shortcut, "F11")) {
94     setFunctionKeyShortcut(item, NSF11FunctionKey);
95     return;
96   }
97   
98   if (!strcmp(shortcut, "F12")) {
99     setFunctionKeyShortcut(item, NSF12FunctionKey);
100     return;
101   }
102   
103   [item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]];
104   NSUInteger modifiers = 0;
105   if (hasCtrl) modifiers |= NSControlKeyMask;
106   if (hasShift) modifiers |= NSShiftKeyMask;
107   if (hasAlt) modifiers |= NSAlternateKeyMask;
108   
109   [item setKeyEquivalentModifierMask:modifiers];
110 }
111
112 namespace {
113   class CocoaAutoreleasePool
114   {
115   public:
116     CocoaAutoreleasePool()
117     {
118       pool = [[NSAutoreleasePool alloc] init];
119     }
120     
121     ~CocoaAutoreleasePool()
122     {
123       [pool release];
124     }
125     
126   private:
127     NSAutoreleasePool* pool;
128   };
129   
130   class CocoaEnabledListener : public SGPropertyChangeListener
131   {
132   public:
133     CocoaEnabledListener(NSMenuItem* i) :
134       item(i)
135     {}
136     
137     
138     virtual void valueChanged(SGPropertyNode *node) 
139     {
140       CocoaAutoreleasePool pool;
141       BOOL b = node->getBoolValue();
142       [item setEnabled:b];
143     }
144     
145   private:
146     NSMenuItem* item;
147   };
148 } // of anonymous namespace
149
150 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
151 {
152   delegate = [[CocoaMenuDelegate alloc] init];
153   delegate.peer = this;
154 }
155   
156 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
157 {
158   CocoaAutoreleasePool pool;
159   [delegate release];
160 }
161   
162 static bool labelIsSeparator(NSString* s)
163 {
164   return [s hasPrefix:@"---"];
165 }
166   
167 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
168 {
169   int index = 0;
170   BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
171     if (!n->hasValue("enabled")) {
172       n->setBoolValue("enabled", true);
173     }
174     
175     string l = getLocalizedLabel(n);
176     NSString* label = stdStringToCocoa(strutils::simplify(l));
177     string shortcut = n->getStringValue("key");
178     
179     NSMenuItem* item;
180     if (index >= [menu numberOfItems]) {
181       if (labelIsSeparator(label)) {
182         item = [NSMenuItem separatorItem];
183         [menu addItem:item];
184       } else {        
185         item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
186         if (!shortcut.empty()) {
187           setItemShortcutFromString(item, shortcut);
188         }
189         
190         n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
191         [item setTarget:delegate];
192         [item setAction:@selector(itemAction:)];
193       }
194     } else {
195       item = [menu itemAtIndex:index];
196       [item setTitle:label]; 
197     }
198     
199     BOOL enabled = n->getBoolValue("enabled");
200     [item setEnabled:enabled];
201     
202     SGBindingList bl;
203     BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
204     // have to clone the bindings, since SGBinding takes ownership of the
205     // passed in node. Seems like something is wrong here, but following the
206     // PUI code for the moment.
207       SGPropertyNode* cloned(new SGPropertyNode);
208       copyProperties(binding, cloned);
209       bl.push_back(new SGBinding(cloned, globals->get_props()));
210     }
211     
212     itemBindings[item] = bl;    
213     ++index;
214   } // of item iteration
215 }
216
217 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
218 {
219   MenuItemBindings::iterator it = itemBindings.find(item);
220   if (it == itemBindings.end()) {
221     return;
222   }
223
224   BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
225     b->fire();
226   }
227 }
228
229 FGCocoaMenuBar::FGCocoaMenuBar() :
230   p(new CocoaMenuBarPrivate)
231 {
232   
233 }
234
235 FGCocoaMenuBar::~FGCocoaMenuBar()
236 {
237   
238 }
239
240 void FGCocoaMenuBar::init()
241 {
242   CocoaAutoreleasePool pool;
243   
244   NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
245   SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
246   
247   int index = 0;
248   NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
249   if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
250     [previousMenu setTitle:@"FlightGear"];
251   }
252   
253   BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
254     NSString* label = stdStringToCocoa(getLocalizedLabel(n));
255     NSMenuItem* item = [mainBar itemWithTitle:label];
256     NSMenu* menu;
257     
258     if (!item) {
259       NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1; 
260       item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
261       item.tag = index + 400;
262       
263       menu = [[NSMenu alloc] init];
264       menu.title = label;
265       [menu setAutoenablesItems:NO];
266       [mainBar setSubmenu:menu forItem:item];
267       [menu autorelease];
268     } else {
269       menu = item.submenu;
270     }
271     
272   // synchronise menu with properties
273     p->menuFromProps(menu, n);
274     ++index;
275     previousMenu = item;
276     
277   // track menu enable/disable state
278     if (!n->hasValue("enabled")) {
279       n->setBoolValue("enabled", true);
280     }
281     
282     n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
283   }
284 }
285
286 bool FGCocoaMenuBar::isVisible() const
287 {
288   return true;
289 }
290
291 void FGCocoaMenuBar::show()
292 {
293   // no-op
294 }
295
296 void FGCocoaMenuBar::hide()
297 {
298   // no-op
299 }