diff options
Diffstat (limited to 'chrome/browser/ui/cocoa/browser_window_cocoa.mm')
-rw-r--r-- | chrome/browser/ui/cocoa/browser_window_cocoa.mm | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/browser_window_cocoa.mm b/chrome/browser/ui/cocoa/browser_window_cocoa.mm new file mode 100644 index 0000000..003fec7 --- /dev/null +++ b/chrome/browser/ui/cocoa/browser_window_cocoa.mm @@ -0,0 +1,638 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/cocoa/browser_window_cocoa.h" + +#include "app/l10n_util_mac.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/sys_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/download/download_shelf.h" +#include "chrome/browser/global_keyboard_shortcuts_mac.h" +#include "chrome/browser/page_info_window.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sidebar/sidebar_container.h" +#include "chrome/browser/sidebar/sidebar_manager.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents_wrapper.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#import "chrome/browser/ui/cocoa/browser_window_controller.h" +#import "chrome/browser/ui/cocoa/bug_report_window_controller.h" +#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" +#import "chrome/browser/ui/cocoa/clear_browsing_data_controller.h" +#import "chrome/browser/ui/cocoa/collected_cookies_mac.h" +#import "chrome/browser/ui/cocoa/content_settings_dialog_controller.h" +#import "chrome/browser/ui/cocoa/download/download_shelf_controller.h" +#import "chrome/browser/ui/cocoa/edit_search_engine_cocoa_controller.h" +#import "chrome/browser/ui/cocoa/html_dialog_window_controller.h" +#import "chrome/browser/ui/cocoa/import_settings_dialog.h" +#import "chrome/browser/ui/cocoa/keyword_editor_cocoa_controller.h" +#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" +#import "chrome/browser/ui/cocoa/nsmenuitem_additions.h" +#include "chrome/browser/ui/cocoa/repost_form_warning_mac.h" +#include "chrome/browser/ui/cocoa/restart_browser.h" +#include "chrome/browser/ui/cocoa/status_bubble_mac.h" +#include "chrome/browser/ui/cocoa/task_manager_mac.h" +#import "chrome/browser/ui/cocoa/theme_install_bubble_view.h" +#import "chrome/browser/ui/cocoa/toolbar_controller.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/native_web_keyboard_event.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "gfx/rect.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" + +BrowserWindowCocoa::BrowserWindowCocoa(Browser* browser, + BrowserWindowController* controller, + NSWindow* window) + : browser_(browser), + controller_(controller), + confirm_close_factory_(browser) { + // This pref applies to all windows, so all must watch for it. + registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::SIDEBAR_CHANGED, + NotificationService::AllSources()); +} + +BrowserWindowCocoa::~BrowserWindowCocoa() { +} + +void BrowserWindowCocoa::Show() { + // The Browser associated with this browser window must become the active + // browser at the time |Show()| is called. This is the natural behaviour under + // Windows, but |-makeKeyAndOrderFront:| won't send |-windowDidBecomeMain:| + // until we return to the runloop. Therefore any calls to + // |BrowserList::GetLastActive()| (for example, in bookmark_util), will return + // the previous browser instead if we don't explicitly set it here. + BrowserList::SetLastActive(browser_); + + [window() makeKeyAndOrderFront:controller_]; +} + +void BrowserWindowCocoa::SetBounds(const gfx::Rect& bounds) { + NSRect cocoa_bounds = NSMakeRect(bounds.x(), 0, bounds.width(), + bounds.height()); + // Flip coordinates based on the primary screen. + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + cocoa_bounds.origin.y = + [screen frame].size.height - bounds.height() - bounds.y(); + + [window() setFrame:cocoa_bounds display:YES]; +} + +// Callers assume that this doesn't immediately delete the Browser object. +// The controller implementing the window delegate methods called from +// |-performClose:| must take precautions to ensure that. +void BrowserWindowCocoa::Close() { + // If there is an overlay window, we contain a tab being dragged between + // windows. Don't hide the window as it makes the UI extra confused. We can + // still close the window, as that will happen when the drag completes. + if ([controller_ overlayWindow]) { + [controller_ deferPerformClose]; + } else { + // Make sure we hide the window immediately. Even though performClose: + // calls orderOut: eventually, it leaves the window on-screen long enough + // that we start to see tabs shutting down. http://crbug.com/23959 + // TODO(viettrungluu): This is kind of bad, since |-performClose:| calls + // |-windowShouldClose:| (on its delegate, which is probably the + // controller) which may return |NO| causing the window to not be closed, + // thereby leaving a hidden window. In fact, our window-closing procedure + // involves a (indirect) recursion on |-performClose:|, which is also bad. + [window() orderOut:controller_]; + [window() performClose:controller_]; + } +} + +void BrowserWindowCocoa::Activate() { + [controller_ activate]; +} + +void BrowserWindowCocoa::Deactivate() { + // TODO(jcivelli): http://crbug.com/51364 Implement me. + NOTIMPLEMENTED(); +} + +void BrowserWindowCocoa::FlashFrame() { + [NSApp requestUserAttention:NSInformationalRequest]; +} + +bool BrowserWindowCocoa::IsActive() const { + return [window() isKeyWindow]; +} + +gfx::NativeWindow BrowserWindowCocoa::GetNativeHandle() { + return window(); +} + +BrowserWindowTesting* BrowserWindowCocoa::GetBrowserWindowTesting() { + return NULL; +} + +StatusBubble* BrowserWindowCocoa::GetStatusBubble() { + return [controller_ statusBubble]; +} + +void BrowserWindowCocoa::SelectedTabToolbarSizeChanged(bool is_animating) { + // According to beng, this is an ugly method that comes from the days when the + // download shelf was a ChromeView attached to the TabContents, and as its + // size changed via animation it notified through TCD/etc to the browser view + // to relayout for each tick of the animation. We don't need anything of the + // sort on Mac. +} + +void BrowserWindowCocoa::UpdateTitleBar() { + NSString* newTitle = + base::SysUTF16ToNSString(browser_->GetWindowTitleForCurrentTab()); + + // Work around Cocoa bug: if a window changes title during the tracking of the + // Window menu it doesn't display well and the constant re-sorting of the list + // makes it difficult for the user to pick the desired window. Delay window + // title updates until the default run-loop mode. + + if (pending_window_title_.get()) + [[NSRunLoop currentRunLoop] + cancelPerformSelector:@selector(setTitle:) + target:window() + argument:pending_window_title_.get()]; + + pending_window_title_.reset([newTitle copy]); + [[NSRunLoop currentRunLoop] + performSelector:@selector(setTitle:) + target:window() + argument:newTitle + order:0 + modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; +} + +void BrowserWindowCocoa::ShelfVisibilityChanged() { + // Mac doesn't yet support showing the bookmark bar at a different size on + // the new tab page. When it does, this method should attempt to relayout the + // bookmark bar/extension shelf as their preferred height may have changed. + // http://crbug.com/43346 +} + +void BrowserWindowCocoa::UpdateDevTools() { + [controller_ updateDevToolsForContents: + browser_->GetSelectedTabContents()]; +} + +void BrowserWindowCocoa::UpdateLoadingAnimations(bool should_animate) { + // Do nothing on Mac. +} + +void BrowserWindowCocoa::SetStarredState(bool is_starred) { + [controller_ setStarredState:is_starred ? YES : NO]; +} + +gfx::Rect BrowserWindowCocoa::GetRestoredBounds() const { + // Flip coordinates based on the primary screen. + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + NSRect frame = [controller_ regularWindowFrame]; + gfx::Rect bounds(frame.origin.x, 0, frame.size.width, frame.size.height); + bounds.set_y([screen frame].size.height - frame.origin.y - frame.size.height); + return bounds; +} + +bool BrowserWindowCocoa::IsMaximized() const { + return [window() isZoomed]; +} + +void BrowserWindowCocoa::SetFullscreen(bool fullscreen) { + [controller_ setFullscreen:fullscreen]; +} + +bool BrowserWindowCocoa::IsFullscreen() const { + return !![controller_ isFullscreen]; +} + +bool BrowserWindowCocoa::IsFullscreenBubbleVisible() const { + return false; +} + +void BrowserWindowCocoa::ConfirmAddSearchProvider( + const TemplateURL* template_url, + Profile* profile) { + // The controller will release itself when the window closes. + EditSearchEngineCocoaController* editor = + [[EditSearchEngineCocoaController alloc] initWithProfile:profile + delegate:NULL + templateURL:template_url]; + [NSApp beginSheet:[editor window] + modalForWindow:window() + modalDelegate:controller_ + didEndSelector:@selector(sheetDidEnd:returnCode:context:) + contextInfo:NULL]; +} + +LocationBar* BrowserWindowCocoa::GetLocationBar() const { + return [controller_ locationBarBridge]; +} + +void BrowserWindowCocoa::SetFocusToLocationBar(bool select_all) { + [controller_ focusLocationBar:select_all ? YES : NO]; +} + +void BrowserWindowCocoa::UpdateReloadStopState(bool is_loading, bool force) { + [controller_ setIsLoading:is_loading force:force]; +} + +void BrowserWindowCocoa::UpdateToolbar(TabContentsWrapper* contents, + bool should_restore_state) { + [controller_ updateToolbarWithContents:contents->tab_contents() + shouldRestoreState:should_restore_state ? YES : NO]; +} + +void BrowserWindowCocoa::FocusToolbar() { + // Not needed on the Mac. +} + +void BrowserWindowCocoa::FocusAppMenu() { + // Chrome uses the standard Mac OS X menu bar, so this isn't needed. +} + +void BrowserWindowCocoa::RotatePaneFocus(bool forwards) { + // Not needed on the Mac. +} + +void BrowserWindowCocoa::FocusBookmarksToolbar() { + // Not needed on the Mac. +} + +void BrowserWindowCocoa::FocusChromeOSStatus() { + // Not needed on the Mac. +} + +bool BrowserWindowCocoa::IsBookmarkBarVisible() const { + return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +bool BrowserWindowCocoa::IsBookmarkBarAnimating() const { + return [controller_ isBookmarkBarAnimating]; +} + +bool BrowserWindowCocoa::IsToolbarVisible() const { + return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) || + browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR); +} + +// This is called from Browser, which in turn is called directly from +// a menu option. All we do here is set a preference. The act of +// setting the preference sends notifications to all windows who then +// know what to do. +void BrowserWindowCocoa::ToggleBookmarkBar() { + bookmark_utils::ToggleWhenVisible(browser_->profile()); +} + +void BrowserWindowCocoa::AddFindBar( + FindBarCocoaController* find_bar_cocoa_controller) { + return [controller_ addFindBar:find_bar_cocoa_controller]; +} + +views::Window* BrowserWindowCocoa::ShowAboutChromeDialog() { + NOTIMPLEMENTED(); + return NULL; +} + +void BrowserWindowCocoa::ShowUpdateChromeDialog() { + restart_browser::RequestRestart(nil); +} + +void BrowserWindowCocoa::ShowTaskManager() { + TaskManagerMac::Show(); +} + +void BrowserWindowCocoa::ShowBookmarkBubble(const GURL& url, + bool already_bookmarked) { + [controller_ showBookmarkBubbleForURL:url + alreadyBookmarked:(already_bookmarked ? YES : NO)]; +} + +bool BrowserWindowCocoa::IsDownloadShelfVisible() const { + return [controller_ isDownloadShelfVisible] != NO; +} + +DownloadShelf* BrowserWindowCocoa::GetDownloadShelf() { + DownloadShelfController* shelfController = [controller_ downloadShelf]; + return [shelfController bridge]; +} + +void BrowserWindowCocoa::ShowReportBugDialog() { + TabContents* current_tab = browser_->GetSelectedTabContents(); + if (current_tab && current_tab->controller().GetActiveEntry()) { + browser_->ShowBrokenPageTab(current_tab); + } +} + +void BrowserWindowCocoa::ShowClearBrowsingDataDialog() { + [ClearBrowsingDataController + showClearBrowsingDialogForProfile:browser_->profile()]; +} + +void BrowserWindowCocoa::ShowImportDialog() { + [ImportSettingsDialogController + showImportSettingsDialogForProfile:browser_->profile()]; +} + +void BrowserWindowCocoa::ShowSearchEnginesDialog() { + [KeywordEditorCocoaController showKeywordEditor:browser_->profile()]; +} + +void BrowserWindowCocoa::ShowPasswordManager() { + NOTIMPLEMENTED(); +} + +void BrowserWindowCocoa::ShowRepostFormWarningDialog( + TabContents* tab_contents) { + RepostFormWarningMac::Create(GetNativeHandle(), tab_contents); +} + +void BrowserWindowCocoa::ShowContentSettingsWindow( + ContentSettingsType settings_type, + Profile* profile) { + [ContentSettingsDialogController showContentSettingsForType:settings_type + profile:profile]; +} + +void BrowserWindowCocoa::ShowCollectedCookiesDialog(TabContents* tab_contents) { + // Deletes itself on close. + new CollectedCookiesMac(GetNativeHandle(), tab_contents); +} + +void BrowserWindowCocoa::ShowProfileErrorDialog(int message_id) { + scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]); + [alert addButtonWithTitle:l10n_util::GetNSStringWithFixup(IDS_OK)]; + [alert setMessageText:l10n_util::GetNSStringWithFixup(IDS_PRODUCT_NAME)]; + [alert setInformativeText:l10n_util::GetNSStringWithFixup(message_id)]; + [alert setAlertStyle:NSWarningAlertStyle]; + [alert runModal]; +} + +void BrowserWindowCocoa::ShowThemeInstallBubble() { + ThemeInstallBubbleView::Show(window()); +} + +// We allow closing the window here since the real quit decision on Mac is made +// in [AppController quit:]. +void BrowserWindowCocoa::ConfirmBrowserCloseWithPendingDownloads() { + // Call InProgressDownloadResponse asynchronously to avoid a crash when the + // browser window is closed here (http://crbug.com/44454). + MessageLoop::current()->PostTask( + FROM_HERE, + confirm_close_factory_.NewRunnableMethod( + &Browser::InProgressDownloadResponse, + true)); +} + +void BrowserWindowCocoa::ShowHTMLDialog(HtmlDialogUIDelegate* delegate, + gfx::NativeWindow parent_window) { + [HtmlDialogWindowController showHtmlDialog:delegate + profile:browser_->profile()]; +} + +void BrowserWindowCocoa::UserChangedTheme() { + [controller_ userChangedTheme]; +} + +int BrowserWindowCocoa::GetExtraRenderViewHeight() const { + // Currently this is only used on linux. + return 0; +} + +void BrowserWindowCocoa::TabContentsFocused(TabContents* tab_contents) { + NOTIMPLEMENTED(); +} + +void BrowserWindowCocoa::ShowPageInfo(Profile* profile, + const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history) { + browser::ShowPageInfoBubble(window(), profile, url, ssl, show_history); +} + +void BrowserWindowCocoa::ShowAppMenu() { + // No-op. Mac doesn't support showing the menus via alt keys. +} + +bool BrowserWindowCocoa::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { + if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) + return false; + + DCHECK(event.os_event != NULL); + int id = GetCommandId(event); + if (id == -1) + return false; + + if (browser_->IsReservedCommand(id)) + return HandleKeyboardEventInternal(event.os_event); + + DCHECK(is_keyboard_shortcut != NULL); + *is_keyboard_shortcut = true; + + return false; +} + +void BrowserWindowCocoa::HandleKeyboardEvent( + const NativeWebKeyboardEvent& event) { + if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) + return; + + DCHECK(event.os_event != NULL); + HandleKeyboardEventInternal(event.os_event); +} + +@interface MenuWalker : NSObject ++ (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key + menu:(NSMenu*)menu; +@end + +@implementation MenuWalker ++ (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key + menu:(NSMenu*)menu { + NSMenuItem* result = nil; + + for (NSMenuItem *item in [menu itemArray]) { + NSMenu* submenu = [item submenu]; + if (submenu) { + if (submenu != [NSApp servicesMenu]) + result = [self itemForKeyEquivalent:key + menu:submenu]; + } else if ([item cr_firesForKeyEvent:key]) { + result = item; + } + + if (result) + break; + } + + return result; +} +@end + +int BrowserWindowCocoa::GetCommandId(const NativeWebKeyboardEvent& event) { + if ([event.os_event type] != NSKeyDown) + return -1; + + // Look in menu. + NSMenuItem* item = [MenuWalker itemForKeyEquivalent:event.os_event + menu:[NSApp mainMenu]]; + + if (item && [item action] == @selector(commandDispatch:) && [item tag] > 0) + return [item tag]; + + // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items + // that do not correspond to IDC_ constants need no special treatment however, + // as they can't be blacklisted in |Browser::IsReservedCommand()| anyhow. + if (item && [item action] == @selector(performClose:)) + return IDC_CLOSE_WINDOW; + + // "Exit" doesn't use the |commandDispatch:| mechanism either. + if (item && [item action] == @selector(terminate:)) + return IDC_EXIT; + + // Look in secondary keyboard shortcuts. + NSUInteger modifiers = [event.os_event modifierFlags]; + const bool cmdKey = (modifiers & NSCommandKeyMask) != 0; + const bool shiftKey = (modifiers & NSShiftKeyMask) != 0; + const bool cntrlKey = (modifiers & NSControlKeyMask) != 0; + const bool optKey = (modifiers & NSAlternateKeyMask) != 0; + const int keyCode = [event.os_event keyCode]; + const unichar keyChar = KeyCharacterForEvent(event.os_event); + + int cmdNum = CommandForWindowKeyboardShortcut( + cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar); + if (cmdNum != -1) + return cmdNum; + + cmdNum = CommandForBrowserKeyboardShortcut( + cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar); + if (cmdNum != -1) + return cmdNum; + + return -1; +} + +bool BrowserWindowCocoa::HandleKeyboardEventInternal(NSEvent* event) { + ChromeEventProcessingWindow* event_window = + static_cast<ChromeEventProcessingWindow*>(window()); + DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]); + + // Do not fire shortcuts on key up. + if ([event type] == NSKeyDown) { + // Send the event to the menu before sending it to the browser/window + // shortcut handling, so that if a user configures cmd-left to mean + // "previous tab", it takes precedence over the built-in "history back" + // binding. Other than that, the |-redispatchKeyEvent:| call would take care + // of invoking the original menu item shortcut as well. + + if ([[NSApp mainMenu] performKeyEquivalent:event]) + return true; + + if ([event_window handleExtraBrowserKeyboardShortcut:event]) + return true; + + if ([event_window handleExtraWindowKeyboardShortcut:event]) + return true; + + if ([event_window handleDelayedWindowKeyboardShortcut:event]) + return true; + } + + return [event_window redispatchKeyEvent:event]; +} + +void BrowserWindowCocoa::ShowCreateWebAppShortcutsDialog( + TabContents* tab_contents) { + NOTIMPLEMENTED(); +} + +void BrowserWindowCocoa::ShowCreateChromeAppShortcutsDialog( + Profile* profile, const Extension* app) { + NOTIMPLEMENTED(); +} + +void BrowserWindowCocoa::Cut() { + [NSApp sendAction:@selector(cut:) to:nil from:nil]; +} + +void BrowserWindowCocoa::Copy() { + [NSApp sendAction:@selector(copy:) to:nil from:nil]; +} + +void BrowserWindowCocoa::Paste() { + [NSApp sendAction:@selector(paste:) to:nil from:nil]; +} + +void BrowserWindowCocoa::ToggleTabStripMode() { + [controller_ toggleTabStripDisplayMode]; +} + +void BrowserWindowCocoa::OpenTabpose() { + [controller_ openTabpose]; +} + +void BrowserWindowCocoa::PrepareForInstant() { + // TODO: implement fade as done on windows. +} + +void BrowserWindowCocoa::ShowInstant(TabContents* preview_contents) { + [controller_ showInstant:preview_contents]; +} + +void BrowserWindowCocoa::HideInstant() { + [controller_ hideInstant]; +} + +gfx::Rect BrowserWindowCocoa::GetInstantBounds() { + // Flip coordinates based on the primary screen. + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + NSRect monitorFrame = [screen frame]; + NSRect frame = [controller_ instantFrame]; + gfx::Rect bounds(NSRectToCGRect(frame)); + bounds.set_y(NSHeight(monitorFrame) - bounds.y() - bounds.height()); + return bounds; +} + +void BrowserWindowCocoa::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + // Only the key window gets a direct toggle from the menu. + // Other windows hear about it from the notification. + case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED: + [controller_ updateBookmarkBarVisibilityWithAnimation:YES]; + break; + case NotificationType::SIDEBAR_CHANGED: + UpdateSidebarForContents( + Details<SidebarContainer>(details)->tab_contents()); + break; + default: + NOTREACHED(); // we don't ask for anything else! + break; + } +} + +void BrowserWindowCocoa::DestroyBrowser() { + [controller_ destroyBrowser]; + + // at this point the controller is dead (autoreleased), so + // make sure we don't try to reference it any more. +} + +NSWindow* BrowserWindowCocoa::window() const { + return [controller_ window]; +} + +void BrowserWindowCocoa::UpdateSidebarForContents(TabContents* tab_contents) { + if (tab_contents == browser_->GetSelectedTabContents()) { + [controller_ updateSidebarForContents:tab_contents]; + } +} |