]> git.mxchange.org Git - flightgear.git/blob - src/GUI/FGCocoaMenuBar.mm
Work on new download-dir option
[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 #include <GUI/CocoaHelpers_private.h>
15
16 #include <iostream>
17
18 using std::string;
19 using std::map;
20 using std::cout;
21 using namespace simgear;
22
23 typedef std::map<NSMenuItem*, SGBindingList> MenuItemBindings;
24
25 @class CocoaMenuDelegate;
26
27 namespace {
28     
29     class CocoaEnabledListener : public SGPropertyChangeListener
30     {
31     public:
32         CocoaEnabledListener(SGPropertyNode_ptr prop, NSMenuItem* i) :
33             property(prop->getNode("enabled")),
34             item(i)
35         {
36             if (property.get()) {
37                 property->addChangeListener(this);
38             }
39         }
40         
41         ~CocoaEnabledListener()
42         {
43             if (property.get()) {
44                 property->removeChangeListener(this);
45             }
46         }
47         
48         
49         virtual void valueChanged(SGPropertyNode *node)
50         {
51             CocoaAutoreleasePool pool;
52             BOOL b = node->getBoolValue();
53             [item setEnabled:b];
54         }
55         
56     private:
57         SGPropertyNode_ptr property;
58         NSMenuItem* item;
59     };
60 } // of anonymous namespace
61
62 class FGCocoaMenuBar::CocoaMenuBarPrivate
63 {
64 public:
65   CocoaMenuBarPrivate();
66   ~CocoaMenuBarPrivate();
67   
68   void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
69   
70   void fireBindingsForItem(NSMenuItem* item);
71   
72 public:
73   CocoaMenuDelegate* delegate;
74   
75   MenuItemBindings itemBindings;
76     std::vector<CocoaEnabledListener*> listeners;
77 };
78
79 // prior to the 10.6 SDK, NSMenuDelegate was an informal protocol
80 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060
81 @protocol NSMenuDelegate <NSObject>
82 @end
83 #endif
84
85 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
86 @private
87   FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
88 }
89
90 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
91 @end
92
93 @implementation CocoaMenuDelegate
94
95 @synthesize peer;
96
97 - (void) itemAction:(id) sender
98 {
99   peer->fireBindingsForItem((NSMenuItem*) sender);
100 }
101
102 @end
103
104 static void setFunctionKeyShortcut(const std::string& shortcut, NSMenuItem* item)
105 {
106     unichar shortcutChar = NSF1FunctionKey;
107     if (shortcut == "F1") {
108         shortcutChar = NSF1FunctionKey;
109     } else if (shortcut == "F2") {
110         shortcutChar = NSF2FunctionKey;
111     } else if (shortcut == "F3") {
112         shortcutChar = NSF3FunctionKey;
113     } else if (shortcut == "F10") {
114         shortcutChar = NSF10FunctionKey;
115     } else if (shortcut == "F11") {
116         shortcutChar = NSF11FunctionKey;
117     } else if (shortcut == "F12") {
118         shortcutChar = NSF12FunctionKey;
119     } else {
120         SG_LOG(SG_GENERAL, SG_WARN, "CocoaMenu:setFunctionKeyShortcut: unsupported:" << shortcut);
121     }
122     
123   unichar ch[1];
124   ch[0] = shortcutChar;
125   [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
126   [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
127   
128 }
129
130
131
132 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
133 {
134     std::string shortcut;
135   
136   bool hasCtrl = strutils::starts_with(s, "Ctrl-"); 
137   bool hasShift = strutils::starts_with(s, "Shift-");
138   bool hasAlt = strutils::starts_with(s, "Alt-");
139   
140   int offset = 0; // character offset from start of string
141   if (hasShift) offset += 6;
142   if (hasCtrl) offset += 5;
143   if (hasAlt) offset += 4;
144   
145   shortcut = s.substr(offset);
146   if (shortcut == "Esc")
147     shortcut = "\e";    
148   
149     if ((shortcut.length() >= 2) && (shortcut[0] == 'F') && isdigit(shortcut[1])) {
150         setFunctionKeyShortcut(shortcut, item);
151         return;
152     }
153
154     simgear::strutils::lowercase(shortcut);
155   [item setKeyEquivalent:[NSString stringWithCString:shortcut.c_str() encoding:NSUTF8StringEncoding]];
156   NSUInteger modifiers = 0;
157   if (hasCtrl) modifiers |= NSControlKeyMask;
158   if (hasShift) modifiers |= NSShiftKeyMask;
159   if (hasAlt) modifiers |= NSAlternateKeyMask;
160   
161   [item setKeyEquivalentModifierMask:modifiers];
162 }
163
164 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
165 {
166   delegate = [[CocoaMenuDelegate alloc] init];
167   delegate.peer = this;
168 }
169   
170 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
171 {
172   CocoaAutoreleasePool pool;
173   [delegate release];
174 }
175   
176 static bool labelIsSeparator(NSString* s)
177 {
178   return [s hasPrefix:@"---"];
179 }
180   
181 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
182 {
183   int index = 0;
184   BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
185     if (!n->hasValue("enabled")) {
186       n->setBoolValue("enabled", true);
187     }
188     
189     string l = getLocalizedLabel(n);
190     NSString* label = stdStringToCocoa(strutils::simplify(l));
191     string shortcut = n->getStringValue("key");
192     
193     NSMenuItem* item;
194     if (index >= [menu numberOfItems]) {
195       if (labelIsSeparator(label)) {
196         item = [NSMenuItem separatorItem];
197         [menu addItem:item];
198       } else {        
199         item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
200         if (!shortcut.empty()) {
201           setItemShortcutFromString(item, shortcut);
202         }
203         
204         CocoaEnabledListener* cl = new CocoaEnabledListener(n, item);
205         listeners.push_back(cl);
206           
207         [item setTarget:delegate];
208         [item setAction:@selector(itemAction:)];
209       }
210     } else {
211       item = [menu itemAtIndex:index];
212       [item setTitle:label]; 
213     }
214     
215     BOOL enabled = n->getBoolValue("enabled");
216     [item setEnabled:enabled];
217     
218     SGBindingList bl = readBindingList(n->getChildren("binding"), globals->get_props());
219       
220     itemBindings[item] = bl;    
221     ++index;
222   } // of item iteration
223 }
224
225 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
226 {
227   MenuItemBindings::iterator it = itemBindings.find(item);
228   if (it == itemBindings.end()) {
229     return;
230   }
231     
232   fireBindingList(it->second);
233 }
234
235 FGCocoaMenuBar::FGCocoaMenuBar() :
236   p(new CocoaMenuBarPrivate)
237 {
238   
239 }
240
241 FGCocoaMenuBar::~FGCocoaMenuBar()
242 {
243     CocoaAutoreleasePool ap;
244     NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
245
246     int num = [mainBar numberOfItems];
247     for (int index=1; index < num; ++index) {
248         NSMenuItem* topLevelItem = [mainBar itemAtIndex:index];
249         [topLevelItem.submenu removeAllItems];
250     }
251     
252     std::vector<CocoaEnabledListener*>::iterator it;
253     for (it = p->listeners.begin(); it != p->listeners.end(); ++it) {
254         delete *it;
255     }
256     
257     // owing to the bizarre destructor behaviour of SGBinding, we need
258     // to explicitly clear these bindings. (PUIMenuBar takes a different
259     // approach, and copies each binding into /sim/bindings)
260     MenuItemBindings::iterator j;
261     for (j = p->itemBindings.begin(); j != p->itemBindings.end(); ++j) {
262         clearBindingList(j->second);
263     }
264 }
265
266 void FGCocoaMenuBar::init()
267 {
268   CocoaAutoreleasePool pool;
269   
270   NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
271   SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
272   
273   int index = 0;
274   NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
275   if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
276     [previousMenu setTitle:@"FlightGear"];
277   }
278   
279   BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
280     NSString* label = stdStringToCocoa(getLocalizedLabel(n));
281     NSMenuItem* item = [mainBar itemWithTitle:label];
282     NSMenu* menu;
283     
284     if (!item) {
285       NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1; 
286       item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
287       item.tag = index + 400;
288       
289       menu = [[NSMenu alloc] init];
290       menu.title = label;
291       [menu setAutoenablesItems:NO];
292       [mainBar setSubmenu:menu forItem:item];
293       [menu autorelease];
294     } else {
295       menu = item.submenu;
296     }
297     
298   // synchronise menu with properties
299     p->menuFromProps(menu, n);
300     ++index;
301     previousMenu = item;
302     
303   // track menu enable/disable state
304     if (!n->hasValue("enabled")) {
305       n->setBoolValue("enabled", true);
306     }
307     
308     CocoaEnabledListener* l = new CocoaEnabledListener( n, item);
309     p->listeners.push_back(l);
310   }
311 }
312
313 bool FGCocoaMenuBar::isVisible() const
314 {
315   return true;
316 }
317
318 void FGCocoaMenuBar::show()
319 {
320   // no-op
321 }
322
323 void FGCocoaMenuBar::hide()
324 {
325   // no-op
326 }
327