diff options
author | thakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-11 22:03:17 +0000 |
---|---|---|
committer | thakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-11 22:03:17 +0000 |
commit | a9e8afc41dd0adfe4ac5900f2b73ff259276e2bd (patch) | |
tree | 7c82250d47888a4057e3048bfc7f54c07027fdb2 /chrome/browser/cocoa | |
parent | e819b55cf062822a23280b4da7760abfd2a86e64 (diff) | |
download | chromium_src-a9e8afc41dd0adfe4ac5900f2b73ff259276e2bd.zip chromium_src-a9e8afc41dd0adfe4ac5900f2b73ff259276e2bd.tar.gz chromium_src-a9e8afc41dd0adfe4ac5900f2b73ff259276e2bd.tar.bz2 |
Add support for constrained windows on os x, based on Avi's GTMWindowSheetController. Add carpet bombing dialog as first per-tab sheet.
Depends http://codereview.appspot.com/105064 .
The main issue with this patch is that GTMWindowSheetController doesn't provide an api to move sheets between windows, so this CL disables tab dragging for tabs with sheets, and fullscreen mode for windows with sheets. We can fix this later.
Other stuff that should be done at some point, but not now:
* Open/Save panels should be per-tab
* Need an ui test that goes to page, then page with sheet, then hit back, forward, reload.
* Bookmark sheets should not be sheets but in a separate window
BUG=14666
TEST=Go to skypher.com/SkyLined/Repro/Chrome/carpet bombing/repro.html , a per-window sheet should appear. Things to test with this dialog:
* Hitting cmd-q while a sheet is open in any tab should not quit but instead focus the sheet.
* Hitting cmd-w while a sheet is open in any tab should not close the window but instead focus the sheet.
* Dragging a tab with a sheet should move the window (and keep the tab visible), not detach the tab.
* Going fullscreen should be disabled for windows with open tabs.
* When a per-tab sheet is open in a non-active tab, it shouldn't steal the focus, i.e. going to the page above, then hitting cmd-t, and then hitting cmd-l should work.
* Closing a non-frontmost tab with a per-tab sheet shouldn't crash.
* Going to the url above and quickly opening a new tab, so that the sheet opens while its tab is not front-most should work (sheet should display only when you switch back to the tab with the sheet).
* Go to google.com, then to skypher.com/SkyLined/Repro/Chrome/carpet bombing/repro.html ,
hit "backward" with open sheet, hit forward, focus location bar, hit enter. This shouldn't crash.
* Hitting escape should dismiss the sheet
* Hitting enter should confirm the sheet.
Review URL: http://codereview.chromium.org/159780
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23091 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r-- | chrome/browser/cocoa/browser_window_cocoa.h | 3 | ||||
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.h | 13 | ||||
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.mm | 60 | ||||
-rw-r--r-- | chrome/browser/cocoa/constrained_window_mac.h | 135 | ||||
-rw-r--r-- | chrome/browser/cocoa/constrained_window_mac.mm | 87 | ||||
-rw-r--r-- | chrome/browser/cocoa/download_request_dialog_delegate_mac.h | 43 | ||||
-rw-r--r-- | chrome/browser/cocoa/download_request_dialog_delegate_mac.mm | 105 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_controller.h | 17 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_controller.mm | 102 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_view.h | 2 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_view.mm | 54 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_window_controller.h | 5 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_window_controller.mm | 18 |
13 files changed, 618 insertions, 26 deletions
diff --git a/chrome/browser/cocoa/browser_window_cocoa.h b/chrome/browser/cocoa/browser_window_cocoa.h index 688b658..c93b733 100644 --- a/chrome/browser/cocoa/browser_window_cocoa.h +++ b/chrome/browser/cocoa/browser_window_cocoa.h @@ -90,6 +90,9 @@ class BrowserWindowCocoa : public BrowserWindow, // Adds the given FindBar cocoa controller to this browser window. void AddFindBar(FindBarCocoaController* find_bar_cocoa_controller); + // Returns the cocoa-world BrowserWindowController + BrowserWindowController* cocoa_controller() { return controller_; } + protected: virtual void DestroyBrowser(); diff --git a/chrome/browser/cocoa/browser_window_controller.h b/chrome/browser/cocoa/browser_window_controller.h index 5ebbbf0..6713044 100644 --- a/chrome/browser/cocoa/browser_window_controller.h +++ b/chrome/browser/cocoa/browser_window_controller.h @@ -22,8 +22,10 @@ class Browser; class BrowserWindow; class BrowserWindowCocoa; +class ConstrainedWindowMac; @class DownloadShelfController; @class FindBarCocoaController; +@class GTMWindowSheetController; @class InfoBarContainerController; class LocationBar; class StatusBubble; @@ -137,6 +139,17 @@ class TabStripModelObserverBridge; // Delegate method for the status bubble to query about its vertical offset. - (float)verticalOffsetForStatusBubble; +// Returns the (lazily created) window sheet controller of this window. Used +// for the per-tab sheets. +- (GTMWindowSheetController*)sheetController; + +// Checks if there are any tabs with sheets open, and if so, raises one of +// the tabs with a sheet and returns NO. +- (BOOL)shouldCloseWithOpenPerTabSheets; + +- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window; +- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window; + @end diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index aca4ef1..56ff676 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -62,6 +62,7 @@ const int kWindowGradientHeight = 24; - (void)setBottomCornerRounded:(BOOL)rounded; - (NSRect)_growBoxRect; + @end @@ -238,7 +239,7 @@ willPositionSheet:(NSWindow*)sheet } // Called when the window meets the criteria to be closed (ie, -// |-windowShoudlClose:| returns YES). We must be careful to preserve the +// |-windowShouldClose:| returns YES). We must be careful to preserve the // semantics of BrowserWindow::Close() and not call the Browser's dtor directly // from this method. - (void)windowWillClose:(NSNotification*)notification { @@ -253,6 +254,28 @@ willPositionSheet:(NSWindow*)sheet afterDelay:0]; } +// Checks if there are any tabs with sheets open, and if so, raises one of +// the tabs with a sheet and returns NO. +- (BOOL)shouldCloseWithOpenPerTabSheets { + // This is O(n_open_sheets * n_tabs), i.e. O(n**2). Since people probably + // won't have 100 tabs open, and not every tab will have a sheet, this is ok. + for (NSView* view in + [[tabStripController_ sheetController] viewsWithAttachedSheets]) { + [tabStripController_ gtm_systemRequestsVisibilityForView:view]; + return NO; + } + + return YES; +} + +- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window { + [tabStripController_ attachConstrainedWindow:window]; +} + +- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window { + [tabStripController_ removeConstrainedWindow:window]; +} + // Called when the user wants to close a window or from the shutdown process. // The Browser object is in control of whether or not we're allowed to close. It // may defer closing due to several states, such as onUnload handlers needing to @@ -260,6 +283,11 @@ willPositionSheet:(NSWindow*)sheet // required to get us to the closing state and (by watching for all the tabs // going away) will again call to close the window when it's finally ready. - (BOOL)windowShouldClose:(id)sender { + // Do not close a window with open sheets, as required by + // GTMWindowSheetController. + if (![self shouldCloseWithOpenPerTabSheets]) + return NO; + // Disable updates while closing all tabs to avoid flickering. base::ScopedNSDisableScreenUpdates disabler; // Give beforeunload handlers the chance to cancel the close before we hide @@ -420,7 +448,14 @@ willPositionSheet:(NSWindow*)sheet if (oldState != newState) [item setState:newState]; } +} +- (BOOL)supportsFullscreen { + // TODO(avi, thakis): GTMWindowSheetController has no api to move + // tabsheets between windows. Until then, we have to prevent having to + // move a tabsheet between windows, e.g. no fullscreen toggling + NSArray* a = [[tabStripController_ sheetController] viewsWithAttachedSheets]; + return [a count] == 0; } // Called to validate menu and toolbar items when this window is key. All the @@ -452,6 +487,9 @@ willPositionSheet:(NSWindow*)sheet // command updater doesn't know. enable &= browser_->CanRestoreTab(); break; + case IDC_FULLSCREEN: + enable &= [self supportsFullscreen]; + break; } // If the item is toggleable, find its toggle state and @@ -507,6 +545,10 @@ willPositionSheet:(NSWindow*)sheet return offset; } +- (GTMWindowSheetController*)sheetController { + return [tabStripController_ sheetController]; +} + - (LocationBar*)locationBar { return [toolbarController_ locationBar]; } @@ -784,6 +826,9 @@ willPositionSheet:(NSWindow*)sheet } - (void)setFullscreen:(BOOL)fullscreen { + if (![self supportsFullscreen]) + return; + fullscreen_ = fullscreen; if (fullscreen) { // Move content to a new fullscreen window @@ -861,25 +906,26 @@ willPositionSheet:(NSWindow*)sheet // TabContents. #if 0 // TODO(pinkerton):Update as more things become window-specific - contents_container_->SetTabContents(new_contents); + contents_container_->SetTabContents(newContents); #endif // Update all the UI bits. windowShim_->UpdateTitleBar(); #if 0 // TODO(pinkerton):Update as more things become window-specific - toolbar_->SetProfile(new_contents->profile()); - UpdateToolbar(new_contents, true); - UpdateUIForContents(new_contents); + toolbar_->SetProfile(newContents->profile()); + UpdateToolbar(newContents, true); + UpdateUIForContents(newContents); #endif } - (void)tabChangedWithContents:(TabContents*)contents atIndex:(NSInteger)index loadingOnly:(BOOL)loading { - // Update titles if this is the currently selected tab. - if (index == browser_->tabstrip_model()->selected_index()) + if (index == browser_->tabstrip_model()->selected_index()) { + // Update titles if this is the currently selected tab. windowShim_->UpdateTitleBar(); + } } - (void)userChangedTheme { diff --git a/chrome/browser/cocoa/constrained_window_mac.h b/chrome/browser/cocoa/constrained_window_mac.h new file mode 100644 index 0000000..48e0a9f --- /dev/null +++ b/chrome/browser/cocoa/constrained_window_mac.h @@ -0,0 +1,135 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_COCOA_CONSTRAINED_WINDOW_MAC_H_ +#define CHROME_BROWSER_COCOA_CONSTRAINED_WINDOW_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include "chrome/browser/tab_contents/constrained_window.h" + +#include "base/basictypes.h" +#include "base/scoped_nsobject.h" + +@class BrowserWindowController; +@class GTMWindowSheetController; +@class NSView; +@class NSWindow; +class TabContents; + +// Base class for constrained dialog delegates. Never inherit from this +// directly. +class ConstrainedWindowMacDelegate { + public: + ConstrainedWindowMacDelegate() : is_sheet_open_(false) { } + virtual ~ConstrainedWindowMacDelegate(); + + // Tells the delegate to either delete itself or set up a task to delete + // itself later. Note that you MUST close the sheet belonging to your delegate + // in this method. + virtual void DeleteDelegate() = 0; + + // Called by the tab controller, you do not need to do anything yourself + // with this method. + virtual void RunSheet(GTMWindowSheetController* sheetController, + NSView* view) = 0; + protected: + // Returns true if this delegate's sheet is currently showing. + bool is_sheet_open() { return is_sheet_open_; } + + private: + bool is_sheet_open_; + void set_sheet_open(bool is_open) { is_sheet_open_ = is_open; } + friend class ConstrainedWindowMac; +}; + +// Subclass this for a dialog delegate that displays a system sheet such as +// an NSAlert, an open or save file panel, etc. +class ConstrainedWindowMacDelegateSystemSheet + : public ConstrainedWindowMacDelegate { + public: + ConstrainedWindowMacDelegateSystemSheet(id delegate, SEL didEndSelector) + : systemSheet_(nil), + delegate_([delegate retain]), + didEndSelector_(didEndSelector) { } + + protected: + void set_sheet(id sheet) { systemSheet_.reset([sheet retain]); } + id sheet() { return systemSheet_; } + + private: + virtual void RunSheet(GTMWindowSheetController* sheetController, + NSView* view); + scoped_nsobject<id> systemSheet_; + scoped_nsobject<id> delegate_; + SEL didEndSelector_; +}; + +// Subclass this for a dialog delegate that displays a custom sheet, e.g. loaded +// from a nib file. +class ConstrainedWindowMacDelegateCustomSheet + : public ConstrainedWindowMacDelegate { + public: + ConstrainedWindowMacDelegateCustomSheet( + NSWindow* customSheet, id delegate, SEL didEndSelector) + : customSheet_(nil), + delegate_([delegate retain]), + didEndSelector_(didEndSelector) { } + + protected: + void set_sheet(NSWindow* sheet) { customSheet_.reset([sheet retain]); } + NSWindow* sheet() { return customSheet_; } + + private: + virtual void RunSheet(GTMWindowSheetController* sheetController, + NSView* view); + scoped_nsobject<NSWindow> customSheet_; + scoped_nsobject<id> delegate_; + SEL didEndSelector_; +}; + +// Constrained window implementation for the Mac port. A constrained window +// is a per-tab sheet on OS X. +// +// Constrained windows work slightly differently on OS X than on the other +// platforms: +// 1. A constrained window is bound to both a tab and window on OS X. +// 2. The delegate is responsible for closing the sheet again when it is +// deleted. +class ConstrainedWindowMac : public ConstrainedWindow { + public: + virtual ~ConstrainedWindowMac(); + + // Overridden from ConstrainedWindow: + virtual void CloseConstrainedWindow(); + + // Returns the TabContents that constrains this Constrained Window. + TabContents* owner() const { return owner_; } + + // Returns the window's delegate. + ConstrainedWindowMacDelegate* delegate() { return delegate_; } + + // Makes the constrained window visible, if it is not yet visible. + void Realize(BrowserWindowController* controller); + + private: + friend class ConstrainedWindow; + + ConstrainedWindowMac(TabContents* owner, + ConstrainedWindowMacDelegate* delegate); + + // The TabContents that owns and constrains this ConstrainedWindow. + TabContents* owner_; + + // Delegate that provides the contents of this constrained window. + ConstrainedWindowMacDelegate* delegate_; + + // Controller of the window that contains this sheet. + BrowserWindowController* controller_; + + DISALLOW_COPY_AND_ASSIGN(ConstrainedWindowMac); +}; + +#endif // CHROME_BROWSER_COCOA_CONSTRAINED_WINDOW_MAC_H_ + diff --git a/chrome/browser/cocoa/constrained_window_mac.mm b/chrome/browser/cocoa/constrained_window_mac.mm new file mode 100644 index 0000000..67d1030 --- /dev/null +++ b/chrome/browser/cocoa/constrained_window_mac.mm @@ -0,0 +1,87 @@ +// Copyright (c) 2009 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/cocoa/constrained_window_mac.h" + +#import "chrome/browser/cocoa/browser_window_controller.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#import "third_party/GTM/AppKit/GTMWindowSheetController.h" + +ConstrainedWindowMacDelegate::~ConstrainedWindowMacDelegate() {} + +void ConstrainedWindowMacDelegateSystemSheet::RunSheet( + GTMWindowSheetController* sheetController, + NSView* view) { + NSArray* params = [NSArray arrayWithObjects: + [NSNull null], // window, must be [NSNull null] + delegate_.get(), + [NSValue valueWithPointer:didEndSelector_], + [NSValue valueWithPointer:NULL], // context info for didEndSelector_. + nil]; + [sheetController beginSystemSheet:systemSheet_ + modalForView:view + withParameters:params]; +} + +void ConstrainedWindowMacDelegateCustomSheet::RunSheet( + GTMWindowSheetController* sheetController, + NSView* view) { + [sheetController beginSheet:customSheet_.get() + modalForView:view modalDelegate:delegate_.get() + didEndSelector:didEndSelector_ + contextInfo:NULL]; +} + +// static +ConstrainedWindow* ConstrainedWindow::CreateConstrainedDialog( + TabContents* parent, + ConstrainedWindowMacDelegate* delegate) { + return new ConstrainedWindowMac(parent, delegate); +} + +ConstrainedWindowMac::ConstrainedWindowMac( + TabContents* owner, ConstrainedWindowMacDelegate* delegate) + : owner_(owner), + delegate_(delegate), + controller_(nil) { + DCHECK(owner); + DCHECK(delegate); + + // The TabContents only has a native window if it is currently visible. In + // this case, open the sheet now. Else, Realize() will be called later, when + // our tab becomes visible. + NSWindow* browserWindow = owner_->view()->GetTopLevelNativeWindow(); + NSWindowController* controller = [browserWindow windowController]; + if (controller != nil) { + DCHECK([controller isKindOfClass:[BrowserWindowController class]]); + Realize(static_cast<BrowserWindowController*>(controller)); + } +} + +ConstrainedWindowMac::~ConstrainedWindowMac() {} + +void ConstrainedWindowMac::CloseConstrainedWindow() { + // Note: controller_ can be `nil` here if the sheet was never realized. That's + // ok. + [controller_ removeConstrainedWindow:this]; + delegate_->DeleteDelegate(); + owner_->WillClose(this); + + delete this; +} + +void ConstrainedWindowMac::Realize(BrowserWindowController* controller) { + if (controller_ != nil) { + DCHECK(controller_ == controller); + return; + } + DCHECK(controller != nil); + + // Remember the controller we're adding ourselves to, so that we can later + // remove us from it. + controller_ = controller; + [controller_ attachConstrainedWindow:this]; + delegate_->set_sheet_open(true); +} diff --git a/chrome/browser/cocoa/download_request_dialog_delegate_mac.h b/chrome/browser/cocoa/download_request_dialog_delegate_mac.h new file mode 100644 index 0000000..f12ae19 --- /dev/null +++ b/chrome/browser/cocoa/download_request_dialog_delegate_mac.h @@ -0,0 +1,43 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_DIALOG_DELEGATE_MAC_H_ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_DIALOG_DELEGATE_MAC_H_ + +#include "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/cocoa/constrained_window_mac.h" +#include "chrome/browser/download/download_request_dialog_delegate.h" +#include "chrome/browser/download/download_request_manager.h" + +@class SheetCallback; +class TabContents; + +class DownloadRequestDialogDelegateMac : public DownloadRequestDialogDelegate, + public ConstrainedWindowMacDelegateSystemSheet { + public: + DownloadRequestDialogDelegateMac(TabContents* tab, + DownloadRequestManager::TabDownloadState* host); + + // Implement ConstrainedWindowMacDelegateSystemSheet. + virtual void DeleteDelegate(); + + // Called when the sheet is done. + void SheetDidEnd(int returnCode); + + private: + // DownloadRequestDialogDelegate methods. + virtual void CloseWindow(); + + // The ConstrainedWindow that is hosting our DownloadRequestDialog. + ConstrainedWindow* window_; + + // Has a button on the sheet been clicked? + bool responded_; + + DISALLOW_COPY_AND_ASSIGN(DownloadRequestDialogDelegateMac); +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_DIALOG_DELEGATE_MAC_H_ + diff --git a/chrome/browser/cocoa/download_request_dialog_delegate_mac.mm b/chrome/browser/cocoa/download_request_dialog_delegate_mac.mm new file mode 100644 index 0000000..3a34940 --- /dev/null +++ b/chrome/browser/cocoa/download_request_dialog_delegate_mac.mm @@ -0,0 +1,105 @@ +// Copyright (c) 2009 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/cocoa/download_request_dialog_delegate_mac.h" + +#import <Cocoa/Cocoa.h> + +#include "app/l10n_util_mac.h" +#include "app/message_box_flags.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/tab_contents/constrained_window.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "grit/generated_resources.h" +#include "views/controls/message_box_view.h" + +@interface SheetCallback : NSObject { + DownloadRequestDialogDelegateMac* target_; // weak +} +- (id)initWithTarget:(DownloadRequestDialogDelegateMac*)target; +- (void) alertDidEnd:(NSAlert *)alert + returnCode:(int)returnCode + contextInfo:(void *)contextInfo; +@end + +@implementation SheetCallback +- (id)initWithTarget:(DownloadRequestDialogDelegateMac*)target { + if ((self = [super init]) != nil) { + target_ = target; + } + return self; +} +- (void) alertDidEnd:(NSAlert *)alert + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + target_->SheetDidEnd(returnCode); +} +@end + + +// static +DownloadRequestDialogDelegate* DownloadRequestDialogDelegate::Create( + TabContents* tab, + DownloadRequestManager::TabDownloadState* host) { + return new DownloadRequestDialogDelegateMac(tab, host); +} + +DownloadRequestDialogDelegateMac::DownloadRequestDialogDelegateMac( + TabContents* tab, + DownloadRequestManager::TabDownloadState* host) + : DownloadRequestDialogDelegate(host), + ConstrainedWindowMacDelegateSystemSheet( + [[[SheetCallback alloc] initWithTarget:this] autorelease], + @selector(alertDidEnd:returnCode:contextInfo:)), + responded_(false) { + + // Set up alert. + NSAlert* alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle: + l10n_util::GetNSStringWithFixup(IDS_MULTI_DOWNLOAD_WARNING_ALLOW)]; + NSButton* denyButton = [alert addButtonWithTitle: + l10n_util::GetNSStringWithFixup(IDS_MULTI_DOWNLOAD_WARNING_DENY)]; + [alert setMessageText: + l10n_util::GetNSStringWithFixup(IDS_MULTI_DOWNLOAD_WARNING)]; + [alert setAlertStyle:NSInformationalAlertStyle]; + + // Cocoa automatically gives ESC as a key equivalent to buttons with the text + // "Cancel". Since we use a different text, we have to set this ourselves. + NSString* escapeKey = @"\e"; + [denyButton setKeyEquivalent:escapeKey]; + + set_sheet(alert); + + // Display alert in a per-tab sheet. + window_ = tab->CreateConstrainedDialog(this); +} + +void DownloadRequestDialogDelegateMac::CloseWindow() { + window_->CloseConstrainedWindow(); +} + +void DownloadRequestDialogDelegateMac::DeleteDelegate() { + if (is_sheet_open()) { + // Close sheet if it's still open. + [NSApp endSheet:[(NSAlert*)sheet() window]]; // class SheetDidEnd(). + DCHECK(responded_); + } + if (!responded_) { + // Happens if the sheet was never visible + responded_ = true; + DoCancel(); + } + DCHECK(!host_); + delete this; +} + +void DownloadRequestDialogDelegateMac::SheetDidEnd(int returnCode) { + DCHECK(!responded_); + if (returnCode == NSAlertFirstButtonReturn) { + DoAccept(); + } else { + DoCancel(); + } + responded_ = true; +} diff --git a/chrome/browser/cocoa/tab_strip_controller.h b/chrome/browser/cocoa/tab_strip_controller.h index ead599e..06db772 100644 --- a/chrome/browser/cocoa/tab_strip_controller.h +++ b/chrome/browser/cocoa/tab_strip_controller.h @@ -10,11 +10,13 @@ #include "base/scoped_nsobject.h" #include "base/scoped_ptr.h" #import "chrome/browser/cocoa/tab_controller_target.h" +#import "third_party/GTM/AppKit/GTMWindowSheetController.h" @class TabView; @class TabStripView; class Browser; +class ConstrainedWindowMac; class TabStripModelObserverBridge; class TabStripModel; class TabContents; @@ -31,7 +33,9 @@ class ToolbarModel; // the single child of the contentView is swapped around to hold the contents // (toolbar and all) representing that tab. -@interface TabStripController : NSObject <TabControllerTarget> { +@interface TabStripController : + NSObject<TabControllerTarget, + GTMWindowSheetControllerDelegate> { @private TabContents* currentTab_; // weak, tab for which we're showing state TabStripView* tabView_; // weak @@ -77,6 +81,9 @@ class ToolbarModel; // Array of subviews which are permanent (and which should never be removed), // such as the new-tab button, but *not* the tabs themselves. scoped_nsobject<NSMutableArray> permanentSubviews_; + + // Manages per-tab sheets. + scoped_nsobject<GTMWindowSheetController> sheetController_; } // Initialize the controller with a view and browser that contains @@ -127,6 +134,14 @@ class ToolbarModel; // Default height for tabs. + (CGFloat)defaultTabHeight; + +// Returns the (lazily created) window sheet controller of this window. Used +// for the per-tab sheets. +- (GTMWindowSheetController*)sheetController; + +- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window; +- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window; + @end // Notification sent when the number of tabs changes. The object will be this diff --git a/chrome/browser/cocoa/tab_strip_controller.mm b/chrome/browser/cocoa/tab_strip_controller.mm index 1fdf0fc..8aec3f8 100644 --- a/chrome/browser/cocoa/tab_strip_controller.mm +++ b/chrome/browser/cocoa/tab_strip_controller.mm @@ -14,6 +14,8 @@ #include "chrome/browser/find_bar_controller.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/profile.h" +#import "chrome/browser/cocoa/browser_window_controller.h" +#import "chrome/browser/cocoa/constrained_window_mac.h" #import "chrome/browser/cocoa/tab_strip_view.h" #import "chrome/browser/cocoa/tab_cell.h" #import "chrome/browser/cocoa/tab_contents_controller.h" @@ -49,6 +51,7 @@ static const float kUseFullAvailableWidth = -1.0; - (BOOL)useFullWidthForLayout; - (void)addSubviewToPermanentList:(NSView*)aView; - (void)regenerateSubviewList; +- (NSInteger)indexForContentsView:(NSView*)view; @end @implementation TabStripController @@ -136,6 +139,28 @@ static const float kUseFullAvailableWidth = -1.0; } else { [switchView_ addSubview:newView]; } + + // Make sure the new tabs's sheets are visible (necessary when a background + // tab opened a sheet while it was in the background and now becomes active). + TabContents* newTab = tabModel_->GetTabContentsAt(index); + DCHECK(newTab); + if (newTab) { + TabContents::ConstrainedWindowList::iterator it, end; + end = newTab->constrained_window_end(); + NSWindowController* controller = [[newView window] windowController]; + DCHECK([controller isKindOfClass:[BrowserWindowController class]]); + + for (it = newTab->constrained_window_begin(); it != end; ++it) { + ConstrainedWindow* constrainedWindow = *it; + static_cast<ConstrainedWindowMac*>(constrainedWindow)->Realize( + static_cast<BrowserWindowController*>(controller)); + } + } + + // Tell per-tab sheet manager about currently selected tab. + if (sheetController_.get()) { + [sheetController_ setActiveView:newView]; + } } // Create a new tab view and set its cell correctly so it draws the way we want @@ -169,6 +194,18 @@ static const float kUseFullAvailableWidth = -1.0; return -1; } +// Returns the index of the contents subview |view|. Returns -1 if not present. +- (NSInteger)indexForContentsView:(NSView*)view { + NSInteger index = 0; + for (TabContentsController* current in tabContentsArray_.get()) { + if ([current view] == view) + return index; + ++index; + } + return -1; +} + + // Returns the view at the given index, using the array of TabControllers to // get the associated view. Returns nil if out of range. - (NSView*)viewAtIndex:(NSUInteger)index { @@ -782,4 +819,69 @@ static const float kUseFullAvailableWidth = -1.0; [tabView_ setSubviews:subviews]; } +- (GTMWindowSheetController*)sheetController { + if (!sheetController_.get()) + sheetController_.reset([[GTMWindowSheetController alloc] + initWithWindow:[switchView_ window] delegate:self]); + return sheetController_.get(); +} + +- (void)gtm_systemRequestsVisibilityForView:(NSView*)view { + // This implementation is required by GTMWindowSheetController. + + // Raise window... + [[switchView_ window] makeKeyAndOrderFront:self]; + + // ...and raise a tab with a sheet. + NSInteger index = [self indexForContentsView:view]; + DCHECK(index >= 0); + if (index >= 0) + tabModel_->SelectTabContentsAt(index, false /* not a user gesture */); +} + +- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window { + // TODO(thakis, avi): Figure out how to make this work when tabs are dragged + // out or if fullscreen mode is toggled. + + // View hierarchy of the contents view: + // NSView -- switchView, same for all tabs + // +- NSView -- TabContentsController's view + // +- NSBox + // +- TabContentsViewCocoa + // We use the TabContentsController's view in |swapInTabAtIndex|, so we have + // to pass it to the sheet controller here. + NSView* tabContentsView = + [[window->owner()->GetNativeView() superview] superview]; + window->delegate()->RunSheet([self sheetController], tabContentsView); + + // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets + // between windows. Until then, we have to prevent having to move a tabsheet + // between windows, e.g. no tearing off of tabs. + NSInteger index = [self indexForContentsView:tabContentsView]; + BrowserWindowController* controller = + (BrowserWindowController*)[[switchView_ window] windowController]; + DCHECK(controller != nil); + DCHECK(index >= 0); + if (index >= 0) { + [controller setTab:[self viewAtIndex:index] isDraggable:NO]; + } +} + +- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window { + NSView* tabContentsView = + [[window->owner()->GetNativeView() superview] superview]; + + // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets + // between windows. Until then, we have to prevent having to move a tabsheet + // between windows, e.g. no tearing off of tabs. + NSInteger index = [self indexForContentsView:tabContentsView]; + BrowserWindowController* controller = + (BrowserWindowController*)[[switchView_ window] windowController]; + DCHECK(index >= 0); + if (index >= 0) { + [controller setTab:[self viewAtIndex:index] isDraggable:YES]; + } +} + + @end diff --git a/chrome/browser/cocoa/tab_view.h b/chrome/browser/cocoa/tab_view.h index 1e8669e..fe6e567 100644 --- a/chrome/browser/cocoa/tab_view.h +++ b/chrome/browser/cocoa/tab_view.h @@ -28,7 +28,7 @@ // All following variables are valid for the duration of a drag. // These are released on mouseUp: - BOOL isTheOnlyTab_; // Is this the only tab in the window? + BOOL moveWindowOnDrag_; // Set if the only tab of a window is dragged. BOOL tabWasDragged_; // Has the tab been dragged? BOOL draggingWithinTabStrip_; // Did drag stay in the current tab strip? BOOL chromeIsVisible_; diff --git a/chrome/browser/cocoa/tab_view.mm b/chrome/browser/cocoa/tab_view.mm index 4036b18..c5bc791 100644 --- a/chrome/browser/cocoa/tab_view.mm +++ b/chrome/browser/cocoa/tab_view.mm @@ -86,6 +86,17 @@ static const CGFloat kControlPoint2Multiplier = 3.0/8.0; return nil; } +// Returns |YES| if this tab can be torn away into a new window. +- (BOOL)canBeDragged { + NSWindowController *controller = [sourceWindow_ windowController]; + if ([controller isKindOfClass:[TabWindowController class]]) { + TabWindowController* realController = + static_cast<TabWindowController*>(controller); + return [realController isTabDraggable:self]; + } + return YES; +} + // Handle clicks and drags in this button. We get here because we have // overridden acceptsFirstMouse: and the click is within our bounds. // TODO(pinkerton/alcor): This routine needs *a lot* of work to marry Cole's @@ -124,7 +135,8 @@ static const double kDragStartDistance = 3.0; // unit tests might have |-numberOfTabs| reporting zero since the model // won't be fully hooked up. We need to be prepared for that and not send // them into the "magnetic" codepath. - isTheOnlyTab_ = [sourceController_ numberOfTabs] <= 1; + moveWindowOnDrag_ = + [sourceController_ numberOfTabs] <= 1 || ![self canBeDragged]; dragOrigin_ = [NSEvent mouseLocation]; @@ -148,11 +160,20 @@ static const double kDragStartDistance = 3.0; } - (void)mouseDragged:(NSEvent *)theEvent { + // Special-case this to keep the logic below simpler. + if (moveWindowOnDrag_) { + NSPoint thisPoint = [NSEvent mouseLocation]; + NSPoint origin = sourceWindowFrame_.origin; + origin.x += (thisPoint.x - dragOrigin_.x); + origin.y += (thisPoint.y - dragOrigin_.y); + [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; + return; + } + // First, go through the magnetic drag cycle. We break out of this if - // "stretchiness" ever exceeds the a set amount. + // "stretchiness" ever exceeds a set amount. tabWasDragged_ = YES; - if (isTheOnlyTab_) draggingWithinTabStrip_ = NO; if (draggingWithinTabStrip_) { NSRect frame = [self frame]; NSPoint thisPoint = [NSEvent mouseLocation]; @@ -190,7 +211,6 @@ static const double kDragStartDistance = 3.0; // appropriate class, and visible (obviously). if (![targets count]) { for (NSWindow* window in [NSApp windows]) { - if (window == sourceWindow_ && isTheOnlyTab_) continue; if (window == dragWindow_) continue; if (![window isVisible]) continue; NSWindowController *controller = [window windowController]; @@ -235,16 +255,13 @@ static const double kDragStartDistance = 3.0; // Create or identify the dragged controller. if (!draggedController_) { - if (isTheOnlyTab_) { - draggedController_ = sourceController_; - dragWindow_ = [draggedController_ window]; - } else { - // Detach from the current window and put it in a new window. - draggedController_ = [sourceController_ detachTabToNewWindow:self]; - dragWindow_ = [draggedController_ window]; - [dragWindow_ setAlphaValue:0.0]; - } + // Detach from the current window and put it in a new window. + draggedController_ = [sourceController_ detachTabToNewWindow:self]; + dragWindow_ = [draggedController_ window]; + [dragWindow_ setAlphaValue:0.0]; + // If dragging the tab only moves the current window, do not show overlay + // so that sheets stay on top of the window. // Bring the target window to the front and make sure it has a border. [dragWindow_ setLevel:NSFloatingWindowLevel]; [dragWindow_ orderFront:nil]; @@ -255,10 +272,8 @@ static const double kDragStartDistance = 3.0; [draggedController_ showNewTabButton:NO]; //if (![targets count]) // [dragOverlay_ setHasShadow:NO]; - if (!isTheOnlyTab_) { - tearTime_ = [NSDate timeIntervalSinceReferenceDate]; - tearOrigin_ = sourceWindowFrame_.origin; - } + tearTime_ = [NSDate timeIntervalSinceReferenceDate]; + tearOrigin_ = sourceWindowFrame_.origin; } float tearProgress = [NSDate timeIntervalSinceReferenceDate] - tearTime_; @@ -348,6 +363,10 @@ static const double kDragStartDistance = 3.0; // The drag/click is done. If the user dragged the mouse, finalize the drag // and clean up. + // Special-case this to keep the logic below simpler. + if (moveWindowOnDrag_) + return; + // We are now free to re-display the new tab button in the window we're // dragging. It will show when the next call to -layoutTabs (which happens // indrectly by several of the calls below, such as removing the placeholder). @@ -369,6 +388,7 @@ static const double kDragStartDistance = 3.0; fromController:draggedController_]; [targetController_ showWindow:nil]; } else { + // Tab dragging did move window only. [dragWindow_ setAlphaValue:1.0]; [dragOverlay_ setHasShadow:NO]; [dragWindow_ setHasShadow:YES]; diff --git a/chrome/browser/cocoa/tab_window_controller.h b/chrome/browser/cocoa/tab_window_controller.h index 46928bd..4c0f07a 100644 --- a/chrome/browser/cocoa/tab_window_controller.h +++ b/chrome/browser/cocoa/tab_window_controller.h @@ -20,6 +20,8 @@ #import <Cocoa/Cocoa.h> +#include "base/scoped_nsobject.h" + @class TabStripView; @class TabView; @@ -31,6 +33,7 @@ NSView* cachedContentView_; // Used during dragging for identifying which // view is the proper content area in the overlay // (weak) + scoped_nsobject<NSMutableSet> lockedTabs_; } @property(readonly, nonatomic) TabStripView* tabStripView; @property(readonly, nonatomic) NSView* tabContentArea; @@ -99,6 +102,8 @@ // default implementation returns YES. - (BOOL)isNormalWindow; +- (BOOL)isTabDraggable:(NSView*)tabView; +- (void)setTab:(NSView*)tabView isDraggable:(BOOL)draggable; @end diff --git a/chrome/browser/cocoa/tab_window_controller.mm b/chrome/browser/cocoa/tab_window_controller.mm index 34417a0..7684f13 100644 --- a/chrome/browser/cocoa/tab_window_controller.mm +++ b/chrome/browser/cocoa/tab_window_controller.mm @@ -15,6 +15,13 @@ @synthesize tabStripView = tabStripView_; @synthesize tabContentArea = tabContentArea_; +- (id)initWithWindow:(NSWindow *)window { + if ((self = [super initWithWindow:window]) != nil) { + lockedTabs_.reset([[NSMutableSet alloc] initWithCapacity:10]); + } + return self; +} + - (void)windowDidLoad { if ([self isNormalWindow]) { // Place the tab bar above the content box and add it to the view hierarchy @@ -176,4 +183,15 @@ return YES; } +- (BOOL)isTabDraggable:(NSView*)tabView { + return ![lockedTabs_ containsObject:tabView]; +} + +- (void)setTab:(NSView*)tabView isDraggable:(BOOL)draggable { + if (draggable) + [lockedTabs_ removeObject:tabView]; + else + [lockedTabs_ addObject:tabView]; +} + @end |