blob: faf678960584b259f746d3f22288963b2e27f051 [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]3b6aa8b62009-09-15 21:36:1119#import "chrome/browser/chrome_application_mac.h"
[email protected]bde3dda2009-05-20 22:13:0720#import "chrome/browser/cocoa/about_window_controller.h"
[email protected]3f34599d2009-03-25 22:11:4321#import "chrome/browser/cocoa/bookmark_menu_bridge.h"
[email protected]a9e8afc2009-08-11 22:03:1722#import "chrome/browser/cocoa/browser_window_cocoa.h"
23#import "chrome/browser/cocoa/browser_window_controller.h"
[email protected]1c84c82c2009-07-27 15:37:2524#import "chrome/browser/cocoa/history_menu_bridge.h"
[email protected]93e181762009-05-29 14:30:3825#import "chrome/browser/cocoa/clear_browsing_data_controller.h"
[email protected]2bcec612009-05-14 17:50:5326#import "chrome/browser/cocoa/encoding_menu_controller_delegate_mac.h"
[email protected]42404382009-04-30 17:59:2427#import "chrome/browser/cocoa/preferences_window_controller.h"
[email protected]449dd2f2009-05-27 13:04:0028#import "chrome/browser/cocoa/tab_strip_controller.h"
29#import "chrome/browser/cocoa/tab_window_controller.h"
[email protected]2c47bc12009-04-10 20:14:0030#include "chrome/browser/command_updater.h"
[email protected]09729a552009-08-03 23:21:4131#include "chrome/browser/download/download_manager.h"
[email protected]60ad3e22009-09-18 21:07:1932#include "chrome/browser/options_window.h"
[email protected]fbc947b2009-06-19 13:28:2433#include "chrome/browser/sessions/tab_restore_service.h"
[email protected]60ad3e22009-09-18 21:07:1934#include "chrome/browser/tab_contents/tab_contents.h"
[email protected]f4acfae82009-09-11 00:33:3835#include "chrome/common/chrome_switches.h"
[email protected]cd63ef62009-05-06 19:41:3736#include "chrome/common/pref_names.h"
37#include "chrome/common/pref_service.h"
[email protected]2c47bc12009-04-10 20:14:0038#include "chrome/browser/profile_manager.h"
39#include "chrome/common/temp_scaffolding_stubs.h"
[email protected]9dcb59662009-09-23 01:15:5040#include "grit/chromium_strings.h"
[email protected]09729a552009-08-03 23:21:4141#include "grit/generated_resources.h"
[email protected]88d74942009-01-21 22:04:4442
43@interface AppController(PRIVATE)
44- (void)initMenuState;
[email protected]136140c2009-05-19 13:58:2545- (void)openURLs:(const std::vector<GURL>&)urls;
46- (void)openPendingURLs;
[email protected]57750f822009-04-21 21:43:0947- (void)getUrl:(NSAppleEventDescriptor*)event
48 withReply:(NSAppleEventDescriptor*)reply;
[email protected]36fe18f2009-04-29 20:26:2049- (void)openFiles:(NSAppleEventDescriptor*)event
50 withReply:(NSAppleEventDescriptor*)reply;
[email protected]449dd2f2009-05-27 13:04:0051- (void)windowLayeringDidChange:(NSNotification*)inNotification;
[email protected]73f5b362009-08-10 23:58:2152- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
[email protected]09729a552009-08-03 23:21:4153- (BOOL)shouldQuitWithInProgressDownloads;
[email protected]88d74942009-01-21 22:04:4454@end
[email protected]1bcdb532009-01-16 17:47:5755
[email protected]216e9042009-10-29 17:05:2356// True while AppController is calling Browser::OpenEmptyWindow(). We need a
57// global flag here, analogue to BrowserInit::InProcessStartup() because
58// otherwise the SessionService will try to restore sessions when we make a new
59// window while there are no other active windows.
60static bool g_is_opening_new_window = false;
61
[email protected]1bcdb532009-01-16 17:47:5762@implementation AppController
63
[email protected]cd63ef62009-05-06 19:41:3764// This method is called very early in application startup (ie, before
65// the profile is loaded or any preferences have been registered). Defer any
66// user-data initialization until -applicationDidFinishLaunching:.
[email protected]88d74942009-01-21 22:04:4467- (void)awakeFromNib {
[email protected]136140c2009-05-19 13:58:2568 pendingURLs_.reset(new std::vector<GURL>());
[email protected]88d74942009-01-21 22:04:4469
[email protected]136140c2009-05-19 13:58:2570 // We need to register the handlers early to catch events fired on launch.
[email protected]57750f822009-04-21 21:43:0971 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
72 [em setEventHandler:self
73 andSelector:@selector(getUrl:withReply:)
74 forEventClass:kInternetEventClass
75 andEventID:kAEGetURL];
76 [em setEventHandler:self
77 andSelector:@selector(getUrl:withReply:)
78 forEventClass:'WWW!' // A particularly ancient AppleEvent that dates
79 andEventID:'OURL']; // back to the Spyglass days.
[email protected]36fe18f2009-04-29 20:26:2080 [em setEventHandler:self
81 andSelector:@selector(openFiles:withReply:)
82 forEventClass:kCoreEventClass
83 andEventID:kAEOpenDocuments];
[email protected]cd63ef62009-05-06 19:41:3784
[email protected]449dd2f2009-05-27 13:04:0085 // Register for various window layering changes. We use these to update
86 // various UI elements (command-key equivalents, etc) when the frontmost
87 // window changes.
88 NSNotificationCenter* notificationCenter =
89 [NSNotificationCenter defaultCenter];
[email protected]ce560f82009-06-03 09:39:4490 [notificationCenter
[email protected]449dd2f2009-05-27 13:04:0091 addObserver:self
92 selector:@selector(windowLayeringDidChange:)
93 name:NSWindowDidBecomeKeyNotification
94 object:nil];
95 [notificationCenter
96 addObserver:self
97 selector:@selector(windowLayeringDidChange:)
98 name:NSWindowDidResignKeyNotification
99 object:nil];
100 [notificationCenter
101 addObserver:self
102 selector:@selector(windowLayeringDidChange:)
103 name:NSWindowDidBecomeMainNotification
104 object:nil];
105 [notificationCenter
106 addObserver:self
107 selector:@selector(windowLayeringDidChange:)
108 name:NSWindowDidResignMainNotification
109 object:nil];
110
111 // Register for a notification that the number of tabs changes in windows
112 // so we can adjust the close tab/window command keys.
113 [notificationCenter
114 addObserver:self
115 selector:@selector(tabsChanged:)
116 name:kTabStripNumberOfTabsChanged
117 object:nil];
118
[email protected]136140c2009-05-19 13:58:25119 // Set up the command updater for when there are no windows open
120 [self initMenuState];
[email protected]f4acfae82009-09-11 00:33:38121
122 // Activate (bring to foreground) if asked to do so. On
123 // Windows this logic isn't necessary since
124 // BrowserWindow::Activate() calls ::SetForegroundWindow() which is
125 // adequate. On Mac, BrowserWindow::Activate() calls -[NSWindow
126 // makeKeyAndOrderFront:] which does not activate the application
127 // itself.
128 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
129 if (parsed_command_line.HasSwitch(switches::kActivateOnLaunch)) {
130 [NSApp activateIgnoringOtherApps:YES];
131 }
[email protected]136140c2009-05-19 13:58:25132}
133
[email protected]a9e8afc2009-08-11 22:03:17134- (NSApplicationTerminateReply)applicationShouldTerminate:
135 (NSApplication *)sender {
[email protected]a9e8afc2009-08-11 22:03:17136 // Check for in-progress downloads, and prompt the user if they really want to
137 // quit (and thus cancel the downloads).
138 if (![self shouldQuitWithInProgressDownloads])
139 return NSTerminateCancel;
140
141 return NSTerminateNow;
142}
143
[email protected]449dd2f2009-05-27 13:04:00144// Called when the app is shutting down. Clean-up as appropriate.
145- (void)applicationWillTerminate:(NSNotification *)aNotification {
[email protected]3b6aa8b62009-09-15 21:36:11146 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
147 [em removeEventHandlerForEventClass:kInternetEventClass
148 andEventID:kAEGetURL];
149 [em removeEventHandlerForEventClass:'WWW!'
150 andEventID:'OURL'];
151 [em removeEventHandlerForEventClass:kCoreEventClass
152 andEventID:kAEOpenDocuments];
153
154 // Close all the windows.
155 BrowserList::CloseAllBrowsers(true);
156
157 // On Windows, this is done in Browser::OnWindowClosing, but that's not
158 // appropriate on Mac since we don't shut down when we reach zero windows.
159 browser_shutdown::OnShutdownStarting(browser_shutdown::BROWSER_EXIT);
160
161 // Release the reference to the browser process. Once all the browsers get
162 // dealloc'd, it will stop the RunLoop and fall back into main().
163 g_browser_process->ReleaseModule();
[email protected]fbc947b2009-06-19 13:28:24164
[email protected]449dd2f2009-05-27 13:04:00165 [[NSNotificationCenter defaultCenter] removeObserver:self];
166}
167
[email protected]3b6aa8b62009-09-15 21:36:11168- (void)didEndMainMessageLoop {
169 DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile]));
170 if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) {
171 // As we're shutting down, we need to nuke the TabRestoreService, which
172 // will start the shutdown of the NavigationControllers and allow for
173 // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
174 // and may end up crashing when some thread tries to use the IO thread (or
175 // another thread) that is no longer valid.
176 [self defaultProfile]->ResetTabRestoreService();
177 }
178}
179
[email protected]449dd2f2009-05-27 13:04:00180// 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 =
[email protected]5dbabd02009-09-17 20:44:13185 [[NSApp keyWindow] windowController];
[email protected]449dd2f2009-05-27 13:04:00186 if ([keyWindowController isKindOfClass:[TabWindowController class]])
187 return (TabWindowController*)keyWindowController;
188
189 return nil;
190}
191
[email protected]74c0c642009-09-22 21:25:50192// Helper routine to get the window controller if the main window is a tabbed
193// window, or nil if not. Examples of non-tabbed windows are "about" or
194// "preferences".
195- (TabWindowController*)mainWindowTabController {
196 NSWindowController* mainWindowController =
197 [[NSApp mainWindow] windowController];
198 if ([mainWindowController isKindOfClass:[TabWindowController class]])
199 return (TabWindowController*)mainWindowController;
200
201 return nil;
202}
203
[email protected]449dd2f2009-05-27 13:04:00204// If the window has tabs, make "close window" be cmd-shift-w, otherwise leave
205// it as the normal cmd-w. Capitalization of the key equivalent affects whether
206// the shift modifer is used.
207- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs {
208 [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")];
[email protected]74c0c642009-09-22 21:25:50209 [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
[email protected]449dd2f2009-05-27 13:04:00210}
211
212// If the window has tabs, make "close tab" take over cmd-w, otherwise it
213// shouldn't have any key-equivalent because it should be disabled.
214- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs {
215 if (hasTabs) {
216 [closeTabMenuItem_ setKeyEquivalent:@"w"];
217 [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
218 } else {
219 [closeTabMenuItem_ setKeyEquivalent:@""];
220 [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
221 }
222}
223
[email protected]74c0c642009-09-22 21:25:50224// Explicitly remove any command-key equivalents from the close tab/window
225// menus so that nothing can go haywire if we get a user action during pending
226// updates.
227- (void)clearCloseMenuItemKeyEquivalents {
228 [closeTabMenuItem_ setKeyEquivalent:@""];
229 [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
230 [closeWindowMenuItem_ setKeyEquivalent:@""];
231 [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
232}
233
[email protected]449dd2f2009-05-27 13:04:00234// See if we have a window with tabs open, and adjust the key equivalents for
235// Close Tab/Close Window accordingly
236- (void)fixCloseMenuItemKeyEquivalents {
237 TabWindowController* tabController = [self keyWindowTabController];
[email protected]74c0c642009-09-22 21:25:50238 if (!tabController && ![NSApp keyWindow]) {
239 // There might be a small amount of time where there is no key window,
240 // so just use our main browser window if there is one.
241 tabController = [self mainWindowTabController];
242 }
[email protected]449dd2f2009-05-27 13:04:00243 BOOL windowWithMultipleTabs =
244 (tabController && [tabController numberOfTabs] > 1);
245 [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs];
246 [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs];
247 fileMenuUpdatePending_ = NO;
248}
249
250// Fix up the "close tab/close window" command-key equivalents. We do this
251// after a delay to ensure that window layer state has been set by the time
252// we do the enabling.
253- (void)delayedFixCloseMenuItemKeyEquivalents {
254 if (!fileMenuUpdatePending_) {
[email protected]74c0c642009-09-22 21:25:50255 // The OS prefers keypresses to timers, so it's possible that a cmd-w
256 // can sneak in before this timer fires. In order to prevent that from
257 // having any bad consequences, just clear the keys combos altogether. They
258 // will be reset when the timer eventually fires.
259 [self clearCloseMenuItemKeyEquivalents];
[email protected]449dd2f2009-05-27 13:04:00260 [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
261 withObject:nil
262 afterDelay:0];
263 fileMenuUpdatePending_ = YES;
264 }
265}
266
267// Called when we get a notification about the window layering changing to
268// update the UI based on the new main window.
269- (void)windowLayeringDidChange:(NSNotification*)notify {
270 [self delayedFixCloseMenuItemKeyEquivalents];
[email protected]ce560f82009-06-03 09:39:44271
[email protected]449dd2f2009-05-27 13:04:00272 // TODO(pinkerton): If we have other things here, such as inspector panels
273 // that follow the contents of the selected webpage, we would update those
274 // here.
275}
276
277// Called when the number of tabs changes in one of the browser windows. The
278// object is the tab strip controller, but we don't currently care.
279- (void)tabsChanged:(NSNotification*)notify {
[email protected]e72746b12009-10-19 14:11:51280 // We don't need to do this on a delay, as in the method above, because the
281 // window layering isn't changing. As a result, there's no chance that a
282 // different window will sneak in as the key window and cause the problems
283 // we hacked around above by clearing the key equivalents.
284 [self fixCloseMenuItemKeyEquivalents];
[email protected]449dd2f2009-05-27 13:04:00285}
286
[email protected]414fde592009-05-21 16:14:43287// If the auto-update interval is not set, make it 5 hours.
288// This code is specific to Mac Chrome Dev Channel.
289// Placed here for 2 reasons:
290// 1) Same spot as other Pref stuff
291// 2) Try and be friendly by keeping this after app launch
292// TODO(jrg): remove once we go Beta.
293- (void)setUpdateCheckInterval {
294#if defined(GOOGLE_CHROME_BUILD)
295 CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
296 CFStringRef checkInterval = (CFStringRef)@"checkInterval";
297 CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
298 if (!plist) {
299 const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
300 NSNumber *value = [NSNumber numberWithFloat:fiveHoursInSeconds];
301 CFPreferencesSetAppValue(checkInterval, value, app);
302 CFPreferencesAppSynchronize(app);
303 }
304#endif
305}
306
[email protected]136140c2009-05-19 13:58:25307// This is called after profiles have been loaded and preferences registered.
308// It is safe to access the default profile here.
309- (void)applicationDidFinishLaunching:(NSNotification*)notify {
310 // Hold an extra ref to the BrowserProcess singleton so it doesn't go away
311 // when all the browser windows get closed. We'll release it on quit which
312 // will be the signal to exit.
313 DCHECK(g_browser_process);
314 g_browser_process->AddRefModule();
315
[email protected]e5575022009-08-22 00:31:22316 bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile]));
[email protected]1c84c82c2009-07-27 15:37:25317 historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile]));
[email protected]cd63ef62009-05-06 19:41:37318
[email protected]414fde592009-05-21 16:14:43319 [self setUpdateCheckInterval];
[email protected]2bcec612009-05-14 17:50:53320
321 // Build up the encoding menu, the order of the items differs based on the
322 // current locale (see http://crbug.com/7647 for details).
323 // We need a valid g_browser_process to get the profile which is why we can't
324 // call this from awakeFromNib.
[email protected]fc349322009-09-23 22:28:23325 NSMenu* view_menu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
326 NSMenuItem* encoding_menu_item = [view_menu itemWithTag:IDC_ENCODING_MENU];
327 NSMenu *encoding_menu = [encoding_menu_item submenu];
328 EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile],
329 encoding_menu);
[email protected]2bcec612009-05-14 17:50:53330
[email protected]136140c2009-05-19 13:58:25331 // Now that we're initialized we can open any URLs we've been holding onto.
332 [self openPendingURLs];
[email protected]7c321082009-02-09 15:35:47333}
334
[email protected]73f5b362009-08-10 23:58:21335// Helper function for populating and displaying the in progress downloads at
336// exit alert panel.
337- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
[email protected]e8b5f7882009-09-14 20:59:13338 NSString* warningText = nil;
339 NSString* explanationText = nil;
[email protected]73f5b362009-08-10 23:58:21340 NSString* waitTitle = nil;
341 NSString* exitTitle = nil;
342
[email protected]9dcb59662009-09-23 01:15:50343 std::wstring product_name = l10n_util::GetString(IDS_PRODUCT_NAME);
344
[email protected]73f5b362009-08-10 23:58:21345 // Set the dialog text based on whether or not there are multiple downloads.
346 if (downloadCount == 1) {
[email protected]e8b5f7882009-09-14 20:59:13347 // Dialog text: warning and explanation.
348 warningText =
[email protected]9dcb59662009-09-23 01:15:50349 base::SysWideToNSString(l10n_util::GetStringF(
350 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_WARNING, product_name));
[email protected]e8b5f7882009-09-14 20:59:13351 explanationText =
[email protected]9dcb59662009-09-23 01:15:50352 base::SysWideToNSString(l10n_util::GetStringF(
353 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION, product_name));
[email protected]73f5b362009-08-10 23:58:21354
355 // Cancel download and exit button text.
356 exitTitle =
[email protected]e8b5f7882009-09-14 20:59:13357 base::SysWideToNSString(l10n_util::GetString(
358 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21359
360 // Wait for download button text.
361 waitTitle =
[email protected]e8b5f7882009-09-14 20:59:13362 base::SysWideToNSString(l10n_util::GetString(
363 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21364 } else {
[email protected]e8b5f7882009-09-14 20:59:13365 // Dialog text: warning and explanation.
366 warningText =
367 base::SysWideToNSString(l10n_util::GetStringF(
[email protected]9dcb59662009-09-23 01:15:50368 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_WARNING, product_name,
369 IntToWString(downloadCount)));
[email protected]e8b5f7882009-09-14 20:59:13370 explanationText =
[email protected]9dcb59662009-09-23 01:15:50371 base::SysWideToNSString(l10n_util::GetStringF(
372 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION, product_name));
[email protected]73f5b362009-08-10 23:58:21373
374 // Cancel downloads and exit button text.
375 exitTitle =
[email protected]e8b5f7882009-09-14 20:59:13376 base::SysWideToNSString(l10n_util::GetString(
377 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21378
379 // Wait for downloads button text.
380 waitTitle =
[email protected]e8b5f7882009-09-14 20:59:13381 base::SysWideToNSString(l10n_util::GetString(
382 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21383 }
384
385 // 'waitButton' is the default choice.
[email protected]e8b5f7882009-09-14 20:59:13386 int choice = NSRunAlertPanel(warningText, explanationText,
387 waitTitle, exitTitle, nil);
[email protected]73f5b362009-08-10 23:58:21388 return choice == NSAlertDefaultReturn ? YES : NO;
389}
390
391// Check all profiles for in progress downloads, and if we find any, prompt the
[email protected]09729a552009-08-03 23:21:41392// user to see if we should continue to exit (and thus cancel the downloads), or
393// if we should wait.
394- (BOOL)shouldQuitWithInProgressDownloads {
[email protected]73f5b362009-08-10 23:58:21395 ProfileManager* profile_manager = g_browser_process->profile_manager();
396 if (!profile_manager)
397 return YES;
[email protected]09729a552009-08-03 23:21:41398
[email protected]73f5b362009-08-10 23:58:21399 ProfileManager::const_iterator it = profile_manager->begin();
400 for (; it != profile_manager->end(); ++it) {
401 Profile* profile = *it;
[email protected]09729a552009-08-03 23:21:41402 DownloadManager* download_manager = profile->GetDownloadManager();
[email protected]73f5b362009-08-10 23:58:21403 if (download_manager && download_manager->in_progress_count() > 0) {
404 int downloadCount = download_manager->in_progress_count();
405 if ([self userWillWaitForInProgressDownloads:downloadCount]) {
406 // Create a new browser window (if necessary) and navigate to the
407 // downloads page if the user chooses to wait.
408 Browser* browser = BrowserList::FindBrowserWithProfile(profile);
409 if (!browser) {
410 browser = Browser::Create(profile);
411 browser->window()->Show();
412 }
413 DCHECK(browser);
414 browser->ShowDownloadsTab();
415 return NO;
416 }
[email protected]09729a552009-08-03 23:21:41417
[email protected]73f5b362009-08-10 23:58:21418 // User wants to exit.
419 return YES;
[email protected]09729a552009-08-03 23:21:41420 }
[email protected]09729a552009-08-03 23:21:41421 }
422
[email protected]73f5b362009-08-10 23:58:21423 // No profiles or active downloads found, okay to exit.
[email protected]09729a552009-08-03 23:21:41424 return YES;
425}
426
[email protected]fbc947b2009-06-19 13:28:24427// Called to determine if we should enable the "restore tab" menu item.
428// Checks with the TabRestoreService to see if there's anything there to
429// restore and returns YES if so.
430- (BOOL)canRestoreTab {
431 TabRestoreService* service = [self defaultProfile]->GetTabRestoreService();
432 return service && !service->entries().empty();
433}
434
[email protected]6a281332009-10-13 17:41:06435// Returns true if there is no browser window, or if the active window is
436// blocked by a modal dialog.
437- (BOOL)keyWindowIsMissingOrBlocked {
438 Browser* browser = BrowserList::GetLastActive();
439 return browser == NULL ||
440 ![[browser->window()->GetNativeHandle() attachedSheet]
441 isKindOfClass:[NSWindow class]];
442}
443
[email protected]88d74942009-01-21 22:04:44444// Called to validate menu items when there are no key windows. All the
445// items we care about have been set with the |commandDispatch:| action and
446// a target of FirstResponder in IB. If it's not one of those, let it
447// continue up the responder chain to be handled elsewhere. We pull out the
448// tag as the cross-platform constant to differentiate and dispatch the
449// various commands.
450- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
451 SEL action = [item action];
452 BOOL enable = NO;
453 if (action == @selector(commandDispatch:)) {
454 NSInteger tag = [item tag];
[email protected]fbc947b2009-06-19 13:28:24455 if (menuState_->SupportsCommand(tag)) {
456 switch (tag) {
457 case IDC_RESTORE_TAB:
[email protected]6a281332009-10-13 17:41:06458 enable = [self keyWindowIsMissingOrBlocked] && [self canRestoreTab];
459 break;
460 // The File Menu commands are not automatically disabled by Cocoa when
461 // a dialog sheet obscures the browser window, so we disable them here.
462 // We don't need to include IDC_CLOSE_WINDOW, because app_controller
463 // is only activated when there are no key windows (see function
464 // comment).
465 case IDC_OPEN_FILE:
466 case IDC_NEW_WINDOW:
467 case IDC_NEW_TAB:
468 case IDC_NEW_INCOGNITO_WINDOW:
469 enable = [self keyWindowIsMissingOrBlocked];
[email protected]fbc947b2009-06-19 13:28:24470 break;
471 default:
472 enable = menuState_->IsCommandEnabled(tag) ? YES : NO;
473 }
474 }
[email protected]3b6aa8b62009-09-15 21:36:11475 } else if (action == @selector(terminate:)) {
[email protected]88d74942009-01-21 22:04:44476 enable = YES;
[email protected]3111f08b2009-04-30 16:01:52477 } else if (action == @selector(showPreferences:)) {
478 enable = YES;
[email protected]bde3dda2009-05-20 22:13:07479 } else if (action == @selector(orderFrontStandardAboutPanel:)) {
480 enable = YES;
[email protected]3d4bd23f2009-10-06 03:58:38481 } else if (action == @selector(newWindowFromDock:)) {
482 enable = YES;
[email protected]88d74942009-01-21 22:04:44483 }
484 return enable;
485}
486
487// Called when the user picks a menu item when there are no key windows. Calls
488// through to the browser object to execute the command. This assumes that the
489// command is supported and doesn't check, otherwise it would have been disabled
490// in the UI in validateUserInterfaceItem:.
491- (void)commandDispatch:(id)sender {
[email protected]93e181762009-05-29 14:30:38492 Profile* defaultProfile = [self defaultProfile];
[email protected]f0a51fb52009-03-05 12:46:38493
[email protected]88d74942009-01-21 22:04:44494 NSInteger tag = [sender tag];
495 switch (tag) {
[email protected]fbc947b2009-06-19 13:28:24496 case IDC_NEW_TAB:
[email protected]88d74942009-01-21 22:04:44497 case IDC_NEW_WINDOW:
[email protected]216e9042009-10-29 17:05:23498 g_is_opening_new_window = true;
[email protected]93e181762009-05-29 14:30:38499 Browser::OpenEmptyWindow(defaultProfile);
[email protected]216e9042009-10-29 17:05:23500 g_is_opening_new_window = false;
[email protected]88d74942009-01-21 22:04:44501 break;
[email protected]863ff662009-01-26 20:18:18502 case IDC_NEW_INCOGNITO_WINDOW:
[email protected]e9e211272009-09-22 16:39:11503 Browser::OpenEmptyWindow(defaultProfile->GetOffTheRecordProfile());
[email protected]863ff662009-01-26 20:18:18504 break;
[email protected]fbc947b2009-06-19 13:28:24505 case IDC_RESTORE_TAB:
506 Browser::OpenWindowWithRestoredTabs(defaultProfile);
507 break;
[email protected]e19516d2009-04-28 17:15:19508 case IDC_OPEN_FILE:
[email protected]93e181762009-05-29 14:30:38509 Browser::OpenEmptyWindow(defaultProfile);
[email protected]e19516d2009-04-28 17:15:19510 BrowserList::GetLastActive()->
511 ExecuteCommandWithDisposition(IDC_OPEN_FILE, CURRENT_TAB);
512 break;
[email protected]1fdff702009-10-22 00:36:18513 case IDC_CLEAR_BROWSING_DATA: {
[email protected]93e181762009-05-29 14:30:38514 // There may not be a browser open, so use the default profile.
515 scoped_nsobject<ClearBrowsingDataController> controller(
516 [[ClearBrowsingDataController alloc]
517 initWithProfile:defaultProfile]);
518 [controller runModalDialog];
519 break;
[email protected]1fdff702009-10-22 00:36:18520 }
521 case IDC_HELP_PAGE:
522 Browser::OpenHelpWindow(defaultProfile);
523 break;
[email protected]88d74942009-01-21 22:04:44524 };
525}
526
[email protected]ff81e0a2009-04-13 14:58:54527// NSApplication delegate method called when someone clicks on the
528// dock icon and there are no open windows. To match standard mac
529// behavior, we should open a new window.
530- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
531 hasVisibleWindows:(BOOL)flag {
532 // Don't do anything if there are visible windows. This will cause
533 // AppKit to unminimize the most recently minimized window.
534 if (flag)
535 return YES;
536
537 // Otherwise open a new window.
[email protected]216e9042009-10-29 17:05:23538 g_is_opening_new_window = true;
[email protected]ff81e0a2009-04-13 14:58:54539 Browser::OpenEmptyWindow([self defaultProfile]);
[email protected]216e9042009-10-29 17:05:23540 g_is_opening_new_window = false;
[email protected]ff81e0a2009-04-13 14:58:54541
542 // We've handled the reopen event, so return NO to tell AppKit not
543 // to do anything.
544 return NO;
545}
546
[email protected]88d74942009-01-21 22:04:44547- (void)initMenuState {
[email protected]3111f08b2009-04-30 16:01:52548 menuState_.reset(new CommandUpdater(NULL));
[email protected]fbc947b2009-06-19 13:28:24549 menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
[email protected]88d74942009-01-21 22:04:44550 menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
[email protected]863ff662009-01-26 20:18:18551 menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
[email protected]e19516d2009-04-28 17:15:19552 menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
[email protected]93e181762009-05-29 14:30:38553 menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
[email protected]fbc947b2009-06-19 13:28:24554 menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
[email protected]1fdff702009-10-22 00:36:18555 menuState_->UpdateCommandEnabled(IDC_HELP_PAGE, true);
[email protected]88d74942009-01-21 22:04:44556 // TODO(pinkerton): ...more to come...
557}
[email protected]1bcdb532009-01-16 17:47:57558
[email protected]3f34599d2009-03-25 22:11:43559- (Profile*)defaultProfile {
[email protected]3f34599d2009-03-25 22:11:43560 // TODO(jrg): Find a better way to get the "default" profile.
561 if (g_browser_process->profile_manager())
[email protected]57750f822009-04-21 21:43:09562 return* g_browser_process->profile_manager()->begin();
[email protected]3f34599d2009-03-25 22:11:43563
[email protected]f6314002009-04-23 01:18:13564 return NULL;
[email protected]3f34599d2009-03-25 22:11:43565}
566
[email protected]57750f822009-04-21 21:43:09567// Various methods to open URLs that we get in a native fashion. We use
568// BrowserInit here because on the other platforms, URLs to open come through
569// the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the
570// openings through that for uniform handling.
571
[email protected]136140c2009-05-19 13:58:25572- (void)openURLs:(const std::vector<GURL>&)urls {
573 if (pendingURLs_.get()) {
574 // too early to open; save for later
575 pendingURLs_->insert(pendingURLs_->end(), urls.begin(), urls.end());
576 return;
577 }
[email protected]57750f822009-04-21 21:43:09578
[email protected]0e863512009-05-28 19:45:07579 Browser* browser = BrowserList::GetLastActive();
580 // if no browser window exists then create one with no tabs to be filled in
581 if (!browser) {
582 browser = Browser::Create([self defaultProfile]);
[email protected]ce560f82009-06-03 09:39:44583 browser->window()->Show();
[email protected]0e863512009-05-28 19:45:07584 }
585
[email protected]51343d5a2009-10-26 22:39:33586 CommandLine dummy(CommandLine::ARGUMENTS_ONLY);
[email protected]57750f822009-04-21 21:43:09587 BrowserInit::LaunchWithProfile launch(std::wstring(), dummy);
[email protected]0e863512009-05-28 19:45:07588 launch.OpenURLsInBrowser(browser, false, urls);
[email protected]57750f822009-04-21 21:43:09589}
590
[email protected]136140c2009-05-19 13:58:25591- (void)openPendingURLs {
592 // Since the existence of pendingURLs_ is a flag that it's too early to
593 // open URLs, we need to reset pendingURLs_.
594 std::vector<GURL> urls;
595 swap(urls, *pendingURLs_);
596 pendingURLs_.reset();
597
598 if (urls.size())
599 [self openURLs:urls];
600}
[email protected]57750f822009-04-21 21:43:09601
602- (void)getUrl:(NSAppleEventDescriptor*)event
603 withReply:(NSAppleEventDescriptor*)reply {
604 NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
605 stringValue];
606
607 GURL gurl(base::SysNSStringToUTF8(urlStr));
608 std::vector<GURL> gurlVector;
609 gurlVector.push_back(gurl);
610
[email protected]136140c2009-05-19 13:58:25611 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09612}
613
[email protected]36fe18f2009-04-29 20:26:20614- (void)openFiles:(NSAppleEventDescriptor*)event
615 withReply:(NSAppleEventDescriptor*)reply {
616 // Ordinarily we'd use the NSApplication delegate method
617 // -application:openFiles:, but Cocoa tries to be smart and it sends files
618 // specified on the command line into that delegate method. That's too smart
619 // for us (our setup isn't done by the time Cocoa triggers the delegate method
620 // and we crash). Since all we want are files dropped on the app icon, and we
621 // have cross-platform code to handle the command-line files anyway, an Apple
622 // Event handler fits the bill just right.
623 NSAppleEventDescriptor* fileList =
624 [event paramDescriptorForKeyword:keyDirectObject];
625 if (!fileList)
626 return;
[email protected]57750f822009-04-21 21:43:09627 std::vector<GURL> gurlVector;
628
[email protected]36fe18f2009-04-29 20:26:20629 for (NSInteger i = 1; i <= [fileList numberOfItems]; ++i) {
630 NSAppleEventDescriptor* fileAliasDesc = [fileList descriptorAtIndex:i];
631 if (!fileAliasDesc)
632 continue;
633 NSAppleEventDescriptor* fileURLDesc =
634 [fileAliasDesc coerceToDescriptorType:typeFileURL];
635 if (!fileURLDesc)
636 continue;
637 NSData* fileURLData = [fileURLDesc data];
638 if (!fileURLData)
639 continue;
640 GURL gurl(std::string((char*)[fileURLData bytes], [fileURLData length]));
[email protected]57750f822009-04-21 21:43:09641 gurlVector.push_back(gurl);
642 }
643
[email protected]136140c2009-05-19 13:58:25644 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09645}
[email protected]3f34599d2009-03-25 22:11:43646
[email protected]767543d2009-04-30 19:23:58647// Called when the preferences window is closed. We use this to release the
648// window controller.
649- (void)prefsWindowClosed:(NSNotification*)notify {
[email protected]bde3dda2009-05-20 22:13:07650 [[NSNotificationCenter defaultCenter]
651 removeObserver:self
652 name:kUserDoneEditingPrefsNotification
653 object:prefsController_.get()];
[email protected]767543d2009-04-30 19:23:58654 prefsController_.reset(NULL);
655}
656
[email protected]3111f08b2009-04-30 16:01:52657// Show the preferences window, or bring it to the front if it's already
658// visible.
659- (IBAction)showPreferences:(id)sender {
[email protected]42404382009-04-30 17:59:24660 if (!prefsController_.get()) {
[email protected]42404382009-04-30 17:59:24661 prefsController_.reset([[PreferencesWindowController alloc]
[email protected]cd63ef62009-05-06 19:41:37662 initWithProfile:[self defaultProfile]]);
[email protected]767543d2009-04-30 19:23:58663 // Watch for a notification of when it goes away so that we can destroy
664 // the controller.
665 [[NSNotificationCenter defaultCenter]
666 addObserver:self
667 selector:@selector(prefsWindowClosed:)
668 name:kUserDoneEditingPrefsNotification
669 object:prefsController_.get()];
[email protected]42404382009-04-30 17:59:24670 }
671 [prefsController_ showPreferences:sender];
[email protected]3111f08b2009-04-30 16:01:52672}
673
[email protected]bde3dda2009-05-20 22:13:07674// Called when the about window is closed. We use this to release the
675// window controller.
676- (void)aboutWindowClosed:(NSNotification*)notify {
677 [[NSNotificationCenter defaultCenter]
[email protected]49aeee52009-10-26 19:58:13678 removeObserver:self
679 name:kUserClosedAboutNotification
680 object:aboutController_.get()];
681 aboutController_.reset(nil);
[email protected]bde3dda2009-05-20 22:13:07682}
683
684- (IBAction)orderFrontStandardAboutPanel:(id)sender {
[email protected]bde3dda2009-05-20 22:13:07685 if (!aboutController_) {
686 aboutController_.reset([[AboutWindowController alloc]
[email protected]49aeee52009-10-26 19:58:13687 initWithProfile:[self defaultProfile]]);
688
[email protected]bde3dda2009-05-20 22:13:07689 // Watch for a notification of when it goes away so that we can destroy
690 // the controller.
691 [[NSNotificationCenter defaultCenter]
692 addObserver:self
693 selector:@selector(aboutWindowClosed:)
694 name:kUserClosedAboutNotification
695 object:aboutController_.get()];
696 }
[email protected]49aeee52009-10-26 19:58:13697
[email protected]bde3dda2009-05-20 22:13:07698 if (![[aboutController_ window] isVisible])
699 [[aboutController_ window] center];
[email protected]49aeee52009-10-26 19:58:13700
[email protected]bde3dda2009-05-20 22:13:07701 [aboutController_ showWindow:self];
[email protected]bde3dda2009-05-20 22:13:07702}
703
[email protected]3d4bd23f2009-10-06 03:58:38704// Explicitly bring to the foreground when creating new windows from the dock.
705- (void)newWindowFromDock:(id)sender {
706 [NSApp activateIgnoringOtherApps:YES];
707 [self commandDispatch:sender];
708}
709
[email protected]288bfcd32009-09-14 18:14:46710- (NSMenu*)applicationDockMenu:(id)sender {
[email protected]018a3962009-09-17 22:23:44711 NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
712 NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
713 scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
714 initWithTitle:titleStr
[email protected]3d4bd23f2009-10-06 03:58:38715 action:@selector(newWindowFromDock:)
[email protected]018a3962009-09-17 22:23:44716 keyEquivalent:@""]);
[email protected]288bfcd32009-09-14 18:14:46717 [item setTarget:self];
718 [item setTag:IDC_NEW_WINDOW];
[email protected]018a3962009-09-17 22:23:44719 [dockMenu addItem:item];
[email protected]288bfcd32009-09-14 18:14:46720
721 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
[email protected]018a3962009-09-17 22:23:44722 item.reset([[NSMenuItem alloc] initWithTitle:titleStr
[email protected]3d4bd23f2009-10-06 03:58:38723 action:@selector(newWindowFromDock:)
[email protected]018a3962009-09-17 22:23:44724 keyEquivalent:@""]);
[email protected]288bfcd32009-09-14 18:14:46725 [item setTarget:self];
726 [item setTag:IDC_NEW_INCOGNITO_WINDOW];
[email protected]018a3962009-09-17 22:23:44727 [dockMenu addItem:item];
[email protected]288bfcd32009-09-14 18:14:46728
[email protected]018a3962009-09-17 22:23:44729 return dockMenu;
[email protected]288bfcd32009-09-14 18:14:46730}
731
[email protected]1bcdb532009-01-16 17:47:57732@end
[email protected]60ad3e22009-09-18 21:07:19733
734//---------------------------------------------------------------------------
735
736// Stub for cross-platform method that isn't called on Mac OS X.
737void ShowOptionsWindow(OptionsPage page,
738 OptionsGroup highlight_group,
739 Profile* profile) {
740 NOTIMPLEMENTED();
741}
[email protected]216e9042009-10-29 17:05:23742
743namespace app_controller_mac {
744
745bool IsOpeningNewWindow() {
746 return g_is_opening_new_window;
747}
748
749} // namespace app_controller_mac