| // Copyright (c) 2012 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. |
| |
| #import "speech_recognition_window_controller.h" |
| |
| #include "base/logging.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "chrome/browser/ui/cocoa/info_bubble_view.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #import "skia/ext/skia_utils_mac.h" |
| #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
| #include "ui/base/l10n/l10n_util_mac.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image.h" |
| |
| const int kBubbleControlVerticalSpacing = 10; // Space between controls. |
| const int kBubbleHorizontalMargin = 5; // Space on either sides of controls. |
| const int kInstructionLabelMaxWidth = 150; |
| |
| @interface SpeechRecognitionWindowController (Private) |
| - (NSSize)calculateContentSize; |
| - (void)layout:(NSSize)size; |
| @end |
| |
| @implementation SpeechRecognitionWindowController |
| |
| - (id)initWithParentWindow:(NSWindow*)parentWindow |
| delegate:(SpeechRecognitionBubbleDelegate*)delegate |
| anchoredAt:(NSPoint)anchoredAt { |
| if ((self = [super initWithWindowNibPath:@"SpeechRecognitionBubble" |
| parentWindow:parentWindow |
| anchoredAt:anchoredAt])) { |
| DCHECK(delegate); |
| delegate_ = delegate; |
| displayMode_ = SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP; |
| } |
| return self; |
| } |
| |
| - (void)awakeFromNib { |
| [super awakeFromNib]; |
| InfoBubbleView* bubble = [self bubble]; |
| [bubble setArrowLocation:info_bubble::kTopLeft]; |
| NSSize arrowSize = NSMakeSize(0, info_bubble::kBubbleArrowHeight); |
| arrowSize = [bubble convertSize:arrowSize toView:nil]; |
| NSPoint anchorPoint = self.anchorPoint; |
| anchorPoint.y += arrowSize.height / 2.0; |
| self.anchorPoint = anchorPoint; |
| } |
| |
| - (IBAction)cancel:(id)sender { |
| delegate_->InfoBubbleButtonClicked(SpeechRecognitionBubble::BUTTON_CANCEL); |
| } |
| |
| - (IBAction)tryAgain:(id)sender { |
| delegate_->InfoBubbleButtonClicked(SpeechRecognitionBubble::BUTTON_TRY_AGAIN); |
| } |
| |
| - (IBAction)micSettings:(id)sender { |
| [[NSWorkspace sharedWorkspace] openFile: |
| @"/System/Library/PreferencePanes/Sound.prefPane"]; |
| } |
| |
| // Calculate the window dimensions to reflect the sum height and max width of |
| // all controls, with appropriate spacing between and around them. The returned |
| // size is in view coordinates. |
| - (NSSize)calculateContentSize { |
| [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_]; |
| [GTMUILocalizerAndLayoutTweaker sizeToFitView:tryAgainButton_]; |
| [GTMUILocalizerAndLayoutTweaker sizeToFitView:micSettingsButton_]; |
| NSSize cancelSize = [cancelButton_ bounds].size; |
| NSSize tryAgainSize = [tryAgainButton_ bounds].size; |
| CGFloat newHeight = cancelSize.height + kBubbleControlVerticalSpacing; |
| CGFloat newWidth = cancelSize.width; |
| if (![tryAgainButton_ isHidden]) |
| newWidth += tryAgainSize.width; |
| |
| // The size of the bubble in warm up mode is fixed to be the same as in |
| // recording mode, so from warm up it can transition to recording without any |
| // UI jank. |
| bool isWarmUp = (displayMode_ == |
| SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP); |
| |
| if (![iconImage_ isHidden]) { |
| NSSize size = [[iconImage_ image] size]; |
| if (isWarmUp) { |
| NSImage* volumeIcon = |
| ResourceBundle::GetSharedInstance().GetNativeImageNamed( |
| IDR_SPEECH_INPUT_MIC_EMPTY).ToNSImage(); |
| size = [volumeIcon size]; |
| } |
| newHeight += size.height; |
| newWidth = std::max(newWidth, size.width + 2 * kBubbleHorizontalMargin); |
| } |
| |
| if (![instructionLabel_ isHidden] || isWarmUp) { |
| [instructionLabel_ sizeToFit]; |
| NSSize textSize = [[instructionLabel_ cell] cellSize]; |
| NSRect boundsRect = NSMakeRect(0, 0, kInstructionLabelMaxWidth, |
| CGFLOAT_MAX); |
| NSSize multiLineSize = |
| [[instructionLabel_ cell] cellSizeForBounds:boundsRect]; |
| if (textSize.width > multiLineSize.width) |
| textSize = multiLineSize; |
| newHeight += textSize.height + kBubbleControlVerticalSpacing; |
| newWidth = std::max(newWidth, textSize.width); |
| } |
| |
| if (![micSettingsButton_ isHidden]) { |
| NSSize size = [micSettingsButton_ bounds].size; |
| newHeight += size.height; |
| newWidth = std::max(newWidth, size.width); |
| } |
| |
| return NSMakeSize(newWidth + 2 * kBubbleHorizontalMargin, |
| newHeight + 3 * kBubbleControlVerticalSpacing); |
| } |
| |
| // Position the controls within the given content area bounds. |
| - (void)layout:(NSSize)size { |
| int y = kBubbleControlVerticalSpacing; |
| |
| NSRect cancelRect = [cancelButton_ bounds]; |
| |
| if ([tryAgainButton_ isHidden]) { |
| cancelRect.origin.x = (size.width - NSWidth(cancelRect)) / 2; |
| } else { |
| NSRect tryAgainRect = [tryAgainButton_ bounds]; |
| cancelRect.origin.x = (size.width - NSWidth(cancelRect) - |
| NSWidth(tryAgainRect)) / 2; |
| tryAgainRect.origin.x = cancelRect.origin.x + NSWidth(cancelRect); |
| tryAgainRect.origin.y = y; |
| [tryAgainButton_ setFrame:tryAgainRect]; |
| } |
| cancelRect.origin.y = y; |
| |
| if (![cancelButton_ isHidden]) { |
| [cancelButton_ setFrame:cancelRect]; |
| y += NSHeight(cancelRect) + kBubbleControlVerticalSpacing; |
| } |
| |
| NSRect rect; |
| if (![micSettingsButton_ isHidden]) { |
| rect = [micSettingsButton_ bounds]; |
| rect.origin.x = (size.width - NSWidth(rect)) / 2; |
| rect.origin.y = y; |
| [micSettingsButton_ setFrame:rect]; |
| y += rect.size.height + kBubbleControlVerticalSpacing; |
| } |
| |
| if (![instructionLabel_ isHidden]) { |
| int spaceForIcon = 0; |
| if (![iconImage_ isHidden]) { |
| spaceForIcon = [[iconImage_ image] size].height + |
| kBubbleControlVerticalSpacing; |
| } |
| |
| rect = NSMakeRect(0, y, size.width, size.height - y - spaceForIcon - |
| kBubbleControlVerticalSpacing * 2); |
| [instructionLabel_ setFrame:rect]; |
| y = size.height - spaceForIcon - kBubbleControlVerticalSpacing; |
| } |
| |
| if (![iconImage_ isHidden]) { |
| rect.size = [[iconImage_ image] size]; |
| // In warm-up mode only the icon gets displayed so center it vertically. |
| if (displayMode_ == SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP) |
| y = (size.height - rect.size.height) / 2; |
| rect.origin.x = (size.width - NSWidth(rect)) / 2; |
| rect.origin.y = y; |
| [iconImage_ setFrame:rect]; |
| } |
| } |
| |
| - (void)updateLayout:(SpeechRecognitionBubbleBase::DisplayMode)mode |
| messageText:(const string16&)messageText |
| iconImage:(NSImage*)iconImage { |
| // The very first time this method is called, the child views would still be |
| // uninitialized and null. So we invoke [self window] first and that sets up |
| // the child views properly so we can do the layout calculations below. |
| NSWindow* window = [self window]; |
| displayMode_ = mode; |
| BOOL is_message = (mode == SpeechRecognitionBubbleBase::DISPLAY_MODE_MESSAGE); |
| BOOL is_recording = |
| (mode == SpeechRecognitionBubbleBase::DISPLAY_MODE_RECORDING); |
| BOOL is_warm_up = (mode == SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP); |
| [iconImage_ setHidden:is_message]; |
| [tryAgainButton_ setHidden:!is_message]; |
| [micSettingsButton_ setHidden:!is_message]; |
| [instructionLabel_ setHidden:!is_message && !is_recording]; |
| [cancelButton_ setHidden:is_warm_up]; |
| |
| // Get the right set of controls to be visible. |
| if (is_message) { |
| [instructionLabel_ setStringValue:base::SysUTF16ToNSString(messageText)]; |
| } else { |
| [iconImage_ setImage:iconImage]; |
| [instructionLabel_ setStringValue:l10n_util::GetNSString( |
| IDS_SPEECH_INPUT_BUBBLE_HEADING)]; |
| } |
| |
| NSSize newSize = [self calculateContentSize]; |
| [[self bubble] setFrameSize:newSize]; |
| |
| NSSize windowDelta = [[window contentView] convertSize:newSize toView:nil]; |
| NSRect newFrame = [window frame]; |
| newFrame.origin.y -= windowDelta.height - newFrame.size.height; |
| newFrame.size = windowDelta; |
| [window setFrame:newFrame display:YES]; |
| |
| [self layout:newSize]; // Layout all the child controls. |
| } |
| |
| - (void)windowWillClose:(NSNotification*)notification { |
| delegate_->InfoBubbleFocusChanged(); |
| } |
| |
| - (void)show { |
| [self showWindow:nil]; |
| } |
| |
| - (void)hide { |
| [[self window] orderOut:nil]; |
| } |
| |
| - (void)setImage:(NSImage*)image { |
| [iconImage_ setImage:image]; |
| } |
| |
| @end // implementation SpeechRecognitionWindowController |