blob: 90439e569a6753a9b359e3e579b410f2dd70d7bf [file] [log] [blame]
[email protected]1bcdb532009-01-16 17:47:571// Copyright (c) 2009 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]2c47bc12009-04-10 20:14:005#import "chrome/browser/app_controller_mac.h"
[email protected]1bcdb532009-01-16 17:47:576
[email protected]288bfcd32009-09-14 18:14:467#include "app/l10n_util_mac.h"
[email protected]57750f822009-04-21 21:43:098#include "base/command_line.h"
[email protected]09729a552009-08-03 23:21:419#include "base/mac_util.h"
[email protected]2c47bc12009-04-10 20:14:0010#include "base/message_loop.h"
[email protected]57750f822009-04-21 21:43:0911#include "base/sys_string_conversions.h"
[email protected]2c47bc12009-04-10 20:14:0012#include "chrome/app/chrome_dll_resource.h"
13#include "chrome/browser/browser.h"
[email protected]57750f822009-04-21 21:43:0914#include "chrome/browser/browser_init.h"
[email protected]2c47bc12009-04-10 20:14:0015#include "chrome/browser/browser_list.h"
[email protected]5c238752009-06-13 10:29:0716#include "chrome/browser/browser_process.h"
[email protected]41741a962009-02-18 21:51:3917#include "chrome/browser/browser_shutdown.h"
[email protected]ce560f82009-06-03 09:39:4418#include "chrome/browser/browser_window.h"
[email protected]bde3dda2009-05-20 22:13:0719#import "chrome/browser/cocoa/about_window_controller.h"
[email protected]3f34599d2009-03-25 22:11:4320#import "chrome/browser/cocoa/bookmark_menu_bridge.h"
[email protected]a9e8afc2009-08-11 22:03:1721#import "chrome/browser/cocoa/browser_window_cocoa.h"
22#import "chrome/browser/cocoa/browser_window_controller.h"
[email protected]1c84c82c2009-07-27 15:37:2523#import "chrome/browser/cocoa/history_menu_bridge.h"
[email protected]93e181762009-05-29 14:30:3824#import "chrome/browser/cocoa/clear_browsing_data_controller.h"
[email protected]2bcec612009-05-14 17:50:5325#import "chrome/browser/cocoa/encoding_menu_controller_delegate_mac.h"
[email protected]42404382009-04-30 17:59:2426#import "chrome/browser/cocoa/preferences_window_controller.h"
[email protected]449dd2f2009-05-27 13:04:0027#import "chrome/browser/cocoa/tab_strip_controller.h"
28#import "chrome/browser/cocoa/tab_window_controller.h"
[email protected]2c47bc12009-04-10 20:14:0029#include "chrome/browser/command_updater.h"
[email protected]09729a552009-08-03 23:21:4130#include "chrome/browser/download/download_manager.h"
[email protected]a9e8afc2009-08-11 22:03:1731#include "chrome/browser/tab_contents/tab_contents.h"
[email protected]fbc947b2009-06-19 13:28:2432#include "chrome/browser/sessions/tab_restore_service.h"
[email protected]f4acfae82009-09-11 00:33:3833#include "chrome/common/chrome_switches.h"
[email protected]cd63ef62009-05-06 19:41:3734#include "chrome/common/pref_names.h"
35#include "chrome/common/pref_service.h"
[email protected]2c47bc12009-04-10 20:14:0036#include "chrome/browser/profile_manager.h"
37#include "chrome/common/temp_scaffolding_stubs.h"
[email protected]09729a552009-08-03 23:21:4138#include "grit/generated_resources.h"
[email protected]88d74942009-01-21 22:04:4439
40@interface AppController(PRIVATE)
41- (void)initMenuState;
[email protected]136140c2009-05-19 13:58:2542- (void)openURLs:(const std::vector<GURL>&)urls;
43- (void)openPendingURLs;
[email protected]57750f822009-04-21 21:43:0944- (void)getUrl:(NSAppleEventDescriptor*)event
45 withReply:(NSAppleEventDescriptor*)reply;
[email protected]36fe18f2009-04-29 20:26:2046- (void)openFiles:(NSAppleEventDescriptor*)event
47 withReply:(NSAppleEventDescriptor*)reply;
[email protected]449dd2f2009-05-27 13:04:0048- (void)windowLayeringDidChange:(NSNotification*)inNotification;
[email protected]73f5b362009-08-10 23:58:2149- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
[email protected]09729a552009-08-03 23:21:4150- (BOOL)shouldQuitWithInProgressDownloads;
[email protected]288bfcd32009-09-14 18:14:4651- (NSMenu*)applicationDockMenu:(NSApplication*)sender;
[email protected]88d74942009-01-21 22:04:4452@end
[email protected]1bcdb532009-01-16 17:47:5753
54@implementation AppController
55
[email protected]cd63ef62009-05-06 19:41:3756// This method is called very early in application startup (ie, before
57// the profile is loaded or any preferences have been registered). Defer any
58// user-data initialization until -applicationDidFinishLaunching:.
[email protected]88d74942009-01-21 22:04:4459- (void)awakeFromNib {
[email protected]136140c2009-05-19 13:58:2560 pendingURLs_.reset(new std::vector<GURL>());
[email protected]88d74942009-01-21 22:04:4461
[email protected]136140c2009-05-19 13:58:2562 // We need to register the handlers early to catch events fired on launch.
[email protected]57750f822009-04-21 21:43:0963 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
64 [em setEventHandler:self
65 andSelector:@selector(getUrl:withReply:)
66 forEventClass:kInternetEventClass
67 andEventID:kAEGetURL];
68 [em setEventHandler:self
69 andSelector:@selector(getUrl:withReply:)
70 forEventClass:'WWW!' // A particularly ancient AppleEvent that dates
71 andEventID:'OURL']; // back to the Spyglass days.
[email protected]36fe18f2009-04-29 20:26:2072 [em setEventHandler:self
73 andSelector:@selector(openFiles:withReply:)
74 forEventClass:kCoreEventClass
75 andEventID:kAEOpenDocuments];
[email protected]cd63ef62009-05-06 19:41:3776
[email protected]449dd2f2009-05-27 13:04:0077 // Register for various window layering changes. We use these to update
78 // various UI elements (command-key equivalents, etc) when the frontmost
79 // window changes.
80 NSNotificationCenter* notificationCenter =
81 [NSNotificationCenter defaultCenter];
[email protected]ce560f82009-06-03 09:39:4482 [notificationCenter
[email protected]449dd2f2009-05-27 13:04:0083 addObserver:self
84 selector:@selector(windowLayeringDidChange:)
85 name:NSWindowDidBecomeKeyNotification
86 object:nil];
87 [notificationCenter
88 addObserver:self
89 selector:@selector(windowLayeringDidChange:)
90 name:NSWindowDidResignKeyNotification
91 object:nil];
92 [notificationCenter
93 addObserver:self
94 selector:@selector(windowLayeringDidChange:)
95 name:NSWindowDidBecomeMainNotification
96 object:nil];
97 [notificationCenter
98 addObserver:self
99 selector:@selector(windowLayeringDidChange:)
100 name:NSWindowDidResignMainNotification
101 object:nil];
102
103 // Register for a notification that the number of tabs changes in windows
104 // so we can adjust the close tab/window command keys.
105 [notificationCenter
106 addObserver:self
107 selector:@selector(tabsChanged:)
108 name:kTabStripNumberOfTabsChanged
109 object:nil];
110
[email protected]136140c2009-05-19 13:58:25111 // Set up the command updater for when there are no windows open
112 [self initMenuState];
[email protected]f4acfae82009-09-11 00:33:38113
114 // Activate (bring to foreground) if asked to do so. On
115 // Windows this logic isn't necessary since
116 // BrowserWindow::Activate() calls ::SetForegroundWindow() which is
117 // adequate. On Mac, BrowserWindow::Activate() calls -[NSWindow
118 // makeKeyAndOrderFront:] which does not activate the application
119 // itself.
120 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
121 if (parsed_command_line.HasSwitch(switches::kActivateOnLaunch)) {
122 [NSApp activateIgnoringOtherApps:YES];
123 }
[email protected]136140c2009-05-19 13:58:25124}
125
[email protected]a9e8afc2009-08-11 22:03:17126// Checks if there are any tabs with sheets open, and if so, raises one of
127// the tabs with a sheet and returns NO.
128- (BOOL)shouldQuitWithOpenPerTabSheets {
129 BrowserList::const_iterator it = BrowserList::begin();
130 for (; it != BrowserList::end(); ++it) {
131 Browser* browser = *it;
132 BrowserWindowCocoa* window =
133 static_cast<BrowserWindowCocoa*>(browser->window());
134
135 // Could do this more nicely with a method e.g. on BWC. If I decide for
136 // keeping it this way, at least add a DCHECK().
137 BrowserWindowController* controller =
138 (BrowserWindowController*)[window->GetNativeHandle() windowController];
139
140 if (![controller shouldCloseWithOpenPerTabSheets])
141 return NO;
142 }
143
144 return YES;
145}
146
147// We do not use the normal application teardown process -- this function is
148// not called by the system but by us in |quit:|. |NSTerminateLater| is not a
149// return value that is supported by |quit:|.
150- (NSApplicationTerminateReply)applicationShouldTerminate:
151 (NSApplication *)sender {
152 // Do not quit if any per-tab sheets are open, as required by
153 // GTMWindowSheetController.
154 if (![self shouldQuitWithOpenPerTabSheets])
155 return NSTerminateCancel;
156
157 // Check for in-progress downloads, and prompt the user if they really want to
158 // quit (and thus cancel the downloads).
159 if (![self shouldQuitWithInProgressDownloads])
160 return NSTerminateCancel;
161
162 return NSTerminateNow;
163}
164
[email protected]449dd2f2009-05-27 13:04:00165// Called when the app is shutting down. Clean-up as appropriate.
166- (void)applicationWillTerminate:(NSNotification *)aNotification {
[email protected]fbc947b2009-06-19 13:28:24167 DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile]));
168 if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) {
169 // As we're shutting down, we need to nuke the TabRestoreService, which will
170 // start the shutdown of the NavigationControllers and allow for proper
171 // shutdown. If we don't do this chrome won't shutdown cleanly, and may end
172 // up crashing when some thread tries to use the IO thread (or another
173 // thread) that is no longer valid.
174 [self defaultProfile]->ResetTabRestoreService();
175 }
176
[email protected]449dd2f2009-05-27 13:04:00177 [[NSNotificationCenter defaultCenter] removeObserver:self];
178}
179
180// Helper routine to get the window controller if the key window is a tabbed
181// window, or nil if not. Examples of non-tabbed windows are "about" or
182// "preferences".
183- (TabWindowController*)keyWindowTabController {
184 NSWindowController* keyWindowController =
185 [[[NSApplication sharedApplication] keyWindow] windowController];
186 if ([keyWindowController isKindOfClass:[TabWindowController class]])
187 return (TabWindowController*)keyWindowController;
188
189 return nil;
190}
191
192// If the window has tabs, make "close window" be cmd-shift-w, otherwise leave
193// it as the normal cmd-w. Capitalization of the key equivalent affects whether
194// the shift modifer is used.
195- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs {
196 [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")];
197}
198
199// If the window has tabs, make "close tab" take over cmd-w, otherwise it
200// shouldn't have any key-equivalent because it should be disabled.
201- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs {
202 if (hasTabs) {
203 [closeTabMenuItem_ setKeyEquivalent:@"w"];
204 [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
205 } else {
206 [closeTabMenuItem_ setKeyEquivalent:@""];
207 [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
208 }
209}
210
211// See if we have a window with tabs open, and adjust the key equivalents for
212// Close Tab/Close Window accordingly
213- (void)fixCloseMenuItemKeyEquivalents {
214 TabWindowController* tabController = [self keyWindowTabController];
215 BOOL windowWithMultipleTabs =
216 (tabController && [tabController numberOfTabs] > 1);
217 [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs];
218 [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs];
219 fileMenuUpdatePending_ = NO;
220}
221
222// Fix up the "close tab/close window" command-key equivalents. We do this
223// after a delay to ensure that window layer state has been set by the time
224// we do the enabling.
225- (void)delayedFixCloseMenuItemKeyEquivalents {
226 if (!fileMenuUpdatePending_) {
227 [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
228 withObject:nil
229 afterDelay:0];
230 fileMenuUpdatePending_ = YES;
231 }
232}
233
234// Called when we get a notification about the window layering changing to
235// update the UI based on the new main window.
236- (void)windowLayeringDidChange:(NSNotification*)notify {
237 [self delayedFixCloseMenuItemKeyEquivalents];
[email protected]ce560f82009-06-03 09:39:44238
[email protected]449dd2f2009-05-27 13:04:00239 // TODO(pinkerton): If we have other things here, such as inspector panels
240 // that follow the contents of the selected webpage, we would update those
241 // here.
242}
243
244// Called when the number of tabs changes in one of the browser windows. The
245// object is the tab strip controller, but we don't currently care.
246- (void)tabsChanged:(NSNotification*)notify {
247 [self delayedFixCloseMenuItemKeyEquivalents];
248}
249
[email protected]414fde592009-05-21 16:14:43250// If the auto-update interval is not set, make it 5 hours.
251// This code is specific to Mac Chrome Dev Channel.
252// Placed here for 2 reasons:
253// 1) Same spot as other Pref stuff
254// 2) Try and be friendly by keeping this after app launch
255// TODO(jrg): remove once we go Beta.
256- (void)setUpdateCheckInterval {
257#if defined(GOOGLE_CHROME_BUILD)
258 CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
259 CFStringRef checkInterval = (CFStringRef)@"checkInterval";
260 CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
261 if (!plist) {
262 const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
263 NSNumber *value = [NSNumber numberWithFloat:fiveHoursInSeconds];
264 CFPreferencesSetAppValue(checkInterval, value, app);
265 CFPreferencesAppSynchronize(app);
266 }
267#endif
268}
269
[email protected]136140c2009-05-19 13:58:25270// This is called after profiles have been loaded and preferences registered.
271// It is safe to access the default profile here.
272- (void)applicationDidFinishLaunching:(NSNotification*)notify {
273 // Hold an extra ref to the BrowserProcess singleton so it doesn't go away
274 // when all the browser windows get closed. We'll release it on quit which
275 // will be the signal to exit.
276 DCHECK(g_browser_process);
277 g_browser_process->AddRefModule();
278
[email protected]e5575022009-08-22 00:31:22279 bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile]));
[email protected]1c84c82c2009-07-27 15:37:25280 historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile]));
[email protected]cd63ef62009-05-06 19:41:37281
[email protected]414fde592009-05-21 16:14:43282 [self setUpdateCheckInterval];
[email protected]2bcec612009-05-14 17:50:53283
284 // Build up the encoding menu, the order of the items differs based on the
285 // current locale (see http://crbug.com/7647 for details).
286 // We need a valid g_browser_process to get the profile which is why we can't
287 // call this from awakeFromNib.
288 EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile]);
289
[email protected]136140c2009-05-19 13:58:25290 // Now that we're initialized we can open any URLs we've been holding onto.
291 [self openPendingURLs];
[email protected]7c321082009-02-09 15:35:47292}
293
[email protected]73f5b362009-08-10 23:58:21294// Helper function for populating and displaying the in progress downloads at
295// exit alert panel.
296- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
297 NSString* descriptionText = nil;
298 NSString* waitTitle = nil;
299 NSString* exitTitle = nil;
300
301 // Set the dialog text based on whether or not there are multiple downloads.
302 if (downloadCount == 1) {
303 // Dialog text.
304 descriptionText =
305 base::SysWideToNSString(
306 l10n_util::GetString(IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_TITLE));
307
308 // Cancel download and exit button text.
309 exitTitle =
310 base::SysWideToNSString(
311 l10n_util::GetString(
312 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL));
313
314 // Wait for download button text.
315 waitTitle =
316 base::SysWideToNSString(
317 l10n_util::GetString(
318 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
319 } else {
320 // Dialog text.
321 descriptionText =
322 base::SysWideToNSString(
323 l10n_util::GetStringF(IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_TITLE,
324 downloadCount));
325
326 // Cancel downloads and exit button text.
327 exitTitle =
328 base::SysWideToNSString(
329 l10n_util::GetString(
330 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL));
331
332 // Wait for downloads button text.
333 waitTitle =
334 base::SysWideToNSString(
335 l10n_util::GetString(
336 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
337 }
338
339 // 'waitButton' is the default choice.
340 int choice = NSRunAlertPanel(nil, descriptionText, waitTitle, exitTitle, nil);
341 return choice == NSAlertDefaultReturn ? YES : NO;
342}
343
344// Check all profiles for in progress downloads, and if we find any, prompt the
[email protected]09729a552009-08-03 23:21:41345// user to see if we should continue to exit (and thus cancel the downloads), or
346// if we should wait.
347- (BOOL)shouldQuitWithInProgressDownloads {
[email protected]73f5b362009-08-10 23:58:21348 ProfileManager* profile_manager = g_browser_process->profile_manager();
349 if (!profile_manager)
350 return YES;
[email protected]09729a552009-08-03 23:21:41351
[email protected]73f5b362009-08-10 23:58:21352 ProfileManager::const_iterator it = profile_manager->begin();
353 for (; it != profile_manager->end(); ++it) {
354 Profile* profile = *it;
[email protected]09729a552009-08-03 23:21:41355 DownloadManager* download_manager = profile->GetDownloadManager();
[email protected]73f5b362009-08-10 23:58:21356 if (download_manager && download_manager->in_progress_count() > 0) {
357 int downloadCount = download_manager->in_progress_count();
358 if ([self userWillWaitForInProgressDownloads:downloadCount]) {
359 // Create a new browser window (if necessary) and navigate to the
360 // downloads page if the user chooses to wait.
361 Browser* browser = BrowserList::FindBrowserWithProfile(profile);
362 if (!browser) {
363 browser = Browser::Create(profile);
364 browser->window()->Show();
365 }
366 DCHECK(browser);
367 browser->ShowDownloadsTab();
368 return NO;
369 }
[email protected]09729a552009-08-03 23:21:41370
[email protected]73f5b362009-08-10 23:58:21371 // User wants to exit.
372 return YES;
[email protected]09729a552009-08-03 23:21:41373 }
[email protected]09729a552009-08-03 23:21:41374 }
375
[email protected]73f5b362009-08-10 23:58:21376 // No profiles or active downloads found, okay to exit.
[email protected]09729a552009-08-03 23:21:41377 return YES;
378}
379
[email protected]983f0fc82009-01-27 16:28:44380// We can't use the standard terminate: method because it will abruptly exit
[email protected]1bcdb532009-01-16 17:47:57381// the app and leave things on the stack in an unfinalized state. We need to
382// post a quit message to our run loop so the stack can gracefully unwind.
[email protected]f0a51fb52009-03-05 12:46:38383- (IBAction)quit:(id)sender {
[email protected]a9e8afc2009-08-11 22:03:17384 if ([self applicationShouldTerminate:NSApp] == NSTerminateCancel)
385 return;
386
[email protected]1bcdb532009-01-16 17:47:57387 // TODO(pinkerton):
388 // since we have to roll it ourselves, ask the delegate (ourselves, really)
389 // if we should terminate. For example, we might not want to if the user
390 // has ongoing downloads or multiple windows/tabs open. However, this would
[email protected]f0a51fb52009-03-05 12:46:38391 // require posting UI and may require spinning up another run loop to
[email protected]1bcdb532009-01-16 17:47:57392 // handle it. If it says to continue, post the quit message, otherwise
393 // go back to normal.
[email protected]8a53ee042009-01-21 16:41:33394
[email protected]57750f822009-04-21 21:43:09395 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
396 [em removeEventHandlerForEventClass:kInternetEventClass
397 andEventID:kAEGetURL];
398 [em removeEventHandlerForEventClass:'WWW!'
399 andEventID:'OURL'];
[email protected]36fe18f2009-04-29 20:26:20400 [em removeEventHandlerForEventClass:kCoreEventClass
401 andEventID:kAEOpenDocuments];
[email protected]57750f822009-04-21 21:43:09402
[email protected]41741a962009-02-18 21:51:39403 // TODO(pinkerton): Not sure where this should live, including it here
404 // causes all sorts of asserts from the open renderers. On Windows, it
405 // lives in Browser::OnWindowClosing, but that's not appropriate on Mac
406 // since we don't shut down when we reach zero windows.
407 // browser_shutdown::OnShutdownStarting(browser_shutdown::WINDOW_CLOSE);
408
[email protected]8a53ee042009-01-21 16:41:33409 // Close all the windows.
[email protected]7c321082009-02-09 15:35:47410 BrowserList::CloseAllBrowsers(true);
[email protected]f0a51fb52009-03-05 12:46:38411
[email protected]7c321082009-02-09 15:35:47412 // Release the reference to the browser process. Once all the browsers get
413 // dealloc'd, it will stop the RunLoop and fall back into main().
414 g_browser_process->ReleaseModule();
[email protected]1bcdb532009-01-16 17:47:57415}
416
[email protected]fbc947b2009-06-19 13:28:24417// Called to determine if we should enable the "restore tab" menu item.
418// Checks with the TabRestoreService to see if there's anything there to
419// restore and returns YES if so.
420- (BOOL)canRestoreTab {
421 TabRestoreService* service = [self defaultProfile]->GetTabRestoreService();
422 return service && !service->entries().empty();
423}
424
[email protected]88d74942009-01-21 22:04:44425// Called to validate menu items when there are no key windows. All the
426// items we care about have been set with the |commandDispatch:| action and
427// a target of FirstResponder in IB. If it's not one of those, let it
428// continue up the responder chain to be handled elsewhere. We pull out the
429// tag as the cross-platform constant to differentiate and dispatch the
430// various commands.
431- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
432 SEL action = [item action];
433 BOOL enable = NO;
434 if (action == @selector(commandDispatch:)) {
435 NSInteger tag = [item tag];
[email protected]fbc947b2009-06-19 13:28:24436 if (menuState_->SupportsCommand(tag)) {
437 switch (tag) {
438 case IDC_RESTORE_TAB:
439 enable = [self canRestoreTab];
440 break;
441 default:
442 enable = menuState_->IsCommandEnabled(tag) ? YES : NO;
443 }
444 }
[email protected]88d74942009-01-21 22:04:44445 } else if (action == @selector(quit:)) {
446 enable = YES;
[email protected]3111f08b2009-04-30 16:01:52447 } else if (action == @selector(showPreferences:)) {
448 enable = YES;
[email protected]bde3dda2009-05-20 22:13:07449 } else if (action == @selector(orderFrontStandardAboutPanel:)) {
450 enable = YES;
[email protected]88d74942009-01-21 22:04:44451 }
452 return enable;
453}
454
455// Called when the user picks a menu item when there are no key windows. Calls
456// through to the browser object to execute the command. This assumes that the
457// command is supported and doesn't check, otherwise it would have been disabled
458// in the UI in validateUserInterfaceItem:.
459- (void)commandDispatch:(id)sender {
[email protected]93e181762009-05-29 14:30:38460 Profile* defaultProfile = [self defaultProfile];
[email protected]f0a51fb52009-03-05 12:46:38461
[email protected]88d74942009-01-21 22:04:44462 NSInteger tag = [sender tag];
463 switch (tag) {
[email protected]fbc947b2009-06-19 13:28:24464 case IDC_NEW_TAB:
[email protected]88d74942009-01-21 22:04:44465 case IDC_NEW_WINDOW:
[email protected]93e181762009-05-29 14:30:38466 Browser::OpenEmptyWindow(defaultProfile);
[email protected]88d74942009-01-21 22:04:44467 break;
[email protected]863ff662009-01-26 20:18:18468 case IDC_NEW_INCOGNITO_WINDOW:
[email protected]93e181762009-05-29 14:30:38469 Browser::OpenURLOffTheRecord(defaultProfile, GURL());
[email protected]863ff662009-01-26 20:18:18470 break;
[email protected]fbc947b2009-06-19 13:28:24471 case IDC_RESTORE_TAB:
472 Browser::OpenWindowWithRestoredTabs(defaultProfile);
473 break;
[email protected]e19516d2009-04-28 17:15:19474 case IDC_OPEN_FILE:
[email protected]93e181762009-05-29 14:30:38475 Browser::OpenEmptyWindow(defaultProfile);
[email protected]e19516d2009-04-28 17:15:19476 BrowserList::GetLastActive()->
477 ExecuteCommandWithDisposition(IDC_OPEN_FILE, CURRENT_TAB);
478 break;
[email protected]93e181762009-05-29 14:30:38479 case IDC_CLEAR_BROWSING_DATA:
480 // There may not be a browser open, so use the default profile.
481 scoped_nsobject<ClearBrowsingDataController> controller(
482 [[ClearBrowsingDataController alloc]
483 initWithProfile:defaultProfile]);
484 [controller runModalDialog];
485 break;
[email protected]88d74942009-01-21 22:04:44486 };
487}
488
[email protected]ff81e0a2009-04-13 14:58:54489// NSApplication delegate method called when someone clicks on the
490// dock icon and there are no open windows. To match standard mac
491// behavior, we should open a new window.
492- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
493 hasVisibleWindows:(BOOL)flag {
494 // Don't do anything if there are visible windows. This will cause
495 // AppKit to unminimize the most recently minimized window.
496 if (flag)
497 return YES;
498
499 // Otherwise open a new window.
500 Browser::OpenEmptyWindow([self defaultProfile]);
501
502 // We've handled the reopen event, so return NO to tell AppKit not
503 // to do anything.
504 return NO;
505}
506
[email protected]88d74942009-01-21 22:04:44507- (void)initMenuState {
[email protected]3111f08b2009-04-30 16:01:52508 menuState_.reset(new CommandUpdater(NULL));
[email protected]fbc947b2009-06-19 13:28:24509 menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
[email protected]88d74942009-01-21 22:04:44510 menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
[email protected]863ff662009-01-26 20:18:18511 menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
[email protected]e19516d2009-04-28 17:15:19512 menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
[email protected]93e181762009-05-29 14:30:38513 menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
[email protected]fbc947b2009-06-19 13:28:24514 menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
[email protected]88d74942009-01-21 22:04:44515 // TODO(pinkerton): ...more to come...
516}
[email protected]1bcdb532009-01-16 17:47:57517
[email protected]3f34599d2009-03-25 22:11:43518- (Profile*)defaultProfile {
[email protected]3f34599d2009-03-25 22:11:43519 // TODO(jrg): Find a better way to get the "default" profile.
520 if (g_browser_process->profile_manager())
[email protected]57750f822009-04-21 21:43:09521 return* g_browser_process->profile_manager()->begin();
[email protected]3f34599d2009-03-25 22:11:43522
[email protected]f6314002009-04-23 01:18:13523 return NULL;
[email protected]3f34599d2009-03-25 22:11:43524}
525
[email protected]57750f822009-04-21 21:43:09526// Various methods to open URLs that we get in a native fashion. We use
527// BrowserInit here because on the other platforms, URLs to open come through
528// the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the
529// openings through that for uniform handling.
530
[email protected]136140c2009-05-19 13:58:25531- (void)openURLs:(const std::vector<GURL>&)urls {
532 if (pendingURLs_.get()) {
533 // too early to open; save for later
534 pendingURLs_->insert(pendingURLs_->end(), urls.begin(), urls.end());
535 return;
536 }
[email protected]57750f822009-04-21 21:43:09537
[email protected]0e863512009-05-28 19:45:07538 Browser* browser = BrowserList::GetLastActive();
539 // if no browser window exists then create one with no tabs to be filled in
540 if (!browser) {
541 browser = Browser::Create([self defaultProfile]);
[email protected]ce560f82009-06-03 09:39:44542 browser->window()->Show();
[email protected]0e863512009-05-28 19:45:07543 }
544
[email protected]57750f822009-04-21 21:43:09545 CommandLine dummy((std::wstring()));
546 BrowserInit::LaunchWithProfile launch(std::wstring(), dummy);
[email protected]0e863512009-05-28 19:45:07547 launch.OpenURLsInBrowser(browser, false, urls);
[email protected]57750f822009-04-21 21:43:09548}
549
[email protected]136140c2009-05-19 13:58:25550- (void)openPendingURLs {
551 // Since the existence of pendingURLs_ is a flag that it's too early to
552 // open URLs, we need to reset pendingURLs_.
553 std::vector<GURL> urls;
554 swap(urls, *pendingURLs_);
555 pendingURLs_.reset();
556
557 if (urls.size())
558 [self openURLs:urls];
559}
[email protected]57750f822009-04-21 21:43:09560
561- (void)getUrl:(NSAppleEventDescriptor*)event
562 withReply:(NSAppleEventDescriptor*)reply {
563 NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
564 stringValue];
565
566 GURL gurl(base::SysNSStringToUTF8(urlStr));
567 std::vector<GURL> gurlVector;
568 gurlVector.push_back(gurl);
569
[email protected]136140c2009-05-19 13:58:25570 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09571}
572
[email protected]36fe18f2009-04-29 20:26:20573- (void)openFiles:(NSAppleEventDescriptor*)event
574 withReply:(NSAppleEventDescriptor*)reply {
575 // Ordinarily we'd use the NSApplication delegate method
576 // -application:openFiles:, but Cocoa tries to be smart and it sends files
577 // specified on the command line into that delegate method. That's too smart
578 // for us (our setup isn't done by the time Cocoa triggers the delegate method
579 // and we crash). Since all we want are files dropped on the app icon, and we
580 // have cross-platform code to handle the command-line files anyway, an Apple
581 // Event handler fits the bill just right.
582 NSAppleEventDescriptor* fileList =
583 [event paramDescriptorForKeyword:keyDirectObject];
584 if (!fileList)
585 return;
[email protected]57750f822009-04-21 21:43:09586 std::vector<GURL> gurlVector;
587
[email protected]36fe18f2009-04-29 20:26:20588 for (NSInteger i = 1; i <= [fileList numberOfItems]; ++i) {
589 NSAppleEventDescriptor* fileAliasDesc = [fileList descriptorAtIndex:i];
590 if (!fileAliasDesc)
591 continue;
592 NSAppleEventDescriptor* fileURLDesc =
593 [fileAliasDesc coerceToDescriptorType:typeFileURL];
594 if (!fileURLDesc)
595 continue;
596 NSData* fileURLData = [fileURLDesc data];
597 if (!fileURLData)
598 continue;
599 GURL gurl(std::string((char*)[fileURLData bytes], [fileURLData length]));
[email protected]57750f822009-04-21 21:43:09600 gurlVector.push_back(gurl);
601 }
602
[email protected]136140c2009-05-19 13:58:25603 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09604}
[email protected]3f34599d2009-03-25 22:11:43605
[email protected]767543d2009-04-30 19:23:58606// Called when the preferences window is closed. We use this to release the
607// window controller.
608- (void)prefsWindowClosed:(NSNotification*)notify {
[email protected]bde3dda2009-05-20 22:13:07609 [[NSNotificationCenter defaultCenter]
610 removeObserver:self
611 name:kUserDoneEditingPrefsNotification
612 object:prefsController_.get()];
[email protected]767543d2009-04-30 19:23:58613 prefsController_.reset(NULL);
614}
615
[email protected]3111f08b2009-04-30 16:01:52616// Show the preferences window, or bring it to the front if it's already
617// visible.
618- (IBAction)showPreferences:(id)sender {
[email protected]42404382009-04-30 17:59:24619 if (!prefsController_.get()) {
[email protected]42404382009-04-30 17:59:24620 prefsController_.reset([[PreferencesWindowController alloc]
[email protected]cd63ef62009-05-06 19:41:37621 initWithProfile:[self defaultProfile]]);
[email protected]767543d2009-04-30 19:23:58622 // Watch for a notification of when it goes away so that we can destroy
623 // the controller.
624 [[NSNotificationCenter defaultCenter]
625 addObserver:self
626 selector:@selector(prefsWindowClosed:)
627 name:kUserDoneEditingPrefsNotification
628 object:prefsController_.get()];
[email protected]42404382009-04-30 17:59:24629 }
630 [prefsController_ showPreferences:sender];
[email protected]3111f08b2009-04-30 16:01:52631}
632
[email protected]bde3dda2009-05-20 22:13:07633// Called when the about window is closed. We use this to release the
634// window controller.
635- (void)aboutWindowClosed:(NSNotification*)notify {
636 [[NSNotificationCenter defaultCenter]
637 removeObserver:self
638 name:kUserClosedAboutNotification
639 object:aboutController_.get()];
640 aboutController_.reset(NULL);
641}
642
643- (IBAction)orderFrontStandardAboutPanel:(id)sender {
[email protected]bde3dda2009-05-20 22:13:07644 // Otherwise bring up our special dialog (e.g. with an auto-update button).
645 if (!aboutController_) {
646 aboutController_.reset([[AboutWindowController alloc]
647 initWithWindowNibName:@"About"]);
648 if (!aboutController_) {
649 // If we get here something is wacky. I managed to do it when
650 // testing by explicitly forcing an auto-update to an older
651 // version then trying to open the about box again (missing
652 // nib). This shouldn't be possible in general but let's try
653 // hard to not do nothing.
654 [NSApp orderFrontStandardAboutPanel:sender];
655 return;
656 }
657 // Watch for a notification of when it goes away so that we can destroy
658 // the controller.
659 [[NSNotificationCenter defaultCenter]
660 addObserver:self
661 selector:@selector(aboutWindowClosed:)
662 name:kUserClosedAboutNotification
663 object:aboutController_.get()];
664 }
665 if (![[aboutController_ window] isVisible])
666 [[aboutController_ window] center];
667 [aboutController_ showWindow:self];
[email protected]bde3dda2009-05-20 22:13:07668}
669
[email protected]288bfcd32009-09-14 18:14:46670- (NSMenu*)applicationDockMenu:(id)sender {
671 NSMenu* result = [[[NSMenu alloc] initWithTitle: @""] autorelease];
672 NSString* titleStr;
673 id item;
674
675 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
676 item = [[[NSMenuItem alloc] initWithTitle:titleStr
677 action:@selector(commandDispatch:)
678 keyEquivalent:@""] autorelease];
679 [item setTarget:self];
680 [item setTag:IDC_NEW_WINDOW];
681 [result addItem:item];
682
683 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
684 item = [[[NSMenuItem alloc] initWithTitle:titleStr
685 action:@selector(commandDispatch:)
686 keyEquivalent:@""] autorelease];
687 [item setTarget:self];
688 [item setTag:IDC_NEW_INCOGNITO_WINDOW];
689 [result addItem:item];
690
691 return result;
692}
693
[email protected]1bcdb532009-01-16 17:47:57694@end