summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/browser_window_controller.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/cocoa/browser_window_controller.mm')
-rw-r--r--chrome/browser/cocoa/browser_window_controller.mm991
1 files changed, 174 insertions, 817 deletions
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm
index c646528..4818b52 100644
--- a/chrome/browser/cocoa/browser_window_controller.mm
+++ b/chrome/browser/cocoa/browser_window_controller.mm
@@ -12,32 +12,25 @@
#import "base/scoped_nsobject.h"
#include "base/sys_string_conversions.h"
#include "chrome/app/chrome_dll_resource.h" // IDC_*
-#include "chrome/browser/bookmarks/bookmark_editor.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_theme_provider.h"
#include "chrome/browser/dock_info.h"
#include "chrome/browser/encoding_menu_controller.h"
#include "chrome/browser/location_bar.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/window_sizer.h"
-#include "chrome/browser/renderer_host/render_widget_host_view.h"
-#include "chrome/browser/tab_contents/tab_contents.h"
-#include "chrome/browser/tab_contents/tab_contents_view.h"
-#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/bookmarks/bookmark_editor.h"
#import "chrome/browser/cocoa/autocomplete_text_field_editor.h"
#import "chrome/browser/cocoa/background_gradient_view.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
#import "chrome/browser/cocoa/bookmark_editor_controller.h"
#import "chrome/browser/cocoa/browser_window_cocoa.h"
-#import "chrome/browser/cocoa/chrome_browser_window.h"
+#import "chrome/browser/cocoa/browser_window_controller_private.h"
#import "chrome/browser/cocoa/download_shelf_controller.h"
#import "chrome/browser/cocoa/event_utils.h"
#import "chrome/browser/cocoa/fast_resize_view.h"
#import "chrome/browser/cocoa/find_bar_cocoa_controller.h"
-#include "chrome/browser/cocoa/find_bar_bridge.h"
-#import "chrome/browser/cocoa/floating_bar_backing_view.h"
+#import "chrome/browser/cocoa/find_bar_bridge.h"
#import "chrome/browser/cocoa/focus_tracker.h"
#import "chrome/browser/cocoa/fullscreen_controller.h"
#import "chrome/browser/cocoa/fullscreen_window.h"
@@ -46,37 +39,51 @@
#import "chrome/browser/cocoa/status_bubble_mac.h"
#import "chrome/browser/cocoa/tab_contents_controller.h"
#import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
-#import "chrome/browser/cocoa/tab_strip_view.h"
#import "chrome/browser/cocoa/tab_strip_controller.h"
+#import "chrome/browser/cocoa/tab_strip_view.h"
#import "chrome/browser/cocoa/tab_view.h"
#import "chrome/browser/cocoa/toolbar_controller.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/sync_ui_util_mac.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/common/pref_service.h"
-#include "grit/generated_resources.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_view.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
#include "grit/locale_settings.h"
-#include "grit/theme_resources.h"
-#import "third_party/GTM/AppKit/GTMTheme.h"
+
// ORGANIZATION: This is a big file. It is (in principle) organized as follows
// (in order):
-// 1. Interfaces, including for our private methods. Very short, one-time-use
-// classes may include an implementation immediately after their interface.
+// 1. Interfaces. Very short, one-time-use classes may include an implementation
+// immediately after their interface.
// 2. The general implementation section, ordered as follows:
// i. Public methods and overrides.
// ii. Overrides/implementations of undocumented methods.
// iii. Delegate methods for various protocols, formal and informal, to which
// |BrowserWindowController| conforms.
-// 3. The private implementation section (|BrowserWindowController (Private)|).
-// 4. Implementation for |GTMTheme (BrowserThemeProviderInitialization)|.
+// 3. (temporary) Implementation sections for various categories.
+//
+// Private methods are defined and implemented separately in
+// browser_window_controller_private.{h,mm}.
//
// Not all of the above guidelines are followed and more (re-)organization is
// needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as
// little as possible, since doing so messes up the file's history.
//
-// TODO(viettrungluu): (re-)organize some more, possibly split into separate
-// files?
+// TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting
+// things into multiple files -- the plan is as follows:
+// - in general, everything stays in browser_window_controller.h, but is split
+// off into categories (see below)
+// - core stuff stays in browser_window_controller.mm
+// - ... overrides also stay (without going into a category, in particular)
+// - private stuff which everyone needs goes into
+// browser_window_controller_private.{h,mm}; if no one else needs them, they
+// can go in individual files (see below)
+// - area/task-specific stuff go in browser_window_controller_<area>.mm
+// - ... in categories called "(<Area>)" or "(<PrivateArea>)"
+// Plan of action:
+// - first re-organize into categories
+// - then split into files
// Notes on self-inflicted (not user-inflicted) window resizing and moving:
//
@@ -119,25 +126,8 @@
// longer indicate that the window is shrinking from an apparent zoomed state)
// and if it's set we continue to constrain the resize.
-namespace {
-// Insets for the location bar, used when the full toolbar is hidden.
-// TODO(viettrungluu): We can argue about the "correct" insetting; I like the
-// following best, though arguably 0 inset is better/more correct.
-const CGFloat kLocBarLeftRightInset = 1;
-const CGFloat kLocBarTopInset = 0;
-const CGFloat kLocBarBottomInset = 1;
-
-// The amount by which the floating bar is offset downwards (to avoid the menu)
-// in fullscreen mode.
-const CGFloat kFullscreenVerticalBarOffset = 14;
-} // end namespace
-
-@interface GTMTheme (BrowserThemeProviderInitialization)
-+ (GTMTheme*)themeWithBrowserThemeProvider:(BrowserThemeProvider*)provider
- isOffTheRecord:(BOOL)offTheRecord;
-@end
-@interface NSWindow (NSPrivateApis)
+@interface NSWindow(NSPrivateApis)
// Note: These functions are private, use -[NSObject respondsToSelector:]
// before calling them.
@@ -147,95 +137,6 @@ const CGFloat kFullscreenVerticalBarOffset = 14;
@end
-@interface BrowserWindowController(Private)
-
-// Saves the window's position in the local state preferences.
-- (void)saveWindowPositionIfNeeded;
-
-// Saves the window's position to the given pref service.
-- (void)saveWindowPositionToPrefs:(PrefService*)prefs;
-
-// We need to adjust where sheets come out of the window, as by default they
-// erupt from the omnibox, which is rather weird.
-- (NSRect)window:(NSWindow*)window
-willPositionSheet:(NSWindow*)sheet
- usingRect:(NSRect)defaultSheetRect;
-
-// Assign a theme to the window.
-- (void)setTheme;
-
-// Repositions the window's subviews. From the top down: toolbar, normal
-// bookmark bar (if shown), infobar, NTP detached bookmark bar (if shown),
-// content area, download shelf (if any).
-- (void)layoutSubviews;
-
-// Find the total height of the floating bar (in fullscreen mode). Safe to call
-// even when not in fullscreen mode.
-- (CGFloat)floatingBarHeight;
-
-// Lays out the tab strip at the given maximum y-coordinate, with the given
-// width, possibly for fullscreen mode; returns the new maximum y (below the tab
-// strip). This is safe to call even when there is no tab strip.
-- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
- width:(CGFloat)width
- fullscreen:(BOOL)fullscreen;
-
-// Lays out the toolbar (or just location bar for popups) at the given maximum
-// y-coordinate, with the given width; returns the new maximum y (below the
-// toolbar).
-- (CGFloat)layoutToolbarAtMaxY:(CGFloat)maxY width:(CGFloat)width;
-
-// Returns YES if the bookmark bar should be placed below the infobar, NO
-// otherwise.
-- (BOOL)placeBookmarkBarBelowInfoBar;
-
-// Lays out the bookmark bar at the given maximum y-coordinate, with the given
-// width; returns the new maximum y (below the bookmark bar). Note that one must
-// call it with the appropriate |maxY| which depends on whether or not the
-// bookmark bar is shown as the NTP bubble or not (use
-// |-placeBookmarkBarBelowInfoBar|).
-- (CGFloat)layoutBookmarkBarAtMaxY:(CGFloat)maxY width:(CGFloat)width;
-
-// Lay out the view which draws the background for the floating bar when in
-// fullscreen mode, with the given (minimum) y-coordinate, width, height, and
-// fullscreen-mode-status. Should be called even when not in fullscreen mode to
-// hide the backing view.
-- (void)layoutFloatingBarBackingViewAtY:(CGFloat)y
- width:(CGFloat)width
- height:(CGFloat)height
- fullscreen:(BOOL)fullscreen;
-
-// Lays out the infobar at the given maximum y-coordinate, with the given width;
-// returns the new maximum y (below the infobar).
-- (CGFloat)layoutInfoBarAtMaxY:(CGFloat)maxY width:(CGFloat)width;
-
-// Lays out the download shelf, if there is one, at the given minimum
-// y-coordinate, with the given width; returns the new minimum y (above the
-// download shelf). This is safe to call even if there is no download shelf.
-- (CGFloat)layoutDownloadShelfAtMinY:(CGFloat)minY width:(CGFloat)width;
-
-// Lays out the tab content area between the given minimum and maximum
-// y-coordinates, with the given width.
-- (void)layoutTabContentAreaAtMinY:(CGFloat)minY
- maxY:(CGFloat)maxY
- width:(CGFloat)width;
-
-// Should we show the normal bookmark bar?
-- (BOOL)shouldShowBookmarkBar;
-
-// Is the current page one for which the bookmark should be shown detached *if*
-// the normal bookmark bar is not shown?
-- (BOOL)shouldShowDetachedBookmarkBar;
-
-// Sets the toolbar's height to a value appropriate for the given compression.
-// Also adjusts the bookmark bar's height by the opposite amount in order to
-// keep the total height of the two views constant.
-- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression;
-
-// Adjust the UI when entering or leaving fullscreen mode.
-- (void)adjustUIForFullscreen:(BOOL)fullscreen;
-
-@end
// IncognitoImageView subclasses NSImageView to allow mouse events to pass
// through it so you can drag the window by dragging on the spy guy
@@ -248,6 +149,7 @@ willPositionSheet:(NSWindow*)sheet
}
@end
+
@implementation BrowserWindowController
+ (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
@@ -264,7 +166,7 @@ willPositionSheet:(NSWindow*)sheet
return [self initWithBrowser:browser takeOwnership:YES];
}
-// Private (TestingAPI) init routine with testing options.
+// Private(TestingAPI) init routine with testing options.
- (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
// Use initWithWindowNibPath:: instead of initWithWindowNibName: so we
// can override it in a unit test.
@@ -413,11 +315,26 @@ willPositionSheet:(NSWindow*)sheet
[super dealloc];
}
-// Access the C++ bridge between the NSWindow and the rest of Chromium
- (BrowserWindow*)browserWindow {
return windowShim_.get();
}
+- (ToolbarController*)toolbarController {
+ return toolbarController_.get();
+}
+
+- (TabStripController*)tabStripController {
+ return tabStripController_.get();
+}
+
+- (StatusBubbleMac*)statusBubble {
+ return statusBubble_;
+}
+
+- (LocationBar*)locationBarBridge {
+ return [toolbarController_ locationBarBridge];
+}
+
- (void)destroyBrowser {
[NSApp removeWindowsItem:[self window]];
@@ -1038,14 +955,6 @@ willPositionSheet:(NSWindow*)sheet
return [tabStripController_ sheetController];
}
-- (LocationBar*)locationBarBridge {
- return [toolbarController_ locationBarBridge];
-}
-
-- (StatusBubbleMac*)statusBubble {
- return statusBubble_;
-}
-
- (void)updateToolbarWithContents:(TabContents*)tab
shouldRestoreState:(BOOL)shouldRestore {
[toolbarController_ updateToolbarWithContents:tab
@@ -1162,14 +1071,6 @@ willPositionSheet:(NSWindow*)sheet
return [tabStripController_ selectedTabView];
}
-- (TabStripController*)tabStripController {
- return tabStripController_.get();
-}
-
-- (ToolbarController*)toolbarController {
- return toolbarController_.get();
-}
-
- (void)setIsLoading:(BOOL)isLoading {
[toolbarController_ setIsLoading:isLoading];
}
@@ -1320,146 +1221,6 @@ willPositionSheet:(NSWindow*)sheet
autorelease];
}
-- (void)setFullscreen:(BOOL)fullscreen {
- // The logic in this function is a bit complicated and very carefully
- // arranged. See the below comments for more details.
-
- if (fullscreen == [self isFullscreen])
- return;
-
- if (![self supportsFullscreen])
- return;
-
- // Save the current first responder so we can restore after views are moved.
- NSWindow* window = [self window];
- scoped_nsobject<FocusTracker> focusTracker(
- [[FocusTracker alloc] initWithWindow:window]);
- BOOL showDropdown = [self floatingBarHasFocus];
-
- // If we're entering fullscreen, create the fullscreen controller. If we're
- // exiting fullscreen, kill the controller.
- if (fullscreen) {
- fullscreenController_.reset([[FullscreenController alloc]
- initWithBrowserController:self]);
- } else {
- [fullscreenController_ exitFullscreen];
- fullscreenController_.reset(nil);
- }
-
- // Retain the tab strip view while we remove it from its superview.
- scoped_nsobject<NSView> tabStripView;
- if ([self hasTabStrip]) {
- tabStripView.reset([[self tabStripView] retain]);
- [tabStripView removeFromSuperview];
- }
-
- // Ditto for the content view.
- scoped_nsobject<NSView> contentView([[window contentView] retain]);
- // Disable autoresizing of subviews while we move views around. This prevents
- // spurious renderer resizes.
- [contentView setAutoresizesSubviews:NO];
- [contentView removeFromSuperview];
-
- NSWindow* destWindow = nil;
- if (fullscreen) {
- DCHECK(!savedRegularWindow_);
- savedRegularWindow_ = [window retain];
- destWindow = [self createFullscreenWindow];
- } else {
- DCHECK(savedRegularWindow_);
- destWindow = [savedRegularWindow_ autorelease];
- savedRegularWindow_ = nil;
- }
- DCHECK(destWindow);
-
- // Have to do this here, otherwise later calls can crash because the window
- // has no delegate.
- [window setDelegate:nil];
- [destWindow setDelegate:self];
-
- // With this call, valgrind complains that a "Conditional jump or move depends
- // on uninitialised value(s)". The error happens in -[NSThemeFrame
- // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
- // no visual impact. I have been unable to tickle it away with other window
- // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
- [contentView setAutoresizesSubviews:YES];
- [destWindow setContentView:contentView];
-
- // Add the tabstrip after setting the content view, so that the tab strip will
- // be on top (in the z-order).
- if ([self hasTabStrip])
- [[[destWindow contentView] superview] addSubview:tabStripView];
-
- [window setWindowController:nil];
- [self setWindow:destWindow];
- [destWindow setWindowController:self];
- [self adjustUIForFullscreen:fullscreen];
-
- // When entering fullscreen mode, the controller forces a layout for us. When
- // exiting, we need to call layoutSubviews manually.
- if (fullscreen) {
- [fullscreenController_ enterFullscreenForContentView:contentView
- showDropdown:showDropdown];
- } else {
- [self layoutSubviews];
- }
-
- // The window needs to be onscreen before we can set its first responder.
- [destWindow makeKeyAndOrderFront:self];
- [focusTracker restoreFocusInWindow:destWindow];
- [window orderOut:self];
-}
-
-- (BOOL)isFullscreen {
- return savedRegularWindow_ != nil;
-}
-
-- (CGFloat)floatingBarShownFraction {
- return floatingBarShownFraction_;
-}
-
-- (void)setFloatingBarShownFraction:(CGFloat)fraction {
- floatingBarShownFraction_ = fraction;
- [self layoutSubviews];
-}
-
-- (BOOL)isBarVisibilityLockedForOwner:(id)owner {
- DCHECK(owner);
- DCHECK(barVisibilityLocks_);
- return [barVisibilityLocks_ containsObject:owner];
-}
-
-- (void)lockBarVisibilityForOwner:(id)owner
- withAnimation:(BOOL)animate
- delay:(BOOL)delay {
- if (![self isBarVisibilityLockedForOwner:owner]) {
- [barVisibilityLocks_ addObject:owner];
-
- // Show the overlay if necessary (and if in fullscreen mode).
- [fullscreenController_ ensureOverlayShownWithAnimation:animate
- delay:delay];
- }
-}
-
-- (void)releaseBarVisibilityForOwner:(id)owner
- withAnimation:(BOOL)animate
- delay:(BOOL)delay {
- if ([self isBarVisibilityLockedForOwner:owner]) {
- [barVisibilityLocks_ removeObject:owner];
-
- // Hide the overlay if necessary (and if in fullscreen mode).
- if (![barVisibilityLocks_ count]) {
- [fullscreenController_ ensureOverlayHiddenWithAnimation:animate
- delay:delay];
- }
- }
-}
-
-- (BOOL)floatingBarHasFocus {
- NSResponder* focused = [[self window] firstResponder];
- return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
-}
-
- (NSInteger)numberOfTabs {
return browser_->tabstrip_model()->count();
}
@@ -1469,11 +1230,6 @@ willPositionSheet:(NSWindow*)sheet
return base::SysUTF16ToNSString(contents->GetTitle());
}
-- (BOOL)supportsWindowFeature:(int)feature {
- return browser_->SupportsWindowFeature(
- static_cast<Browser::WindowFeature>(feature));
-}
-
- (NSRect)regularWindowFrame {
return [self isFullscreen] ? [savedRegularWindow_ frame] :
[[self window] frame];
@@ -1484,26 +1240,6 @@ willPositionSheet:(NSWindow*)sheet
return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
}
-- (BOOL)hasTitleBar {
- return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
-}
-
-- (BOOL)hasToolbar {
- return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
-}
-
-- (BOOL)hasLocationBar {
- return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
-}
-
-- (BOOL)supportsBookmarkBar {
- return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
-}
-
-- (BOOL)isNormalWindow {
- return browser_->type() == Browser::TYPE_NORMAL;
-}
-
- (void)selectTabWithContents:(TabContents*)newContents
previousContents:(TabContents*)oldContents
atIndex:(NSInteger)index
@@ -1844,558 +1580,179 @@ willAnimateFromState:(bookmarks::VisualState)oldState
isShrinkingFromZoomed_ = NO;
}
-@end
+@end // @implementation BrowserWindowController
-@implementation BrowserWindowController (Private)
-- (void)saveWindowPositionIfNeeded {
- if (browser_ != BrowserList::GetLastActive())
- return;
+@implementation BrowserWindowController(Fullscreen)
+
+- (void)setFullscreen:(BOOL)fullscreen {
+ // The logic in this function is a bit complicated and very carefully
+ // arranged. See the below comments for more details.
- if (!g_browser_process || !g_browser_process->local_state() ||
- !browser_->ShouldSaveWindowPlacement())
+ if (fullscreen == [self isFullscreen])
return;
- [self saveWindowPositionToPrefs:g_browser_process->local_state()];
-}
-
-- (void)saveWindowPositionToPrefs:(PrefService*)prefs {
- // If we're in fullscreen mode, save the position of the regular window
- // instead.
- NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
-
- // Window positions are stored relative to the origin of the primary monitor.
- NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
-
- // Start with the window's frame, which is in virtual coordinates.
- // Do some y twiddling to flip the coordinate system.
- gfx::Rect bounds(NSRectToCGRect([window frame]));
- bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
-
- // We also need to save the current work area, in flipped coordinates.
- gfx::Rect workArea(NSRectToCGRect([[window screen] visibleFrame]));
- workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
-
- DictionaryValue* windowPreferences = prefs->GetMutableDictionary(
- browser_->GetWindowPlacementKey().c_str());
- windowPreferences->SetInteger(L"left", bounds.x());
- windowPreferences->SetInteger(L"top", bounds.y());
- windowPreferences->SetInteger(L"right", bounds.right());
- windowPreferences->SetInteger(L"bottom", bounds.bottom());
- windowPreferences->SetBoolean(L"maximized", false);
- windowPreferences->SetBoolean(L"always_on_top", false);
- windowPreferences->SetInteger(L"work_area_left", workArea.x());
- windowPreferences->SetInteger(L"work_area_top", workArea.y());
- windowPreferences->SetInteger(L"work_area_right", workArea.right());
- windowPreferences->SetInteger(L"work_area_bottom", workArea.bottom());
-}
-
-- (NSRect)window:(NSWindow*)window
-willPositionSheet:(NSWindow*)sheet
- usingRect:(NSRect)defaultSheetRect {
- // Position the sheet as follows:
- // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
- // bookmark bar is disabled), position the sheet immediately below the
- // normal toolbar.
- // - If the bookmark bar is shown (attached to the normal toolbar), position
- // the sheet below the bookmark bar.
- // - If the bookmark bar is currently animating, position the sheet according
- // to where the bar will be when the animation ends.
- switch ([bookmarkBarController_ visualState]) {
- case bookmarks::kShowingState: {
- NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
- defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
- break;
- }
- case bookmarks::kHiddenState:
- case bookmarks::kDetachedState: {
- NSRect toolbarFrame = [[toolbarController_ view] frame];
- defaultSheetRect.origin.y = toolbarFrame.origin.y;
- break;
- }
- case bookmarks::kInvalidState:
- default:
- NOTREACHED();
- }
- return defaultSheetRect;
-}
-
-- (void)setTheme {
- ThemeProvider* theme_provider = browser_->profile()->GetThemeProvider();
- BrowserThemeProvider* browser_theme_provider =
- static_cast<BrowserThemeProvider*>(theme_provider);
- if (browser_theme_provider) {
- bool offtheRecord = browser_->profile()->IsOffTheRecord();
- GTMTheme* theme =
- [GTMTheme themeWithBrowserThemeProvider:browser_theme_provider
- isOffTheRecord:offtheRecord];
- theme_.reset([theme retain]);
- }
-}
+ if (![self supportsFullscreen])
+ return;
-- (void)layoutSubviews {
- // With the exception of the tab strip, the subviews which we lay out are
- // subviews of the content view, so we mainly work in the content view's
- // coordinate system. Note, however, that the content view's coordinate system
- // and the window's base coordinate system should coincide.
+ // Save the current first responder so we can restore after views are moved.
NSWindow* window = [self window];
- NSView* contentView = [window contentView];
- NSRect contentBounds = [contentView bounds];
- CGFloat minY = NSMinY(contentBounds);
- CGFloat width = NSWidth(contentBounds);
-
- // Suppress title drawing if necessary.
- if ([window respondsToSelector:@selector(setShouldHideTitle:)])
- [(id)window setShouldHideTitle:![self hasTitleBar]];
+ scoped_nsobject<FocusTracker> focusTracker(
+ [[FocusTracker alloc] initWithWindow:window]);
+ BOOL showDropdown = [self floatingBarHasFocus];
- BOOL isFullscreen = [self isFullscreen];
- CGFloat floatingBarHeight = [self floatingBarHeight];
- CGFloat yOffset = floor(
- isFullscreen ? (1 - floatingBarShownFraction_) * floatingBarHeight : 0);
- CGFloat maxY = NSMaxY(contentBounds) + yOffset;
- CGFloat startMaxY = maxY;
+ // If we're entering fullscreen, create the fullscreen controller. If we're
+ // exiting fullscreen, kill the controller.
+ if (fullscreen) {
+ fullscreenController_.reset([[FullscreenController alloc]
+ initWithBrowserController:self]);
+ } else {
+ [fullscreenController_ exitFullscreen];
+ fullscreenController_.reset(nil);
+ }
+ // Retain the tab strip view while we remove it from its superview.
+ scoped_nsobject<NSView> tabStripView;
if ([self hasTabStrip]) {
- // If we need to lay out the tab strip, replace |maxY| and |startMaxY| with
- // higher values, and then lay out the tab strip.
- startMaxY = maxY = NSHeight([window frame]) + yOffset;
- maxY = [self layoutTabStripAtMaxY:maxY width:width fullscreen:isFullscreen];
+ tabStripView.reset([[self tabStripView] retain]);
+ [tabStripView removeFromSuperview];
}
- // Sanity-check |maxY|.
- DCHECK_GE(maxY, minY);
- DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
-
- // Place the toolbar at the top of the reserved area.
- maxY = [self layoutToolbarAtMaxY:maxY width:width];
-
- // If we're not displaying the bookmark bar below the infobar, then it goes
- // immediately below the toolbar.
- BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
- if (!placeBookmarkBarBelowInfoBar)
- maxY = [self layoutBookmarkBarAtMaxY:maxY width:width];
-
- // The floating bar backing view doesn't actually add any height.
- [self layoutFloatingBarBackingViewAtY:maxY
- width:width
- height:floatingBarHeight
- fullscreen:isFullscreen];
+ // Ditto for the content view.
+ scoped_nsobject<NSView> contentView([[window contentView] retain]);
+ // Disable autoresizing of subviews while we move views around. This prevents
+ // spurious renderer resizes.
+ [contentView setAutoresizesSubviews:NO];
+ [contentView removeFromSuperview];
- [fullscreenController_ overlayFrameChanged:[floatingBarBackingView_ frame]];
+ NSWindow* destWindow = nil;
+ if (fullscreen) {
+ DCHECK(!savedRegularWindow_);
+ savedRegularWindow_ = [window retain];
+ destWindow = [self createFullscreenWindow];
+ } else {
+ DCHECK(savedRegularWindow_);
+ destWindow = [savedRegularWindow_ autorelease];
+ savedRegularWindow_ = nil;
+ }
+ DCHECK(destWindow);
- // Place the find bar immediately below the toolbar/attached bookmark bar. In
- // fullscreen mode, it hangs off the top of the screen when the bar is hidden.
- [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
+ // Have to do this here, otherwise later calls can crash because the window
+ // has no delegate.
+ [window setDelegate:nil];
+ [destWindow setDelegate:self];
- // If in fullscreen mode, reset |maxY| to top of screen, so that the floating
- // bar slides over the things which appear to be in the content area.
- if (isFullscreen)
- maxY = NSMaxY(contentBounds);
+ // With this call, valgrind complains that a "Conditional jump or move depends
+ // on uninitialised value(s)". The error happens in -[NSThemeFrame
+ // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
+ // no visual impact. I have been unable to tickle it away with other window
+ // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
+ [contentView setAutoresizesSubviews:YES];
+ [destWindow setContentView:contentView];
- // Also place the infobar container immediate below the toolbar, except in
- // fullscreen mode in which case it's at the top of the visual content area.
- maxY = [self layoutInfoBarAtMaxY:maxY width:width];
+ // Add the tabstrip after setting the content view, so that the tab strip will
+ // be on top (in the z-order).
+ if ([self hasTabStrip])
+ [[[destWindow contentView] superview] addSubview:tabStripView];
- // If the bookmark bar is detached, place it next in the visual content area.
- if (placeBookmarkBarBelowInfoBar)
- maxY = [self layoutBookmarkBarAtMaxY:maxY width:width];
+ [window setWindowController:nil];
+ [self setWindow:destWindow];
+ [destWindow setWindowController:self];
+ [self adjustUIForFullscreen:fullscreen];
- // Place the download shelf, if any, at the bottom of the view.
- minY = [self layoutDownloadShelfAtMinY:minY width:width];
+ // When entering fullscreen mode, the controller forces a layout for us. When
+ // exiting, we need to call layoutSubviews manually.
+ if (fullscreen) {
+ [fullscreenController_ enterFullscreenForContentView:contentView
+ showDropdown:showDropdown];
+ } else {
+ [self layoutSubviews];
+ }
- // Finally, the content area takes up all of the remaining space.
- [self layoutTabContentAreaAtMinY:minY maxY:maxY width:width];
+ // The window needs to be onscreen before we can set its first responder.
+ [destWindow makeKeyAndOrderFront:self];
+ [focusTracker restoreFocusInWindow:destWindow];
+ [window orderOut:self];
+}
- // Place the status bubble at the bottom of the content area.
- verticalOffsetForStatusBubble_ = minY;
+- (BOOL)isFullscreen {
+ return savedRegularWindow_ != nil;
+}
- // Normally, we don't need to tell the toolbar whether or not to show the
- // divider, but things break down during animation.
- [toolbarController_
- setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]];
+- (CGFloat)floatingBarShownFraction {
+ return floatingBarShownFraction_;
}
-- (CGFloat)floatingBarHeight {
- if (![self isFullscreen])
- return 0;
+- (void)setFloatingBarShownFraction:(CGFloat)fraction {
+ floatingBarShownFraction_ = fraction;
+ [self layoutSubviews];
+}
- CGFloat totalHeight = kFullscreenVerticalBarOffset;
+- (BOOL)isBarVisibilityLockedForOwner:(id)owner {
+ DCHECK(owner);
+ DCHECK(barVisibilityLocks_);
+ return [barVisibilityLocks_ containsObject:owner];
+}
- if ([self hasTabStrip])
- totalHeight += NSHeight([[self tabStripView] frame]);
+- (void)lockBarVisibilityForOwner:(id)owner
+ withAnimation:(BOOL)animate
+ delay:(BOOL)delay {
+ if (![self isBarVisibilityLockedForOwner:owner]) {
+ [barVisibilityLocks_ addObject:owner];
- if ([self hasToolbar]) {
- totalHeight += NSHeight([[toolbarController_ view] frame]);
- } else if ([self hasLocationBar]) {
- totalHeight += NSHeight([[toolbarController_ view] frame]) +
- kLocBarTopInset + kLocBarBottomInset;
+ // Show the overlay if necessary (and if in fullscreen mode).
+ [fullscreenController_ ensureOverlayShownWithAnimation:animate
+ delay:delay];
}
-
- if (![self placeBookmarkBarBelowInfoBar])
- totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
-
- return totalHeight;
}
-- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
- width:(CGFloat)width
- fullscreen:(BOOL)fullscreen {
- // Nothing to do if no tab strip.
- if (![self hasTabStrip])
- return maxY;
-
- NSView* tabStripView = [self tabStripView];
- CGFloat tabStripHeight = NSHeight([tabStripView frame]);
- // In fullscreen mode, push the tab strip down so that the main menu (which
- // also slides down) doesn't run it over.
- if (fullscreen)
- maxY -= kFullscreenVerticalBarOffset;
- maxY -= tabStripHeight;
- [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
-
- // Set indentation.
- [tabStripController_ setIndentForControls:(fullscreen ? 0 :
- [[tabStripController_ class] defaultIndentForControls])];
-
- // TODO(viettrungluu): Seems kind of bad -- shouldn't |-layoutSubviews| do
- // this? Moreover, |-layoutTabs| will try to animate....
- [tabStripController_ layoutTabs];
-
- return maxY;
-}
+- (void)releaseBarVisibilityForOwner:(id)owner
+ withAnimation:(BOOL)animate
+ delay:(BOOL)delay {
+ if ([self isBarVisibilityLockedForOwner:owner]) {
+ [barVisibilityLocks_ removeObject:owner];
-- (CGFloat)layoutToolbarAtMaxY:(CGFloat)maxY width:(CGFloat)width {
- NSView* toolbarView = [toolbarController_ view];
- NSRect toolbarFrame = [toolbarView frame];
- if ([self hasToolbar]) {
- // The toolbar is present in the window, so we make room for it.
- DCHECK(![toolbarView isHidden]);
- toolbarFrame.origin.x = 0;
- toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
- toolbarFrame.size.width = width;
- maxY -= NSHeight(toolbarFrame);
- } else {
- if ([self hasLocationBar]) {
- // Location bar is present with no toolbar. Put a border of
- // |kLocBar...Inset| pixels around the location bar.
- // TODO(viettrungluu): This is moderately ridiculous. The toolbar should
- // really be aware of what its height should be (the way the toolbar
- // compression stuff is currently set up messes things up).
- DCHECK(![toolbarView isHidden]);
- toolbarFrame.origin.x = kLocBarLeftRightInset;
- toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
- toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
- maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
- } else {
- DCHECK([toolbarView isHidden]);
- }
- }
- [toolbarView setFrame:toolbarFrame];
- return maxY;
-}
-
-- (BOOL)placeBookmarkBarBelowInfoBar {
- // If we are currently displaying the NTP detached bookmark bar or animating
- // to/from it (from/to anything else), we display the bookmark bar below the
- // infobar.
- return [bookmarkBarController_ isInState:bookmarks::kDetachedState] ||
- [bookmarkBarController_ isAnimatingToState:bookmarks::kDetachedState] ||
- [bookmarkBarController_ isAnimatingFromState:bookmarks::kDetachedState];
-}
-
-- (CGFloat)layoutBookmarkBarAtMaxY:(CGFloat)maxY width:(CGFloat)width {
- NSView* bookmarkBarView = [bookmarkBarController_ view];
- NSRect bookmarkBarFrame = [bookmarkBarView frame];
- BOOL oldHidden = [bookmarkBarView isHidden];
- BOOL newHidden = ![self isBookmarkBarVisible];
- if (oldHidden != newHidden)
- [bookmarkBarView setHidden:newHidden];
- bookmarkBarFrame.origin.y = maxY - NSHeight(bookmarkBarFrame);
- bookmarkBarFrame.size.width = width;
- [bookmarkBarView setFrame:bookmarkBarFrame];
- maxY -= NSHeight(bookmarkBarFrame);
-
- // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
- // necessary in the non-NTP case.
- [bookmarkBarController_ layoutSubviews];
-
- return maxY;
-}
-
-- (void)layoutFloatingBarBackingViewAtY:(CGFloat)y
- width:(CGFloat)width
- height:(CGFloat)height
- fullscreen:(BOOL)fullscreen {
- // Only display when in fullscreen mode.
- if (fullscreen) {
- DCHECK(floatingBarBackingView_.get());
- BOOL aboveBookmarkBar = [self placeBookmarkBarBelowInfoBar];
-
- // Insert it into the view hierarchy if necessary.
- if (![floatingBarBackingView_ superview] ||
- aboveBookmarkBar != floatingBarAboveBookmarkBar_) {
- NSView* contentView = [[self window] contentView];
- // z-order gets messed up unless we explicitly remove the floatingbar view
- // and re-add it.
- [floatingBarBackingView_ removeFromSuperview];
- [contentView addSubview:floatingBarBackingView_
- positioned:(aboveBookmarkBar ?
- NSWindowAbove : NSWindowBelow)
- relativeTo:[bookmarkBarController_ view]];
- floatingBarAboveBookmarkBar_ = aboveBookmarkBar;
+ // Hide the overlay if necessary (and if in fullscreen mode).
+ if (![barVisibilityLocks_ count]) {
+ [fullscreenController_ ensureOverlayHiddenWithAnimation:animate
+ delay:delay];
}
-
- // Set its frame.
- [floatingBarBackingView_ setFrame:NSMakeRect(0, y, width, height)];
- } else {
- // Okay to call even if |floatingBarBackingView_| is nil.
- if ([floatingBarBackingView_ superview])
- [floatingBarBackingView_ removeFromSuperview];
}
}
-- (CGFloat)layoutInfoBarAtMaxY:(CGFloat)maxY width:(CGFloat)width {
- NSView* infoBarView = [infoBarContainerController_ view];
- NSRect infoBarFrame = [infoBarView frame];
- infoBarFrame.origin.y = maxY - NSHeight(infoBarFrame);
- infoBarFrame.size.width = width;
- [infoBarView setFrame:infoBarFrame];
- maxY -= NSHeight(infoBarFrame);
- return maxY;
-}
-
-- (CGFloat)layoutDownloadShelfAtMinY:(CGFloat)minY width:(CGFloat)width {
- if (downloadShelfController_.get()) {
- NSView* downloadView = [downloadShelfController_ view];
- NSRect downloadFrame = [downloadView frame];
- downloadFrame.origin.y = minY;
- downloadFrame.size.width = width;
- [downloadView setFrame:downloadFrame];
- minY += NSHeight(downloadFrame);
- }
- return minY;
+- (BOOL)floatingBarHasFocus {
+ NSResponder* focused = [[self window] firstResponder];
+ return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
}
-- (void)layoutTabContentAreaAtMinY:(CGFloat)minY
- maxY:(CGFloat)maxY
- width:(CGFloat)width {
- NSView* tabContentView = [self tabContentArea];
- NSRect tabContentFrame = [tabContentView frame];
+@end // @implementation BrowserWindowController(Fullscreen)
- bool contentShifted = NSMaxY(tabContentFrame) != maxY;
- tabContentFrame.origin.y = minY;
- tabContentFrame.size.height = maxY - minY;
- tabContentFrame.size.width = width;
- [tabContentView setFrame:tabContentFrame];
+@implementation BrowserWindowController(WindowType)
- // If the relayout shifts the content area up or down, let the renderer know.
- if (contentShifted) {
- if (TabContents* contents = browser_->GetSelectedTabContents()) {
- if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
- rwhv->WindowFrameChanged();
- }
- }
+- (BOOL)supportsWindowFeature:(int)feature {
+ return browser_->SupportsWindowFeature(
+ static_cast<Browser::WindowFeature>(feature));
}
-- (BOOL)shouldShowBookmarkBar {
- DCHECK(browser_.get());
- return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ?
- YES : NO;
+- (BOOL)hasTitleBar {
+ return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
}
-- (BOOL)shouldShowDetachedBookmarkBar {
- DCHECK(browser_.get());
- TabContents* contents = browser_->GetSelectedTabContents();
- return (contents && contents->ShouldShowBookmarkBar()) ? YES : NO;
+- (BOOL)hasToolbar {
+ return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
}
-- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
- CGFloat newHeight =
- [toolbarController_ desiredHeightForCompression:compression];
- NSRect toolbarFrame = [[toolbarController_ view] frame];
- CGFloat deltaH = newHeight - toolbarFrame.size.height;
-
- if (deltaH == 0)
- return;
-
- toolbarFrame.size.height = newHeight;
- NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
- bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
- [[toolbarController_ view] setFrame:toolbarFrame];
- [[bookmarkBarController_ view] setFrame:bookmarkFrame];
- [self layoutSubviews];
+- (BOOL)hasLocationBar {
+ return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
}
-- (void)adjustUIForFullscreen:(BOOL)fullscreen {
- if (fullscreen) {
- mac_util::RequestFullScreen();
-
- // Create the floating bar backing view if necessary.
- if (!floatingBarBackingView_.get()) {
- floatingBarBackingView_.reset(
- [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
- }
- } else {
- mac_util::ReleaseFullScreen();
- }
+- (BOOL)supportsBookmarkBar {
+ return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
}
-@end // @implementation BrowserWindowController (Private)
-
-@implementation GTMTheme (BrowserThemeProviderInitialization)
-+ (GTMTheme*)themeWithBrowserThemeProvider:(BrowserThemeProvider*)provider
- isOffTheRecord:(BOOL)isOffTheRecord {
- // First check if it's in the cache.
- typedef std::pair<std::string, BOOL> ThemeKey;
- static std::map<ThemeKey, GTMTheme*> cache;
- ThemeKey key(provider->GetThemeID(), isOffTheRecord);
- GTMTheme* theme = cache[key];
- if (theme)
- return theme;
-
- theme = [[GTMTheme alloc] init]; // "Leak" it in the cache.
- cache[key] = theme;
-
- // TODO(pinkerton): Need to be able to theme the entire incognito window
- // http://crbug.com/18568 The hardcoding of the colors here will need to be
- // removed when that bug is addressed, but are here in order for things to be
- // usable in the meantime.
- if (isOffTheRecord) {
- NSColor* incognitoColor = [NSColor colorWithCalibratedRed:83/255.0
- green:108.0/255.0
- blue:140/255.0
- alpha:1.0];
- [theme setBackgroundColor:incognitoColor];
- [theme setValue:[NSColor blackColor]
- forAttribute:@"textColor"
- style:GTMThemeStyleTabBarSelected
- state:GTMThemeStateActiveWindow];
- [theme setValue:[NSColor blackColor]
- forAttribute:@"textColor"
- style:GTMThemeStyleTabBarDeselected
- state:GTMThemeStateActiveWindow];
- [theme setValue:[NSColor blackColor]
- forAttribute:@"textColor"
- style:GTMThemeStyleBookmarksBarButton
- state:GTMThemeStateActiveWindow];
- [theme setValue:[NSColor blackColor]
- forAttribute:@"iconColor"
- style:GTMThemeStyleToolBarButton
- state:GTMThemeStateActiveWindow];
- return theme;
- }
-
- NSImage* frameImage = provider->GetNSImageNamed(IDR_THEME_FRAME);
- NSImage* frameInactiveImage =
- provider->GetNSImageNamed(IDR_THEME_FRAME_INACTIVE);
-
- [theme setValue:frameImage
- forAttribute:@"backgroundImage"
- style:GTMThemeStyleWindow
- state:GTMThemeStateActiveWindow];
-
- NSColor* tabTextColor =
- provider->GetNSColor(BrowserThemeProvider::COLOR_TAB_TEXT);
- [theme setValue:tabTextColor
- forAttribute:@"textColor"
- style:GTMThemeStyleTabBarSelected
- state:GTMThemeStateActiveWindow];
-
- NSColor* tabInactiveTextColor =
- provider->GetNSColor(BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT);
- [theme setValue:tabInactiveTextColor
- forAttribute:@"textColor"
- style:GTMThemeStyleTabBarDeselected
- state:GTMThemeStateActiveWindow];
-
- NSColor* bookmarkBarTextColor =
- provider->GetNSColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
- [theme setValue:bookmarkBarTextColor
- forAttribute:@"textColor"
- style:GTMThemeStyleBookmarksBarButton
- state:GTMThemeStateActiveWindow];
-
- [theme setValue:frameInactiveImage
- forAttribute:@"backgroundImage"
- style:GTMThemeStyleWindow
- state:0];
-
- NSImage* toolbarImage = provider->GetNSImageNamed(IDR_THEME_TOOLBAR);
- [theme setValue:toolbarImage
- forAttribute:@"backgroundImage"
- style:GTMThemeStyleToolBar
- state:GTMThemeStateActiveWindow];
- NSImage* toolbarBackgroundImage =
- provider->GetNSImageNamed(IDR_THEME_TAB_BACKGROUND);
- [theme setValue:toolbarBackgroundImage
- forAttribute:@"backgroundImage"
- style:GTMThemeStyleTabBarDeselected
- state:GTMThemeStateActiveWindow];
-
- NSImage* toolbarButtonImage =
- provider->GetNSImageNamed(IDR_THEME_BUTTON_BACKGROUND);
- if (toolbarButtonImage) {
- [theme setValue:toolbarButtonImage
- forAttribute:@"backgroundImage"
- style:GTMThemeStyleToolBarButton
- state:GTMThemeStateActiveWindow];
- } else {
- NSColor* startColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.0];
- NSColor* endColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.3];
- scoped_nsobject<NSGradient> gradient([[NSGradient alloc]
- initWithStartingColor:startColor
- endingColor:endColor]);
-
- [theme setValue:gradient
- forAttribute:@"gradient"
- style:GTMThemeStyleToolBarButton
- state:GTMThemeStateActiveWindow];
-
- [theme setValue:gradient
- forAttribute:@"gradient"
- style:GTMThemeStyleToolBarButton
- state:GTMThemeStateActiveWindow];
- }
-
- NSColor* toolbarButtonIconColor =
- provider->GetNSColorTint(BrowserThemeProvider::TINT_BUTTONS);
- [theme setValue:toolbarButtonIconColor
- forAttribute:@"iconColor"
- style:GTMThemeStyleToolBarButton
- state:GTMThemeStateActiveWindow];
-
- NSColor* toolbarButtonBorderColor = toolbarButtonIconColor;
- [theme setValue:toolbarButtonBorderColor
- forAttribute:@"borderColor"
- style:GTMThemeStyleToolBar
- state:GTMThemeStateActiveWindow];
-
- NSColor* toolbarBackgroundColor =
- provider->GetNSColor(BrowserThemeProvider::COLOR_TOOLBAR);
- [theme setValue:toolbarBackgroundColor
- forAttribute:@"backgroundColor"
- style:GTMThemeStyleToolBar
- state:GTMThemeStateActiveWindow];
-
- NSImage* frameOverlayImage =
- provider->GetNSImageNamed(IDR_THEME_FRAME_OVERLAY);
- if (frameOverlayImage) {
- [theme setValue:frameOverlayImage
- forAttribute:@"overlay"
- style:GTMThemeStyleWindow
- state:GTMThemeStateActiveWindow];
- }
-
- NSImage* frameOverlayInactiveImage =
- provider->GetNSImageNamed(IDR_THEME_FRAME_OVERLAY_INACTIVE);
- if (frameOverlayInactiveImage) {
- [theme setValue:frameOverlayInactiveImage
- forAttribute:@"overlay"
- style:GTMThemeStyleWindow
- state:GTMThemeStateInactiveWindow];
- }
-
- return theme;
+- (BOOL)isNormalWindow {
+ return browser_->type() == Browser::TYPE_NORMAL;
}
-@end
+
+@end // @implementation BrowserWindowController(WindowType)