chromium/chrome/browser/ui/views/editor_menu/editor_menu_controller_impl.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/editor_menu/editor_menu_controller_impl.h"

#include <memory>
#include <string_view>
#include <vector>

#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "ash/webui/settings/public/constants/setting.mojom.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/editor_menu/editor_manager_factory.h"
#include "chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.h"
#include "chrome/browser/ui/views/editor_menu/editor_menu_view.h"
#include "chrome/browser/ui/views/editor_menu/utils/editor_types.h"
#include "chromeos/components/editor_menu/public/cpp/preset_text_query.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/browser/browser_context.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/url_handler.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#else
#include "ash/public/cpp/new_window_delegate.h"
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

namespace chromeos::editor_menu {

EditorMenuControllerImpl::EditorMenuControllerImpl() = default;

EditorMenuControllerImpl::~EditorMenuControllerImpl() = default;

void EditorMenuControllerImpl::OnContextMenuShown(Profile* profile) {}

void EditorMenuControllerImpl::OnTextAvailable(
    const gfx::Rect& anchor_bounds,
    const std::string& selected_text,
    const std::string& surrounding_text) {
  if (!card_session_) {
    return;
  }

  card_session_->manager().GetEditorPanelContext(base::BindOnce(
      &EditorMenuControllerImpl::OnGetAnchorBoundsAndEditorContext,
      weak_factory_.GetWeakPtr(), anchor_bounds));
}

void EditorMenuControllerImpl::OnAnchorBoundsChanged(
    const gfx::Rect& anchor_bounds) {
  if (!editor_menu_widget_) {
    return;
  }

  auto* editor_menu_view = editor_menu_widget_->GetContentsView();
  if (views::IsViewClass<EditorMenuView>(editor_menu_view)) {
    views::AsViewClass<EditorMenuView>(editor_menu_view)
        ->UpdateBounds(anchor_bounds);
  } else if (views::IsViewClass<EditorMenuPromoCardView>(editor_menu_view)) {
    views::AsViewClass<EditorMenuPromoCardView>(editor_menu_view)
        ->UpdateBounds(anchor_bounds);
  }
}

void EditorMenuControllerImpl::OnDismiss(bool is_other_command_executed) {
  if (editor_menu_widget_ && !editor_menu_widget_->IsActive()) {
    editor_menu_widget_.reset();
  }
}

void EditorMenuControllerImpl::OnSettingsButtonPressed() {
  GURL setting_url = GURL(base::StrCat(
      {"chrome://os-settings/",
       chromeos::features::IsMagicBoostEnabled()
           ? chromeos::settings::mojom::kSystemPreferencesSectionPath
           : chromeos::settings::mojom::kInputSubpagePath,
       "?settingId=",
       base::NumberToString(
           static_cast<int>(chromeos::settings::mojom::Setting::kShowOrca))}));
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  chromeos::LacrosService* service = chromeos::LacrosService::Get();
  DCHECK(service->IsAvailable<crosapi::mojom::UrlHandler>());

  service->GetRemote<crosapi::mojom::UrlHandler>()->OpenUrl(setting_url);
#elif BUILDFLAG(IS_CHROMEOS_ASH)
  ash::NewWindowDelegate::GetInstance()->OpenUrl(
      setting_url, ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      ash::NewWindowDelegate::Disposition::kNewForegroundTab);
#endif
}

void EditorMenuControllerImpl::OnChipButtonPressed(
    std::string_view text_query_id) {
  if (!card_session_) {
    return;
  }

  DisableEditorMenu();
  card_session_->manager().StartEditingFlowWithPreset(
      std::string(text_query_id));
}

void EditorMenuControllerImpl::OnTextfieldArrowButtonPressed(
    std::u16string_view text) {
  if (text.empty() || !card_session_) {
    return;
  }

  DisableEditorMenu();
  card_session_->manager().StartEditingFlowWithFreeform(
      base::UTF16ToUTF8(text));
}

void EditorMenuControllerImpl::OnPromoCardWidgetClosed(
    views::Widget::ClosedReason closed_reason) {
  if (!card_session_) {
    return;
  }

  switch (closed_reason) {
    case views::Widget::ClosedReason::kAcceptButtonClicked:
      card_session_->manager().StartEditingFlow();
      break;
    case views::Widget::ClosedReason::kCloseButtonClicked:
      card_session_->manager().OnPromoCardDeclined();
      break;
    default:
      card_session_->manager().OnPromoCardDismissed();
      break;
  }

  OnEditorCardHidden();
}

void EditorMenuControllerImpl::OnEditorMenuVisibilityChanged(bool visible) {
  if (!card_session_) {
    return;
  }

  card_session_->manager().OnEditorMenuVisibilityChanged(visible);

  if (!visible) {
    OnEditorCardHidden();
  }
}

void EditorMenuControllerImpl::SetBrowserContext(
    content::BrowserContext* context) {
  card_session_ =
      std::make_unique<EditorCardSession>(this, CreateEditorManager(context));
}

void EditorMenuControllerImpl::DismissCard() {
  if (editor_menu_widget_) {
    editor_menu_widget_.reset();
  }
}

void EditorMenuControllerImpl::TryCreatingEditorSession() {
  if (!card_session_) {
    return;
  }
  card_session_->manager().RequestCacheContext();
}

void EditorMenuControllerImpl::LogEditorMode(const EditorMode& editor_mode) {
  if (!card_session_) {
    return;
  }
  card_session_->manager().LogEditorMode(editor_mode);
}

void EditorMenuControllerImpl::GetEditorContext(
    base::OnceCallback<void(const EditorContext&)> callback) {
  if (!card_session_) {
    return;
  }
  card_session_->manager().GetEditorPanelContext(
      base::BindOnce(&EditorMenuControllerImpl::OnGetEditorContext,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void EditorMenuControllerImpl::OnGetAnchorBoundsAndEditorContextForTesting(
    const gfx::Rect& anchor_bounds,
    const EditorContext& context) {
  OnGetAnchorBoundsAndEditorContext(anchor_bounds, std::move(context));
}

void EditorMenuControllerImpl::OnGetEditorContext(
    base::OnceCallback<void(const EditorContext&)> callback,
    const EditorContext& context) {
  std::move(callback).Run(context);
}

void EditorMenuControllerImpl::OnGetAnchorBoundsAndEditorContext(
    const gfx::Rect& anchor_bounds,
    const EditorContext& context) {
  switch (context.mode) {
    case EditorMode::kHardBlocked:
    case EditorMode::kSoftBlocked:
      break;
    case EditorMode::kWrite:
      editor_menu_widget_ = EditorMenuView::CreateWidget(
          EditorMenuMode::kWrite, PresetTextQueries(), anchor_bounds, this);
      editor_menu_widget_->ShowInactive();
      break;
    case EditorMode::kRewrite:
      editor_menu_widget_ = EditorMenuView::CreateWidget(
          EditorMenuMode::kRewrite, context.preset_queries, anchor_bounds,
          this);
      editor_menu_widget_->ShowInactive();
      break;
    case EditorMode::kPromoCard:
      editor_menu_widget_ =
          EditorMenuPromoCardView::CreateWidget(anchor_bounds, this);
      editor_menu_widget_->ShowInactive();
      break;
  }
  if (card_session_ != nullptr && context.mode != EditorMode::kSoftBlocked &&
      context.mode != EditorMode::kHardBlocked) {
    card_session_->manager().LogEditorMode(context.mode);
  }
}

void EditorMenuControllerImpl::OnEditorCardHidden() {
  // The currently visible card is closing and being removed from the user's
  // view, the EditorCardSession has ended.
  if (card_session_) {
    card_session_.reset();
  }
}

void EditorMenuControllerImpl::DisableEditorMenu() {
  auto* editor_menu_view = editor_menu_widget_->GetContentsView();
  if (views::IsViewClass<EditorMenuView>(editor_menu_view)) {
    views::AsViewClass<EditorMenuView>(editor_menu_view)->DisableMenu();
  }
}

base::WeakPtr<EditorMenuControllerImpl> EditorMenuControllerImpl::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

EditorMenuControllerImpl::EditorCardSession::EditorCardSession(
    EditorMenuControllerImpl* controller,
    std::unique_ptr<EditorManager> editor_manager)
    : controller_(controller), manager_(std::move(editor_manager)) {
  manager_->AddObserver(this);
}

EditorMenuControllerImpl::EditorCardSession::~EditorCardSession() {
  manager_->RemoveObserver(this);
}

void EditorMenuControllerImpl::EditorCardSession::OnEditorModeChanged(
    const EditorMode& mode) {
  if (mode == EditorMode::kHardBlocked || mode == EditorMode::kSoftBlocked) {
    controller_->DismissCard();
  }
}

EditorManager& EditorMenuControllerImpl::EditorCardSession::manager() {
  return *manager_;
}

}  // namespace chromeos::editor_menu