47
47
using MonoDevelop.Ide.Commands;
48
48
using MonoDevelop.Ide.Desktop;
49
49
using MonoDevelop.MacInterop;
50
using MonoDevelop.Components.MainToolbar;
51
using MonoDevelop.MacIntegration.MacMenu;
51
53
namespace MonoDevelop.MacIntegration
53
55
public class MacPlatformService : PlatformService
55
static TimerCounter timer = InstrumentationService.CreateTimerCounter ("Mac Platform Initialization", "Platform Service");
56
static TimerCounter mimeTimer = InstrumentationService.CreateTimerCounter ("Mac Mime Database", "Platform Service");
58
static bool setupFail, initedApp, initedGlobal;
60
static Lazy<Dictionary<string, string>> mimemap;
57
const string monoDownloadUrl = "http://www.go-mono.com/mono-downloads/download.html";
59
TimerCounter timer = InstrumentationService.CreateTimerCounter ("Mac Platform Initialization", "Platform Service");
60
TimerCounter mimeTimer = InstrumentationService.CreateTimerCounter ("Mac Mime Database", "Platform Service");
62
static bool initedGlobal;
63
bool setupFail, initedApp;
65
Lazy<Dictionary<string, string>> mimemap;
62
67
//this is a BCD value of the form "xxyz", where x = major, y = minor, z = bugfix
63
68
//eg. 0x1071 = 10.7.1
64
static int systemVersion;
66
static MacPlatformService ()
71
public MacPlatformService ()
74
throw new Exception ("Only one MacPlatformService instance allowed");
68
77
timer.BeginTiming ();
70
79
systemVersion = Carbon.Gestalt ("sysv");
72
81
mimemap = new Lazy<Dictionary<string, string>> (LoadMimeMapAsync);
74
CheckGtkVersion (2, 24, 0);
76
83
//make sure the menu app name is correct even when running Mono 2.6 preview, or not running from the .app
77
84
Carbon.SetProcessName (BrandingService.ApplicationName);
79
MonoDevelop.MacInterop.Cocoa.InitMonoMac ();
88
CheckGtkVersion (2, 24, 14);
81
90
timer.Trace ("Installing App Event Handlers");
84
93
timer.EndTiming ();
87
//Mac GTK+ behaviour isn't completely stable even between micro releases
88
96
static void CheckGtkVersion (uint major, uint minor, uint micro)
90
string url = "http://www.go-mono.com/mono-downloads/download.html";
92
// to require exact version, also check : || Gtk.Global.CheckVersion (major, minor, micro + 1) == null
98
// to require exact version, also check
99
//: || Gtk.Global.CheckVersion (major, minor, micro + 1) == null
93
101
if (Gtk.Global.CheckVersion (major, minor, micro) != null) {
95
MonoDevelop.Core.LoggingService.LogFatalError ("GTK+ version is incompatible with required version {0}.{1}.{2}.", major, minor, micro);
103
LoggingService.LogFatalError (
104
"GTK+ version is incompatible with required version {0}.{1}.{2}.",
97
AlertButton downloadButton = new AlertButton ("Download...", null);
108
var downloadButton = new AlertButton ("Download Mono Framework", null);
98
109
if (downloadButton == MessageService.GenericAlert (
100
"Incompatible Mono Framework Version",
101
"MonoDevelop requires a newer version of the Mono Framework.",
102
new AlertButton ("Cancel", null), downloadButton))
111
GettextCatalog.GetString ("Some dependencies need to be updated"),
112
GettextCatalog.GetString (
113
"{0} requires a newer version of GTK+, which is included with the Mono Framework. Please " +
114
"download and install the latest stable Mono Framework package and restart {0}.",
115
BrandingService.ApplicationName
117
new AlertButton ("Quit", null), downloadButton))
119
OpenUrl (monoDownloadUrl);
107
122
Environment.Exit (1);
165
184
} catch (Exception ex){
166
MonoDevelop.Core.LoggingService.LogError ("Could not load Apache mime database", ex);
185
LoggingService.LogError ("Could not load Apache mime database", ex);
168
187
mimeTimer.EndTiming ();
172
HashSet<object> ignoreCommands = new HashSet<object> () {
173
CommandManager.ToCommandId (HelpCommands.About),
174
CommandManager.ToCommandId (EditCommands.DefaultPolicies),
175
CommandManager.ToCommandId (EditCommands.MonodevelopPreferences),
176
CommandManager.ToCommandId (ToolCommands.AddinManager),
177
CommandManager.ToCommandId (FileCommands.Exit),
180
public override bool SetGlobalMenu (CommandManager commandManager, string commandMenuAddinPath)
191
public override bool SetGlobalMenu (CommandManager commandManager, string commandMenuAddinPath, string appMenuAddinPath)
186
197
InitApp (commandManager);
199
NSApplication.SharedApplication.HelpMenu = null;
201
var rootMenu = NSApplication.SharedApplication.MainMenu;
202
if (rootMenu == null) {
203
rootMenu = new NSMenu ();
204
NSApplication.SharedApplication.MainMenu = rootMenu;
206
rootMenu.RemoveAllItems ();
209
CommandEntrySet appCes = commandManager.CreateCommandEntrySet (appMenuAddinPath);
210
rootMenu.AddItem (new MDSubMenuItem (commandManager, appCes));
187
212
CommandEntrySet ces = commandManager.CreateCommandEntrySet (commandMenuAddinPath);
188
MacMainMenu.Recreate (commandManager, ces, ignoreCommands);
213
foreach (CommandEntry ce in ces) {
214
rootMenu.AddItem (new MDSubMenuItem (commandManager, (CommandEntrySet) ce));
189
216
} catch (Exception ex) {
191
MacMainMenu.Destroy (true);
218
var m = NSApplication.SharedApplication.MainMenu;
222
NSApplication.SharedApplication.MainMenu = null;
193
MonoDevelop.Core.LoggingService.LogError ("Could not install global menu", ex);
224
LoggingService.LogError ("Could not install global menu", ex);
194
225
setupFail = true;
231
static void OnCommandActivating (object sender, CommandActivationEventArgs args)
233
if (args.Source != CommandSource.Keybinding)
235
var m = NSApplication.SharedApplication.MainMenu;
237
foreach (NSMenuItem item in m.ItemArray ()) {
238
var submenu = item.Submenu as MDMenu;
239
if (submenu != null && submenu.FlashIfContainsCommand (args.CommandId))
201
static void InitApp (CommandManager commandManager)
245
void InitApp (CommandManager commandManager)
206
MacMainMenu.AddCommandIDMappings (new Dictionary<object, CarbonCommandID> ()
208
{ CommandManager.ToCommandId (EditCommands.Copy), CarbonCommandID.Copy },
209
{ CommandManager.ToCommandId (EditCommands.Cut), CarbonCommandID.Cut },
210
//FIXME: for some reason mapping this causes two menu items to be created
211
// { EditCommands.MonodevelopPreferences, CarbonCommandID.Preferences },
212
{ CommandManager.ToCommandId (EditCommands.Redo), CarbonCommandID.Redo },
213
{ CommandManager.ToCommandId (EditCommands.Undo), CarbonCommandID.Undo },
214
{ CommandManager.ToCommandId (EditCommands.SelectAll), CarbonCommandID.SelectAll },
215
{ CommandManager.ToCommandId (FileCommands.NewFile), CarbonCommandID.New },
216
{ CommandManager.ToCommandId (FileCommands.OpenFile), CarbonCommandID.Open },
217
{ CommandManager.ToCommandId (FileCommands.Save), CarbonCommandID.Save },
218
{ CommandManager.ToCommandId (FileCommands.SaveAs), CarbonCommandID.SaveAs },
219
{ CommandManager.ToCommandId (FileCommands.CloseFile), CarbonCommandID.Close },
220
{ CommandManager.ToCommandId (FileCommands.Exit), CarbonCommandID.Quit },
221
{ CommandManager.ToCommandId (FileCommands.ReloadFile), CarbonCommandID.Revert },
222
{ CommandManager.ToCommandId (HelpCommands.About), CarbonCommandID.About },
223
{ CommandManager.ToCommandId (HelpCommands.Help), CarbonCommandID.AppHelp },
250
commandManager.CommandActivating += OnCommandActivating;
226
252
//mac-ify these command names
227
253
commandManager.GetCommand (EditCommands.MonodevelopPreferences).Text = GettextCatalog.GetString ("Preferences...");
228
254
commandManager.GetCommand (EditCommands.DefaultPolicies).Text = GettextCatalog.GetString ("Custom Policies...");
229
commandManager.GetCommand (HelpCommands.About).Text = string.Format (GettextCatalog.GetString ("About {0}"), BrandingService.ApplicationName);
255
commandManager.GetCommand (HelpCommands.About).Text = GettextCatalog.GetString ("About {0}", BrandingService.ApplicationName);
256
commandManager.GetCommand (MacIntegrationCommands.HideWindow).Text = GettextCatalog.GetString ("Hide {0}", BrandingService.ApplicationName);
230
257
commandManager.GetCommand (ToolCommands.AddinManager).Text = GettextCatalog.GetString ("Add-in Manager...");
232
259
initedApp = true;
233
MacMainMenu.SetAppQuitCommand (CommandManager.ToCommandId (FileCommands.Exit));
234
MacMainMenu.AddAppMenuItems (
236
CommandManager.ToCommandId (HelpCommands.About),
237
CommandManager.ToCommandId (MonoDevelop.Ide.Updater.UpdateCommands.CheckForUpdates),
238
CommandManager.ToCommandId (Command.Separator),
239
CommandManager.ToCommandId (EditCommands.MonodevelopPreferences),
240
CommandManager.ToCommandId (EditCommands.DefaultPolicies),
241
CommandManager.ToCommandId (ToolCommands.AddinManager));
243
261
IdeApp.Workbench.RootWindow.DeleteEvent += HandleDeleteEvent;
263
if (MacSystemInformation.OsVersion >= MacSystemInformation.Lion) {
264
IdeApp.Workbench.RootWindow.Realized += (sender, args) => {
265
var win = GtkQuartz.GetWindow ((Gtk.Window) sender);
266
win.CollectionBehavior |= NSWindowCollectionBehavior.FullScreenPrimary;
246
static void GlobalSetup ()
248
if (initedGlobal || setupFail)
252
273
//FIXME: should we remove these when finalizing?
254
ApplicationEvents.Quit += delegate (object sender, ApplicationQuitEventArgs e) {
255
//FIXME: can we avoid replying to the message until the app quits?
256
//There's NSTerminateLate but I'm not sure how to access it from carbon, maybe
257
//we need to swizzle methods into the app's NSApplicationDelegate.
258
//Also, it stops the main CFRunLoop, hopefully GTK dialogs use a child runloop.
259
//For now, just bounce.
260
var topDialog = MessageService.GetDefaultModalParent () as Gtk.Dialog;
261
if (topDialog != null && topDialog.Modal) {
262
NSApplication.SharedApplication.RequestUserAttention (
263
NSRequestUserAttentionType.CriticalRequest);
275
ApplicationEvents.Quit += delegate (object sender, ApplicationQuitEventArgs e)
277
// We can only attempt to quit safely if all windows are GTK windows and not modal
278
if (GtkQuartz.GetToplevels ().All (t => t.Value != null && (!t.Value.Visible || !t.Value.Modal))) {
279
e.UserCancelled = !IdeApp.Exit ();
265
//FIXME: delay this until all existing modal dialogs were closed
267
e.UserCancelled = true;
284
// When a modal dialog is running, things are much harder. We can't just shut down MD behind the
285
// dialog, and aborting the dialog may not be appropriate.
287
// There's NSTerminateLater but I'm not sure how to access it from carbon, maybe
288
// we need to swizzle methods into the app's NSApplicationDelegate.
289
// Also, it stops the main CFRunLoop and enters a special runloop mode, not sure how that would
290
// interact with GTK+.
292
// For now, just bounce
293
NSApplication.SharedApplication.RequestUserAttention (NSRequestUserAttentionType.CriticalRequest);
294
// and abort the quit.
295
e.UserCancelled = true;
268
296
e.Handled = true;
519
553
window.Present ();
520
554
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
557
static Cairo.Color ConvertColor (NSColor color)
560
if (color.ColorSpaceName == NSColorSpace.DeviceWhite) {
562
r = g = b = color.WhiteComponent;
564
color.GetRgba (out r, out g, out b, out a);
566
return new Cairo.Color (r, g, b, a);
569
static int GetTitleBarHeight ()
571
var frame = new RectangleF (0, 0, 100, 100);
572
var rect = NSWindow.ContentRectFor (frame, NSWindowStyle.Titled);
573
return (int)(frame.Height - rect.Height);
577
static NSImage LoadImage (string resource)
580
using (var stream = typeof (MacPlatformService).Assembly.GetManifestResourceStream (resource)) {
581
buffer = new byte [stream.Length];
582
stream.Read (buffer, 0, (int)stream.Length);
585
// Workaround: loading from file name.
586
var tmp = Path.GetTempFileName ();
587
File.WriteAllBytes (tmp, buffer);
588
var img = new NSImage (tmp);
593
internal override void SetMainWindowDecorations (Gtk.Window window)
595
NSWindow w = GtkQuartz.GetWindow (window);
598
var resource = "maintoolbarbg.png";
599
NSImage img = LoadImage (resource);
600
w.BackgroundColor = NSColor.FromPatternImage (img);
601
w.StyleMask |= NSWindowStyle.TexturedBackground;
604
internal override void RemoveWindowShadow (Gtk.Window window)
607
throw new ArgumentNullException ("window");
608
NSWindow w = GtkQuartz.GetWindow (window);
612
internal override MainToolbar CreateMainToolbar (Gtk.Window window)
614
NSApplication.Init ();
616
NSWindow w = GtkQuartz.GetWindow (window);
619
var resource = "maintoolbarbg.png";
620
NSImage img = LoadImage (resource);
621
var c = NSColor.FromPatternImage (img);
622
w.BackgroundColor = c;
623
w.StyleMask |= NSWindowStyle.TexturedBackground;
625
var result = new MainToolbar () {
626
Background = MonoDevelop.Components.CairoExtensions.LoadImage (typeof (MacPlatformService).Assembly, resource),
627
TitleBarHeight = GetTitleBarHeight ()
632
protected override RecentFiles CreateRecentFilesProvider ()
634
return new FdoRecentFiles (UserProfile.Current.LocalConfigDir.Combine ("RecentlyUsed.xml"));
637
public override bool GetIsFullscreen (Gtk.Window window)
639
if (MacSystemInformation.OsVersion < MacSystemInformation.Lion) {
640
return base.GetIsFullscreen (window);
643
NSWindow nswin = GtkQuartz.GetWindow (window);
644
return (nswin.StyleMask & NSWindowStyle.FullScreenWindow) != 0;
647
public override void SetIsFullscreen (Gtk.Window window, bool isFullscreen)
649
if (MacSystemInformation.OsVersion < MacSystemInformation.Lion) {
650
base.SetIsFullscreen (window, isFullscreen);
654
NSWindow nswin = GtkQuartz.GetWindow (window);
655
if (isFullscreen != ((nswin.StyleMask & NSWindowStyle.FullScreenWindow) != 0)) {
656
//HACK: workaround for MonoMac not allowing null as argument
657
MonoMac.ObjCRuntime.Messaging.void_objc_msgSend_IntPtr (
659
MonoMac.ObjCRuntime.Selector.GetHandle ("toggleFullScreen:"),