]> git.mxchange.org Git - flightgear.git/blob - src/GUI/FGCocoaMenuBar.mm
451fe5ed1938fbc6b273b7811072b2388eb0297e
[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 shortcut;
176     string l = n->getStringValue("label");
177     string::size_type pos = l.find("(");
178     if (pos != string::npos) {
179       string full(l);
180       l = full.substr(0, pos);
181       shortcut = full.substr(pos + 1, full.size() - (pos + 2));
182     }
183     
184     NSString* label = stdStringToCocoa(strutils::simplify(l));
185     
186     NSMenuItem* item;
187     if (index >= [menu numberOfItems]) {
188       if (labelIsSeparator(label)) {
189         item = [NSMenuItem separatorItem];
190         [menu addItem:item];
191       } else {        
192         item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
193         if (!shortcut.empty()) {
194           setItemShortcutFromString(item, shortcut);
195         }
196         
197         n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
198         [item setTarget:delegate];
199         [item setAction:@selector(itemAction:)];
200       }
201     } else {
202       item = [menu itemAtIndex:index];
203       [item setTitle:label]; 
204     }
205     
206     BOOL enabled = n->getBoolValue("enabled");
207     [item setEnabled:enabled];
208     
209     SGBindingList bl;
210     BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
211     // have to clone the bindings, since SGBinding takes ownership of the
212     // passed in node. Seems like something is wrong here, but following the
213     // PUI code for the moment.
214       SGPropertyNode* cloned(new SGPropertyNode);
215       copyProperties(binding, cloned);
216       bl.push_back(new SGBinding(cloned, globals->get_props()));
217     }
218     
219     itemBindings[item] = bl;    
220     ++index;
221   } // of item iteration
222 }
223
224 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
225 {
226   MenuItemBindings::iterator it = itemBindings.find(item);
227   if (it == itemBindings.end()) {
228     return;
229   }
230
231   BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
232     b->fire();
233   }
234 }
235
236 FGCocoaMenuBar::FGCocoaMenuBar() :
237   p(new CocoaMenuBarPrivate)
238 {
239   
240 }
241
242 FGCocoaMenuBar::~FGCocoaMenuBar()
243 {
244   
245 }
246
247 void FGCocoaMenuBar::init()
248 {
249   CocoaAutoreleasePool pool;
250   
251   NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
252   SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
253   
254   int index = 0;
255   NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
256   if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
257     [previousMenu setTitle:@"FlightGear"];
258   }
259   
260   BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
261     NSString* label = stdStringToCocoa(n->getStringValue("label"));
262     NSMenuItem* item = [mainBar itemWithTitle:label];
263     NSMenu* menu;
264     
265     if (!item) {
266       NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1; 
267       item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
268       item.tag = index + 400;
269       
270       menu = [[NSMenu alloc] init];
271       menu.title = label;
272       [menu setAutoenablesItems:NO];
273       [mainBar setSubmenu:menu forItem:item];
274       [menu autorelease];
275     } else {
276       menu = item.submenu;
277     }
278     
279   // synchronise menu with properties
280     p->menuFromProps(menu, n);
281     ++index;
282     previousMenu = item;
283     
284   // track menu enable/disable state
285     if (!n->hasValue("enabled")) {
286       n->setBoolValue("enabled", true);
287     }
288     
289     n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
290   }
291 }
292
293 bool FGCocoaMenuBar::isVisible() const
294 {
295   return true;
296 }
297
298 void FGCocoaMenuBar::show()
299 {
300   // no-op
301 }
302
303 void FGCocoaMenuBar::hide()
304 {
305   // no-op
306 }