blob: 03b3e12502941ec79f374664c496858f3163f12a [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]3896b182009-11-03 16:15:3223#import "chrome/browser/cocoa/bug_report_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]a96ec6a2009-11-04 17:27:0836#include "chrome/common/notification_service.h"
[email protected]cd63ef62009-05-06 19:41:3737#include "chrome/common/pref_names.h"
38#include "chrome/common/pref_service.h"
[email protected]2c47bc12009-04-10 20:14:0039#include "chrome/browser/profile_manager.h"
40#include "chrome/common/temp_scaffolding_stubs.h"
[email protected]9dcb59662009-09-23 01:15:5041#include "grit/chromium_strings.h"
[email protected]09729a552009-08-03 23:21:4142#include "grit/generated_resources.h"
[email protected]88d74942009-01-21 22:04:4443
44@interface AppController(PRIVATE)
45- (void)initMenuState;
[email protected]136140c2009-05-19 13:58:2546- (void)openURLs:(const std::vector<GURL>&)urls;
47- (void)openPendingURLs;
[email protected]57750f822009-04-21 21:43:0948- (void)getUrl:(NSAppleEventDescriptor*)event
49 withReply:(NSAppleEventDescriptor*)reply;
[email protected]36fe18f2009-04-29 20:26:2050- (void)openFiles:(NSAppleEventDescriptor*)event
51 withReply:(NSAppleEventDescriptor*)reply;
[email protected]449dd2f2009-05-27 13:04:0052- (void)windowLayeringDidChange:(NSNotification*)inNotification;
[email protected]73f5b362009-08-10 23:58:2153- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
[email protected]09729a552009-08-03 23:21:4154- (BOOL)shouldQuitWithInProgressDownloads;
[email protected]d989f07c2009-11-14 00:35:4655- (void)showPreferencesWindow:(id)sender
56 page:(OptionsPage)page
57 profile:(Profile*)profile;
[email protected]88d74942009-01-21 22:04:4458@end
[email protected]1bcdb532009-01-16 17:47:5759
[email protected]216e9042009-10-29 17:05:2360// True while AppController is calling Browser::OpenEmptyWindow(). We need a
61// global flag here, analogue to BrowserInit::InProcessStartup() because
62// otherwise the SessionService will try to restore sessions when we make a new
63// window while there are no other active windows.
64static bool g_is_opening_new_window = false;
65
[email protected]1bcdb532009-01-16 17:47:5766@implementation AppController
67
[email protected]cd63ef62009-05-06 19:41:3768// This method is called very early in application startup (ie, before
69// the profile is loaded or any preferences have been registered). Defer any
70// user-data initialization until -applicationDidFinishLaunching:.
[email protected]88d74942009-01-21 22:04:4471- (void)awakeFromNib {
[email protected]136140c2009-05-19 13:58:2572 pendingURLs_.reset(new std::vector<GURL>());
[email protected]88d74942009-01-21 22:04:4473
[email protected]136140c2009-05-19 13:58:2574 // We need to register the handlers early to catch events fired on launch.
[email protected]57750f822009-04-21 21:43:0975 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
76 [em setEventHandler:self
77 andSelector:@selector(getUrl:withReply:)
78 forEventClass:kInternetEventClass
79 andEventID:kAEGetURL];
80 [em setEventHandler:self
81 andSelector:@selector(getUrl:withReply:)
82 forEventClass:'WWW!' // A particularly ancient AppleEvent that dates
83 andEventID:'OURL']; // back to the Spyglass days.
[email protected]36fe18f2009-04-29 20:26:2084 [em setEventHandler:self
85 andSelector:@selector(openFiles:withReply:)
86 forEventClass:kCoreEventClass
87 andEventID:kAEOpenDocuments];
[email protected]cd63ef62009-05-06 19:41:3788
[email protected]449dd2f2009-05-27 13:04:0089 // Register for various window layering changes. We use these to update
90 // various UI elements (command-key equivalents, etc) when the frontmost
91 // window changes.
92 NSNotificationCenter* notificationCenter =
93 [NSNotificationCenter defaultCenter];
[email protected]ce560f82009-06-03 09:39:4494 [notificationCenter
[email protected]449dd2f2009-05-27 13:04:0095 addObserver:self
96 selector:@selector(windowLayeringDidChange:)
97 name:NSWindowDidBecomeKeyNotification
98 object:nil];
99 [notificationCenter
100 addObserver:self
101 selector:@selector(windowLayeringDidChange:)
102 name:NSWindowDidResignKeyNotification
103 object:nil];
104 [notificationCenter
105 addObserver:self
106 selector:@selector(windowLayeringDidChange:)
107 name:NSWindowDidBecomeMainNotification
108 object:nil];
109 [notificationCenter
110 addObserver:self
111 selector:@selector(windowLayeringDidChange:)
112 name:NSWindowDidResignMainNotification
113 object:nil];
114
115 // Register for a notification that the number of tabs changes in windows
116 // so we can adjust the close tab/window command keys.
117 [notificationCenter
118 addObserver:self
119 selector:@selector(tabsChanged:)
120 name:kTabStripNumberOfTabsChanged
121 object:nil];
122
[email protected]136140c2009-05-19 13:58:25123 // Set up the command updater for when there are no windows open
124 [self initMenuState];
[email protected]f4acfae82009-09-11 00:33:38125
126 // Activate (bring to foreground) if asked to do so. On
127 // Windows this logic isn't necessary since
128 // BrowserWindow::Activate() calls ::SetForegroundWindow() which is
129 // adequate. On Mac, BrowserWindow::Activate() calls -[NSWindow
130 // makeKeyAndOrderFront:] which does not activate the application
131 // itself.
132 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
133 if (parsed_command_line.HasSwitch(switches::kActivateOnLaunch)) {
134 [NSApp activateIgnoringOtherApps:YES];
135 }
[email protected]136140c2009-05-19 13:58:25136}
137
[email protected]a9e8afc2009-08-11 22:03:17138- (NSApplicationTerminateReply)applicationShouldTerminate:
139 (NSApplication *)sender {
[email protected]a9e8afc2009-08-11 22:03:17140 // Check for in-progress downloads, and prompt the user if they really want to
141 // quit (and thus cancel the downloads).
142 if (![self shouldQuitWithInProgressDownloads])
143 return NSTerminateCancel;
144
145 return NSTerminateNow;
146}
147
[email protected]449dd2f2009-05-27 13:04:00148// Called when the app is shutting down. Clean-up as appropriate.
149- (void)applicationWillTerminate:(NSNotification *)aNotification {
[email protected]3b6aa8b62009-09-15 21:36:11150 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
151 [em removeEventHandlerForEventClass:kInternetEventClass
152 andEventID:kAEGetURL];
153 [em removeEventHandlerForEventClass:'WWW!'
154 andEventID:'OURL'];
155 [em removeEventHandlerForEventClass:kCoreEventClass
156 andEventID:kAEOpenDocuments];
157
158 // Close all the windows.
159 BrowserList::CloseAllBrowsers(true);
160
161 // On Windows, this is done in Browser::OnWindowClosing, but that's not
162 // appropriate on Mac since we don't shut down when we reach zero windows.
163 browser_shutdown::OnShutdownStarting(browser_shutdown::BROWSER_EXIT);
164
165 // Release the reference to the browser process. Once all the browsers get
166 // dealloc'd, it will stop the RunLoop and fall back into main().
167 g_browser_process->ReleaseModule();
[email protected]fbc947b2009-06-19 13:28:24168
[email protected]449dd2f2009-05-27 13:04:00169 [[NSNotificationCenter defaultCenter] removeObserver:self];
170}
171
[email protected]3b6aa8b62009-09-15 21:36:11172- (void)didEndMainMessageLoop {
173 DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile]));
174 if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) {
175 // As we're shutting down, we need to nuke the TabRestoreService, which
176 // will start the shutdown of the NavigationControllers and allow for
177 // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
178 // and may end up crashing when some thread tries to use the IO thread (or
179 // another thread) that is no longer valid.
180 [self defaultProfile]->ResetTabRestoreService();
181 }
182}
183
[email protected]449dd2f2009-05-27 13:04:00184// Helper routine to get the window controller if the key window is a tabbed
185// window, or nil if not. Examples of non-tabbed windows are "about" or
186// "preferences".
187- (TabWindowController*)keyWindowTabController {
188 NSWindowController* keyWindowController =
[email protected]5dbabd02009-09-17 20:44:13189 [[NSApp keyWindow] windowController];
[email protected]449dd2f2009-05-27 13:04:00190 if ([keyWindowController isKindOfClass:[TabWindowController class]])
191 return (TabWindowController*)keyWindowController;
192
193 return nil;
194}
195
[email protected]74c0c642009-09-22 21:25:50196// Helper routine to get the window controller if the main window is a tabbed
197// window, or nil if not. Examples of non-tabbed windows are "about" or
198// "preferences".
199- (TabWindowController*)mainWindowTabController {
200 NSWindowController* mainWindowController =
201 [[NSApp mainWindow] windowController];
202 if ([mainWindowController isKindOfClass:[TabWindowController class]])
203 return (TabWindowController*)mainWindowController;
204
205 return nil;
206}
207
[email protected]449dd2f2009-05-27 13:04:00208// If the window has tabs, make "close window" be cmd-shift-w, otherwise leave
209// it as the normal cmd-w. Capitalization of the key equivalent affects whether
210// the shift modifer is used.
211- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs {
212 [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")];
[email protected]74c0c642009-09-22 21:25:50213 [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
[email protected]449dd2f2009-05-27 13:04:00214}
215
216// If the window has tabs, make "close tab" take over cmd-w, otherwise it
217// shouldn't have any key-equivalent because it should be disabled.
218- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs {
219 if (hasTabs) {
220 [closeTabMenuItem_ setKeyEquivalent:@"w"];
221 [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
222 } else {
223 [closeTabMenuItem_ setKeyEquivalent:@""];
224 [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
225 }
226}
227
[email protected]74c0c642009-09-22 21:25:50228// Explicitly remove any command-key equivalents from the close tab/window
229// menus so that nothing can go haywire if we get a user action during pending
230// updates.
231- (void)clearCloseMenuItemKeyEquivalents {
232 [closeTabMenuItem_ setKeyEquivalent:@""];
233 [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
234 [closeWindowMenuItem_ setKeyEquivalent:@""];
235 [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
236}
237
[email protected]449dd2f2009-05-27 13:04:00238// See if we have a window with tabs open, and adjust the key equivalents for
239// Close Tab/Close Window accordingly
240- (void)fixCloseMenuItemKeyEquivalents {
241 TabWindowController* tabController = [self keyWindowTabController];
[email protected]74c0c642009-09-22 21:25:50242 if (!tabController && ![NSApp keyWindow]) {
243 // There might be a small amount of time where there is no key window,
244 // so just use our main browser window if there is one.
245 tabController = [self mainWindowTabController];
246 }
[email protected]449dd2f2009-05-27 13:04:00247 BOOL windowWithMultipleTabs =
248 (tabController && [tabController numberOfTabs] > 1);
249 [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs];
250 [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs];
251 fileMenuUpdatePending_ = NO;
252}
253
254// Fix up the "close tab/close window" command-key equivalents. We do this
255// after a delay to ensure that window layer state has been set by the time
256// we do the enabling.
257- (void)delayedFixCloseMenuItemKeyEquivalents {
258 if (!fileMenuUpdatePending_) {
[email protected]74c0c642009-09-22 21:25:50259 // The OS prefers keypresses to timers, so it's possible that a cmd-w
260 // can sneak in before this timer fires. In order to prevent that from
261 // having any bad consequences, just clear the keys combos altogether. They
262 // will be reset when the timer eventually fires.
263 [self clearCloseMenuItemKeyEquivalents];
[email protected]449dd2f2009-05-27 13:04:00264 [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
265 withObject:nil
266 afterDelay:0];
267 fileMenuUpdatePending_ = YES;
268 }
269}
270
271// Called when we get a notification about the window layering changing to
272// update the UI based on the new main window.
273- (void)windowLayeringDidChange:(NSNotification*)notify {
274 [self delayedFixCloseMenuItemKeyEquivalents];
[email protected]ce560f82009-06-03 09:39:44275
[email protected]449dd2f2009-05-27 13:04:00276 // TODO(pinkerton): If we have other things here, such as inspector panels
277 // that follow the contents of the selected webpage, we would update those
278 // here.
279}
280
281// Called when the number of tabs changes in one of the browser windows. The
282// object is the tab strip controller, but we don't currently care.
283- (void)tabsChanged:(NSNotification*)notify {
[email protected]e72746b12009-10-19 14:11:51284 // We don't need to do this on a delay, as in the method above, because the
285 // window layering isn't changing. As a result, there's no chance that a
286 // different window will sneak in as the key window and cause the problems
287 // we hacked around above by clearing the key equivalents.
288 [self fixCloseMenuItemKeyEquivalents];
[email protected]449dd2f2009-05-27 13:04:00289}
290
[email protected]414fde592009-05-21 16:14:43291// If the auto-update interval is not set, make it 5 hours.
292// This code is specific to Mac Chrome Dev Channel.
293// Placed here for 2 reasons:
294// 1) Same spot as other Pref stuff
295// 2) Try and be friendly by keeping this after app launch
296// TODO(jrg): remove once we go Beta.
297- (void)setUpdateCheckInterval {
298#if defined(GOOGLE_CHROME_BUILD)
299 CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
300 CFStringRef checkInterval = (CFStringRef)@"checkInterval";
301 CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
302 if (!plist) {
303 const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
304 NSNumber *value = [NSNumber numberWithFloat:fiveHoursInSeconds];
305 CFPreferencesSetAppValue(checkInterval, value, app);
306 CFPreferencesAppSynchronize(app);
307 }
308#endif
309}
310
[email protected]136140c2009-05-19 13:58:25311// This is called after profiles have been loaded and preferences registered.
312// It is safe to access the default profile here.
313- (void)applicationDidFinishLaunching:(NSNotification*)notify {
314 // Hold an extra ref to the BrowserProcess singleton so it doesn't go away
315 // when all the browser windows get closed. We'll release it on quit which
316 // will be the signal to exit.
317 DCHECK(g_browser_process);
318 g_browser_process->AddRefModule();
319
[email protected]e5575022009-08-22 00:31:22320 bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile]));
[email protected]1c84c82c2009-07-27 15:37:25321 historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile]));
[email protected]cd63ef62009-05-06 19:41:37322
[email protected]414fde592009-05-21 16:14:43323 [self setUpdateCheckInterval];
[email protected]2bcec612009-05-14 17:50:53324
325 // Build up the encoding menu, the order of the items differs based on the
326 // current locale (see http://crbug.com/7647 for details).
327 // We need a valid g_browser_process to get the profile which is why we can't
328 // call this from awakeFromNib.
[email protected]fc349322009-09-23 22:28:23329 NSMenu* view_menu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
330 NSMenuItem* encoding_menu_item = [view_menu itemWithTag:IDC_ENCODING_MENU];
331 NSMenu *encoding_menu = [encoding_menu_item submenu];
332 EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile],
333 encoding_menu);
[email protected]2bcec612009-05-14 17:50:53334
[email protected]136140c2009-05-19 13:58:25335 // Now that we're initialized we can open any URLs we've been holding onto.
336 [self openPendingURLs];
[email protected]7c321082009-02-09 15:35:47337}
338
[email protected]a96ec6a2009-11-04 17:27:08339// This is called after profiles have been loaded and preferences registered.
340// It is safe to access the default profile here.
341- (void)applicationDidBecomeActive:(NSNotification*)notify {
342 NotificationService::current()->Notify(NotificationType::APP_ACTIVATED,
343 NotificationService::AllSources(),
344 NotificationService::NoDetails());
345}
346
[email protected]73f5b362009-08-10 23:58:21347// Helper function for populating and displaying the in progress downloads at
348// exit alert panel.
349- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
[email protected]e8b5f7882009-09-14 20:59:13350 NSString* warningText = nil;
351 NSString* explanationText = nil;
[email protected]73f5b362009-08-10 23:58:21352 NSString* waitTitle = nil;
353 NSString* exitTitle = nil;
354
[email protected]9dcb59662009-09-23 01:15:50355 std::wstring product_name = l10n_util::GetString(IDS_PRODUCT_NAME);
356
[email protected]73f5b362009-08-10 23:58:21357 // Set the dialog text based on whether or not there are multiple downloads.
358 if (downloadCount == 1) {
[email protected]e8b5f7882009-09-14 20:59:13359 // Dialog text: warning and explanation.
360 warningText =
[email protected]9dcb59662009-09-23 01:15:50361 base::SysWideToNSString(l10n_util::GetStringF(
362 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_WARNING, product_name));
[email protected]e8b5f7882009-09-14 20:59:13363 explanationText =
[email protected]9dcb59662009-09-23 01:15:50364 base::SysWideToNSString(l10n_util::GetStringF(
365 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION, product_name));
[email protected]73f5b362009-08-10 23:58:21366
367 // Cancel download and exit button text.
368 exitTitle =
[email protected]e8b5f7882009-09-14 20:59:13369 base::SysWideToNSString(l10n_util::GetString(
370 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21371
372 // Wait for download button text.
373 waitTitle =
[email protected]e8b5f7882009-09-14 20:59:13374 base::SysWideToNSString(l10n_util::GetString(
375 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21376 } else {
[email protected]e8b5f7882009-09-14 20:59:13377 // Dialog text: warning and explanation.
378 warningText =
379 base::SysWideToNSString(l10n_util::GetStringF(
[email protected]9dcb59662009-09-23 01:15:50380 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_WARNING, product_name,
381 IntToWString(downloadCount)));
[email protected]e8b5f7882009-09-14 20:59:13382 explanationText =
[email protected]9dcb59662009-09-23 01:15:50383 base::SysWideToNSString(l10n_util::GetStringF(
384 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION, product_name));
[email protected]73f5b362009-08-10 23:58:21385
386 // Cancel downloads and exit button text.
387 exitTitle =
[email protected]e8b5f7882009-09-14 20:59:13388 base::SysWideToNSString(l10n_util::GetString(
389 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21390
391 // Wait for downloads button text.
392 waitTitle =
[email protected]e8b5f7882009-09-14 20:59:13393 base::SysWideToNSString(l10n_util::GetString(
394 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL));
[email protected]73f5b362009-08-10 23:58:21395 }
396
397 // 'waitButton' is the default choice.
[email protected]e8b5f7882009-09-14 20:59:13398 int choice = NSRunAlertPanel(warningText, explanationText,
399 waitTitle, exitTitle, nil);
[email protected]73f5b362009-08-10 23:58:21400 return choice == NSAlertDefaultReturn ? YES : NO;
401}
402
403// Check all profiles for in progress downloads, and if we find any, prompt the
[email protected]09729a552009-08-03 23:21:41404// user to see if we should continue to exit (and thus cancel the downloads), or
405// if we should wait.
406- (BOOL)shouldQuitWithInProgressDownloads {
[email protected]73f5b362009-08-10 23:58:21407 ProfileManager* profile_manager = g_browser_process->profile_manager();
408 if (!profile_manager)
409 return YES;
[email protected]09729a552009-08-03 23:21:41410
[email protected]73f5b362009-08-10 23:58:21411 ProfileManager::const_iterator it = profile_manager->begin();
412 for (; it != profile_manager->end(); ++it) {
413 Profile* profile = *it;
[email protected]09729a552009-08-03 23:21:41414 DownloadManager* download_manager = profile->GetDownloadManager();
[email protected]73f5b362009-08-10 23:58:21415 if (download_manager && download_manager->in_progress_count() > 0) {
416 int downloadCount = download_manager->in_progress_count();
417 if ([self userWillWaitForInProgressDownloads:downloadCount]) {
418 // Create a new browser window (if necessary) and navigate to the
419 // downloads page if the user chooses to wait.
420 Browser* browser = BrowserList::FindBrowserWithProfile(profile);
421 if (!browser) {
422 browser = Browser::Create(profile);
423 browser->window()->Show();
424 }
425 DCHECK(browser);
426 browser->ShowDownloadsTab();
427 return NO;
428 }
[email protected]09729a552009-08-03 23:21:41429
[email protected]73f5b362009-08-10 23:58:21430 // User wants to exit.
431 return YES;
[email protected]09729a552009-08-03 23:21:41432 }
[email protected]09729a552009-08-03 23:21:41433 }
434
[email protected]73f5b362009-08-10 23:58:21435 // No profiles or active downloads found, okay to exit.
[email protected]09729a552009-08-03 23:21:41436 return YES;
437}
438
[email protected]fbc947b2009-06-19 13:28:24439// Called to determine if we should enable the "restore tab" menu item.
440// Checks with the TabRestoreService to see if there's anything there to
441// restore and returns YES if so.
442- (BOOL)canRestoreTab {
443 TabRestoreService* service = [self defaultProfile]->GetTabRestoreService();
444 return service && !service->entries().empty();
445}
446
[email protected]6a281332009-10-13 17:41:06447// Returns true if there is no browser window, or if the active window is
448// blocked by a modal dialog.
449- (BOOL)keyWindowIsMissingOrBlocked {
450 Browser* browser = BrowserList::GetLastActive();
451 return browser == NULL ||
452 ![[browser->window()->GetNativeHandle() attachedSheet]
453 isKindOfClass:[NSWindow class]];
454}
455
[email protected]88d74942009-01-21 22:04:44456// Called to validate menu items when there are no key windows. All the
457// items we care about have been set with the |commandDispatch:| action and
458// a target of FirstResponder in IB. If it's not one of those, let it
459// continue up the responder chain to be handled elsewhere. We pull out the
460// tag as the cross-platform constant to differentiate and dispatch the
461// various commands.
462- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
463 SEL action = [item action];
464 BOOL enable = NO;
465 if (action == @selector(commandDispatch:)) {
466 NSInteger tag = [item tag];
[email protected]fbc947b2009-06-19 13:28:24467 if (menuState_->SupportsCommand(tag)) {
468 switch (tag) {
[email protected]419eb2a2009-11-11 16:26:26469 // The File Menu commands are not automatically disabled by Cocoa when a
470 // dialog sheet obscures the browser window, so we disable several of
471 // them here. We don't need to include IDC_CLOSE_WINDOW, because
472 // app_controller is only activated when there are no key windows (see
473 // function comment).
[email protected]fbc947b2009-06-19 13:28:24474 case IDC_RESTORE_TAB:
[email protected]6a281332009-10-13 17:41:06475 enable = [self keyWindowIsMissingOrBlocked] && [self canRestoreTab];
476 break;
[email protected]6a281332009-10-13 17:41:06477 case IDC_OPEN_FILE:
[email protected]6a281332009-10-13 17:41:06478 case IDC_NEW_TAB:
[email protected]6a281332009-10-13 17:41:06479 enable = [self keyWindowIsMissingOrBlocked];
[email protected]fbc947b2009-06-19 13:28:24480 break;
481 default:
482 enable = menuState_->IsCommandEnabled(tag) ? YES : NO;
483 }
484 }
[email protected]3b6aa8b62009-09-15 21:36:11485 } else if (action == @selector(terminate:)) {
[email protected]88d74942009-01-21 22:04:44486 enable = YES;
[email protected]3111f08b2009-04-30 16:01:52487 } else if (action == @selector(showPreferences:)) {
488 enable = YES;
[email protected]bde3dda2009-05-20 22:13:07489 } else if (action == @selector(orderFrontStandardAboutPanel:)) {
490 enable = YES;
[email protected]3d4bd23f2009-10-06 03:58:38491 } else if (action == @selector(newWindowFromDock:)) {
492 enable = YES;
[email protected]88d74942009-01-21 22:04:44493 }
494 return enable;
495}
496
497// Called when the user picks a menu item when there are no key windows. Calls
498// through to the browser object to execute the command. This assumes that the
499// command is supported and doesn't check, otherwise it would have been disabled
500// in the UI in validateUserInterfaceItem:.
501- (void)commandDispatch:(id)sender {
[email protected]93e181762009-05-29 14:30:38502 Profile* defaultProfile = [self defaultProfile];
[email protected]f0a51fb52009-03-05 12:46:38503
[email protected]88d74942009-01-21 22:04:44504 NSInteger tag = [sender tag];
505 switch (tag) {
[email protected]fbc947b2009-06-19 13:28:24506 case IDC_NEW_TAB:
[email protected]88d74942009-01-21 22:04:44507 case IDC_NEW_WINDOW:
[email protected]91a5b3e2009-10-30 19:32:22508 case IDC_FOCUS_LOCATION:
[email protected]216e9042009-10-29 17:05:23509 g_is_opening_new_window = true;
[email protected]93e181762009-05-29 14:30:38510 Browser::OpenEmptyWindow(defaultProfile);
[email protected]216e9042009-10-29 17:05:23511 g_is_opening_new_window = false;
[email protected]88d74942009-01-21 22:04:44512 break;
[email protected]863ff662009-01-26 20:18:18513 case IDC_NEW_INCOGNITO_WINDOW:
[email protected]e9e211272009-09-22 16:39:11514 Browser::OpenEmptyWindow(defaultProfile->GetOffTheRecordProfile());
[email protected]863ff662009-01-26 20:18:18515 break;
[email protected]fbc947b2009-06-19 13:28:24516 case IDC_RESTORE_TAB:
517 Browser::OpenWindowWithRestoredTabs(defaultProfile);
518 break;
[email protected]e19516d2009-04-28 17:15:19519 case IDC_OPEN_FILE:
[email protected]93e181762009-05-29 14:30:38520 Browser::OpenEmptyWindow(defaultProfile);
[email protected]e19516d2009-04-28 17:15:19521 BrowserList::GetLastActive()->
522 ExecuteCommandWithDisposition(IDC_OPEN_FILE, CURRENT_TAB);
523 break;
[email protected]1fdff702009-10-22 00:36:18524 case IDC_CLEAR_BROWSING_DATA: {
[email protected]93e181762009-05-29 14:30:38525 // There may not be a browser open, so use the default profile.
526 scoped_nsobject<ClearBrowsingDataController> controller(
527 [[ClearBrowsingDataController alloc]
528 initWithProfile:defaultProfile]);
529 [controller runModalDialog];
530 break;
[email protected]1fdff702009-10-22 00:36:18531 }
[email protected]91a5b3e2009-10-30 19:32:22532 case IDC_SHOW_HISTORY:
533 Browser::OpenHistoryWindow(defaultProfile);
534 break;
535 case IDC_SHOW_DOWNLOADS:
536 Browser::OpenDownloadsWindow(defaultProfile);
537 break;
[email protected]1fdff702009-10-22 00:36:18538 case IDC_HELP_PAGE:
539 Browser::OpenHelpWindow(defaultProfile);
540 break;
[email protected]3896b182009-11-03 16:15:32541 case IDC_REPORT_BUG: {
542 Browser* browser = BrowserList::GetLastActive();
543 TabContents* current_tab = (browser != NULL) ?
544 browser->GetSelectedTabContents() : NULL;
545 BugReportWindowController* controller =
546 [[BugReportWindowController alloc]
547 initWithTabContents:current_tab
548 profile:[self defaultProfile]];
549 [controller runModalDialog];
550 break;
551 }
[email protected]88d74942009-01-21 22:04:44552 };
553}
554
[email protected]ff81e0a2009-04-13 14:58:54555// NSApplication delegate method called when someone clicks on the
556// dock icon and there are no open windows. To match standard mac
557// behavior, we should open a new window.
558- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
559 hasVisibleWindows:(BOOL)flag {
560 // Don't do anything if there are visible windows. This will cause
561 // AppKit to unminimize the most recently minimized window.
562 if (flag)
563 return YES;
564
565 // Otherwise open a new window.
[email protected]216e9042009-10-29 17:05:23566 g_is_opening_new_window = true;
[email protected]ff81e0a2009-04-13 14:58:54567 Browser::OpenEmptyWindow([self defaultProfile]);
[email protected]216e9042009-10-29 17:05:23568 g_is_opening_new_window = false;
[email protected]ff81e0a2009-04-13 14:58:54569
570 // We've handled the reopen event, so return NO to tell AppKit not
571 // to do anything.
572 return NO;
573}
574
[email protected]88d74942009-01-21 22:04:44575- (void)initMenuState {
[email protected]3111f08b2009-04-30 16:01:52576 menuState_.reset(new CommandUpdater(NULL));
[email protected]fbc947b2009-06-19 13:28:24577 menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
[email protected]88d74942009-01-21 22:04:44578 menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
[email protected]863ff662009-01-26 20:18:18579 menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
[email protected]e19516d2009-04-28 17:15:19580 menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
[email protected]93e181762009-05-29 14:30:38581 menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
[email protected]fbc947b2009-06-19 13:28:24582 menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
[email protected]91a5b3e2009-10-30 19:32:22583 menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
584 menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
585 menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
[email protected]1fdff702009-10-22 00:36:18586 menuState_->UpdateCommandEnabled(IDC_HELP_PAGE, true);
[email protected]3896b182009-11-03 16:15:32587 menuState_->UpdateCommandEnabled(IDC_REPORT_BUG, true);
[email protected]88d74942009-01-21 22:04:44588 // TODO(pinkerton): ...more to come...
589}
[email protected]1bcdb532009-01-16 17:47:57590
[email protected]3f34599d2009-03-25 22:11:43591- (Profile*)defaultProfile {
[email protected]3f34599d2009-03-25 22:11:43592 // TODO(jrg): Find a better way to get the "default" profile.
593 if (g_browser_process->profile_manager())
[email protected]57750f822009-04-21 21:43:09594 return* g_browser_process->profile_manager()->begin();
[email protected]3f34599d2009-03-25 22:11:43595
[email protected]f6314002009-04-23 01:18:13596 return NULL;
[email protected]3f34599d2009-03-25 22:11:43597}
598
[email protected]57750f822009-04-21 21:43:09599// Various methods to open URLs that we get in a native fashion. We use
600// BrowserInit here because on the other platforms, URLs to open come through
601// the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the
602// openings through that for uniform handling.
603
[email protected]136140c2009-05-19 13:58:25604- (void)openURLs:(const std::vector<GURL>&)urls {
605 if (pendingURLs_.get()) {
606 // too early to open; save for later
607 pendingURLs_->insert(pendingURLs_->end(), urls.begin(), urls.end());
608 return;
609 }
[email protected]57750f822009-04-21 21:43:09610
[email protected]0e863512009-05-28 19:45:07611 Browser* browser = BrowserList::GetLastActive();
612 // if no browser window exists then create one with no tabs to be filled in
613 if (!browser) {
614 browser = Browser::Create([self defaultProfile]);
[email protected]ce560f82009-06-03 09:39:44615 browser->window()->Show();
[email protected]0e863512009-05-28 19:45:07616 }
617
[email protected]51343d5a2009-10-26 22:39:33618 CommandLine dummy(CommandLine::ARGUMENTS_ONLY);
[email protected]57750f822009-04-21 21:43:09619 BrowserInit::LaunchWithProfile launch(std::wstring(), dummy);
[email protected]0e863512009-05-28 19:45:07620 launch.OpenURLsInBrowser(browser, false, urls);
[email protected]57750f822009-04-21 21:43:09621}
622
[email protected]136140c2009-05-19 13:58:25623- (void)openPendingURLs {
624 // Since the existence of pendingURLs_ is a flag that it's too early to
625 // open URLs, we need to reset pendingURLs_.
626 std::vector<GURL> urls;
627 swap(urls, *pendingURLs_);
628 pendingURLs_.reset();
629
630 if (urls.size())
631 [self openURLs:urls];
632}
[email protected]57750f822009-04-21 21:43:09633
634- (void)getUrl:(NSAppleEventDescriptor*)event
635 withReply:(NSAppleEventDescriptor*)reply {
636 NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
637 stringValue];
638
639 GURL gurl(base::SysNSStringToUTF8(urlStr));
640 std::vector<GURL> gurlVector;
641 gurlVector.push_back(gurl);
642
[email protected]136140c2009-05-19 13:58:25643 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09644}
645
[email protected]36fe18f2009-04-29 20:26:20646- (void)openFiles:(NSAppleEventDescriptor*)event
647 withReply:(NSAppleEventDescriptor*)reply {
648 // Ordinarily we'd use the NSApplication delegate method
649 // -application:openFiles:, but Cocoa tries to be smart and it sends files
650 // specified on the command line into that delegate method. That's too smart
651 // for us (our setup isn't done by the time Cocoa triggers the delegate method
652 // and we crash). Since all we want are files dropped on the app icon, and we
653 // have cross-platform code to handle the command-line files anyway, an Apple
654 // Event handler fits the bill just right.
655 NSAppleEventDescriptor* fileList =
656 [event paramDescriptorForKeyword:keyDirectObject];
657 if (!fileList)
658 return;
[email protected]57750f822009-04-21 21:43:09659 std::vector<GURL> gurlVector;
660
[email protected]36fe18f2009-04-29 20:26:20661 for (NSInteger i = 1; i <= [fileList numberOfItems]; ++i) {
662 NSAppleEventDescriptor* fileAliasDesc = [fileList descriptorAtIndex:i];
663 if (!fileAliasDesc)
664 continue;
665 NSAppleEventDescriptor* fileURLDesc =
666 [fileAliasDesc coerceToDescriptorType:typeFileURL];
667 if (!fileURLDesc)
668 continue;
669 NSData* fileURLData = [fileURLDesc data];
670 if (!fileURLData)
671 continue;
672 GURL gurl(std::string((char*)[fileURLData bytes], [fileURLData length]));
[email protected]57750f822009-04-21 21:43:09673 gurlVector.push_back(gurl);
674 }
675
[email protected]136140c2009-05-19 13:58:25676 [self openURLs:gurlVector];
[email protected]57750f822009-04-21 21:43:09677}
[email protected]3f34599d2009-03-25 22:11:43678
[email protected]767543d2009-04-30 19:23:58679// Called when the preferences window is closed. We use this to release the
680// window controller.
681- (void)prefsWindowClosed:(NSNotification*)notify {
[email protected]bde3dda2009-05-20 22:13:07682 [[NSNotificationCenter defaultCenter]
683 removeObserver:self
684 name:kUserDoneEditingPrefsNotification
685 object:prefsController_.get()];
[email protected]767543d2009-04-30 19:23:58686 prefsController_.reset(NULL);
687}
688
[email protected]3111f08b2009-04-30 16:01:52689// Show the preferences window, or bring it to the front if it's already
690// visible.
691- (IBAction)showPreferences:(id)sender {
[email protected]d989f07c2009-11-14 00:35:46692 [self showPreferencesWindow:sender
693 page:OPTIONS_PAGE_DEFAULT
694 profile:[self defaultProfile]];
695}
696
697- (void)showPreferencesWindow:(id)sender
698 page:(OptionsPage)page
699 profile:(Profile*)profile {
700 if (prefsController_.get()) {
701 [prefsController_ switchToPage:page animate:YES];
702 } else {
[email protected]42404382009-04-30 17:59:24703 prefsController_.reset([[PreferencesWindowController alloc]
[email protected]d989f07c2009-11-14 00:35:46704 initWithProfile:profile
705 initialPage:page]);
[email protected]767543d2009-04-30 19:23:58706 // Watch for a notification of when it goes away so that we can destroy
707 // the controller.
708 [[NSNotificationCenter defaultCenter]
709 addObserver:self
710 selector:@selector(prefsWindowClosed:)
711 name:kUserDoneEditingPrefsNotification
712 object:prefsController_.get()];
[email protected]42404382009-04-30 17:59:24713 }
714 [prefsController_ showPreferences:sender];
[email protected]3111f08b2009-04-30 16:01:52715}
716
[email protected]bde3dda2009-05-20 22:13:07717// Called when the about window is closed. We use this to release the
718// window controller.
719- (void)aboutWindowClosed:(NSNotification*)notify {
720 [[NSNotificationCenter defaultCenter]
[email protected]49aeee52009-10-26 19:58:13721 removeObserver:self
722 name:kUserClosedAboutNotification
723 object:aboutController_.get()];
724 aboutController_.reset(nil);
[email protected]bde3dda2009-05-20 22:13:07725}
726
727- (IBAction)orderFrontStandardAboutPanel:(id)sender {
[email protected]bde3dda2009-05-20 22:13:07728 if (!aboutController_) {
729 aboutController_.reset([[AboutWindowController alloc]
[email protected]49aeee52009-10-26 19:58:13730 initWithProfile:[self defaultProfile]]);
731
[email protected]bde3dda2009-05-20 22:13:07732 // Watch for a notification of when it goes away so that we can destroy
733 // the controller.
734 [[NSNotificationCenter defaultCenter]
735 addObserver:self
736 selector:@selector(aboutWindowClosed:)
737 name:kUserClosedAboutNotification
738 object:aboutController_.get()];
739 }
[email protected]49aeee52009-10-26 19:58:13740
[email protected]bde3dda2009-05-20 22:13:07741 if (![[aboutController_ window] isVisible])
742 [[aboutController_ window] center];
[email protected]49aeee52009-10-26 19:58:13743
[email protected]bde3dda2009-05-20 22:13:07744 [aboutController_ showWindow:self];
[email protected]bde3dda2009-05-20 22:13:07745}
746
[email protected]3d4bd23f2009-10-06 03:58:38747// Explicitly bring to the foreground when creating new windows from the dock.
748- (void)newWindowFromDock:(id)sender {
749 [NSApp activateIgnoringOtherApps:YES];
750 [self commandDispatch:sender];
751}
752
[email protected]288bfcd32009-09-14 18:14:46753- (NSMenu*)applicationDockMenu:(id)sender {
[email protected]018a3962009-09-17 22:23:44754 NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
755 NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
756 scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
757 initWithTitle:titleStr
[email protected]3d4bd23f2009-10-06 03:58:38758 action:@selector(newWindowFromDock:)
[email protected]018a3962009-09-17 22:23:44759 keyEquivalent:@""]);
[email protected]288bfcd32009-09-14 18:14:46760 [item setTarget:self];
761 [item setTag:IDC_NEW_WINDOW];
[email protected]018a3962009-09-17 22:23:44762 [dockMenu addItem:item];
[email protected]288bfcd32009-09-14 18:14:46763
764 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
[email protected]018a3962009-09-17 22:23:44765 item.reset([[NSMenuItem alloc] initWithTitle:titleStr
[email protected]3d4bd23f2009-10-06 03:58:38766 action:@selector(newWindowFromDock:)
[email protected]018a3962009-09-17 22:23:44767 keyEquivalent:@""]);
[email protected]288bfcd32009-09-14 18:14:46768 [item setTarget:self];
769 [item setTag:IDC_NEW_INCOGNITO_WINDOW];
[email protected]018a3962009-09-17 22:23:44770 [dockMenu addItem:item];
[email protected]288bfcd32009-09-14 18:14:46771
[email protected]018a3962009-09-17 22:23:44772 return dockMenu;
[email protected]288bfcd32009-09-14 18:14:46773}
774
[email protected]1bcdb532009-01-16 17:47:57775@end
[email protected]60ad3e22009-09-18 21:07:19776
777//---------------------------------------------------------------------------
778
[email protected]60ad3e22009-09-18 21:07:19779void ShowOptionsWindow(OptionsPage page,
780 OptionsGroup highlight_group,
781 Profile* profile) {
[email protected]d989f07c2009-11-14 00:35:46782 // TODO(akalin): Use highlight_group.
783 AppController* appController = [NSApp delegate];
784 [appController showPreferencesWindow:nil page:page profile:profile];
[email protected]60ad3e22009-09-18 21:07:19785}
[email protected]216e9042009-10-29 17:05:23786
787namespace app_controller_mac {
788
789bool IsOpeningNewWindow() {
790 return g_is_opening_new_window;
791}
792
793} // namespace app_controller_mac