blob: 4b4f70f0aa967ca7728fbb6bcff9c77215c26438 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/cocoa/text_services_context_menu.h"
#import <AppKit/AppKit.h>
#include <utility>
#include "base/strings/sys_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/strings/grit/ui_strings.h"
namespace {
NSSpeechSynthesizer* SpeechSynthesizer() {
static NSSpeechSynthesizer* speech_synthesizer =
[[NSSpeechSynthesizer alloc] initWithVoice:nil];
return speech_synthesizer;
}
// Returns the TextDirection associated associated with the given BiDi
// |command_id|.
base::i18n::TextDirection GetTextDirectionFromCommandId(int command_id) {
switch (command_id) {
case ui::TextServicesContextMenu::kWritingDirectionDefault:
return base::i18n::UNKNOWN_DIRECTION;
case ui::TextServicesContextMenu::kWritingDirectionLtr:
return base::i18n::LEFT_TO_RIGHT;
case ui::TextServicesContextMenu::kWritingDirectionRtl:
return base::i18n::RIGHT_TO_LEFT;
default:
NOTREACHED();
return base::i18n::UNKNOWN_DIRECTION;
}
}
} // namespace
namespace ui {
TextServicesContextMenu::TextServicesContextMenu(Delegate* delegate)
: speech_submenu_model_(this),
bidi_submenu_model_(this),
delegate_(delegate) {
DCHECK(delegate);
speech_submenu_model_.AddItemWithStringId(kSpeechStartSpeaking,
IDS_SPEECH_START_SPEAKING_MAC);
speech_submenu_model_.AddItemWithStringId(kSpeechStopSpeaking,
IDS_SPEECH_STOP_SPEAKING_MAC);
bidi_submenu_model_.AddCheckItemWithStringId(
kWritingDirectionDefault, IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT);
bidi_submenu_model_.AddCheckItemWithStringId(
kWritingDirectionLtr, IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR);
bidi_submenu_model_.AddCheckItemWithStringId(
kWritingDirectionRtl, IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL);
}
// A note about the Speech submenu.
//
// All standard AppKit implementations of `-(IBAction)startSpeaking:(id)sender`
// and `-(IBAction)stopSpeaking:(id)sender` funnel into messages to
// `NSApplication`:
//
// @interface NSApplication ()
// - (void)speakString:(NSString*)string;
// - (IBAction)stopSpeaking:(id)sender;
// - (BOOL)isSpeaking;
// @end
//
// However, it is an explicit decision to not use these messages, and to keep an
// independent `NSSpeechSynthesizer`, as Chromium tries to avoid the use of SPI
// when it's reasonably straightforward to do so. This does mean that speech
// initiated within Chromium doesn't interoperate with speech initiated with
// any native controls (if there are any left) via this submenu.
void TextServicesContextMenu::SpeakText(const std::u16string& text) {
if (IsSpeaking())
[SpeechSynthesizer() stopSpeaking];
[SpeechSynthesizer() startSpeakingString:base::SysUTF16ToNSString(text)];
}
void TextServicesContextMenu::StopSpeaking() {
[SpeechSynthesizer() stopSpeaking];
}
bool TextServicesContextMenu::IsSpeaking() {
return SpeechSynthesizer().speaking;
}
void TextServicesContextMenu::AppendToContextMenu(SimpleMenuModel* model) {
model->AddSeparator(NORMAL_SEPARATOR);
model->AddSubMenuWithStringId(kSpeechMenu, IDS_SPEECH_MAC,
&speech_submenu_model_);
}
void TextServicesContextMenu::AppendEditableItems(SimpleMenuModel* model) {
// MacOS provides a contextual menu to set writing direction for BiDi
// languages. This functionality is exposed as a keyboard shortcut on
// Windows and Linux.
model->AddSubMenuWithStringId(kWritingDirectionMenu,
IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU,
&bidi_submenu_model_);
}
bool TextServicesContextMenu::SupportsCommand(int command_id) const {
switch (command_id) {
case kWritingDirectionMenu:
case kWritingDirectionDefault:
case kWritingDirectionLtr:
case kWritingDirectionRtl:
case kSpeechMenu:
case kSpeechStartSpeaking:
case kSpeechStopSpeaking:
return true;
}
return false;
}
bool TextServicesContextMenu::IsCommandIdChecked(int command_id) const {
switch (command_id) {
case kWritingDirectionDefault:
case kWritingDirectionLtr:
case kWritingDirectionRtl:
return delegate_->IsTextDirectionChecked(
GetTextDirectionFromCommandId(command_id));
case kSpeechStartSpeaking:
case kSpeechStopSpeaking:
return false;
}
NOTREACHED();
return false;
}
bool TextServicesContextMenu::IsCommandIdEnabled(int command_id) const {
switch (command_id) {
case kSpeechMenu:
case kWritingDirectionMenu:
return true;
case kWritingDirectionDefault:
case kWritingDirectionLtr:
case kWritingDirectionRtl:
return delegate_->IsTextDirectionEnabled(
GetTextDirectionFromCommandId(command_id));
case kSpeechStartSpeaking:
return true;
case kSpeechStopSpeaking:
return IsSpeaking();
}
NOTREACHED();
return false;
}
void TextServicesContextMenu::ExecuteCommand(int command_id, int event_flags) {
switch (command_id) {
case kWritingDirectionDefault:
case kWritingDirectionLtr:
case kWritingDirectionRtl:
delegate_->UpdateTextDirection(GetTextDirectionFromCommandId(command_id));
break;
case kSpeechStartSpeaking:
SpeakText(delegate_->GetSelectedText());
break;
case kSpeechStopSpeaking:
StopSpeaking();
break;
default:
NOTREACHED();
}
}
} // namespace ui