1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
// 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/compose/compose_dialog_view.h"
#include <vector>
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
#include "components/compose/core/browser/config.h"
#include "components/renderer_context_menu/context_menu_delegate.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/view_class_properties.h"
namespace {
gfx::Rect FallbackAtBottomShiftUpToPermanentPosition(
gfx::Rect best_location,
const gfx::Rect& screen_work_area,
const gfx::Size& widget_size) {
// Make sure all sizes of the view will fit in the work area
best_location.AdjustToFit(screen_work_area);
// Now make best_location reflect the actual size of the widget.
best_location.set_size(widget_size);
return best_location;
}
gfx::Rect FallbackAtBottomShiftMinimumForVisibility(gfx::Rect best_location) {
// Just use the initial location anchored at bottom left directly.
return best_location;
}
gfx::Rect FallbackCenterOnFormFieldStrategy(const gfx::Rect& screen_work_are,
const gfx::Size& widget_size,
const gfx::Rect& anchor_rect) {
gfx::Rect widget_rect;
widget_rect.set_size(widget_size);
gfx::Vector2d translation = anchor_rect.CenterPoint().OffsetFromOrigin() -
widget_rect.CenterPoint().OffsetFromOrigin();
widget_rect.Offset(translation);
return widget_rect;
}
} // namespace
DEFINE_ELEMENT_IDENTIFIER_VALUE(kComposeWebviewElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(ComposeDialogView, kComposeDialogId);
// static
gfx::Rect ComposeDialogView::CalculateBubbleBounds(
gfx::Rect screen_work_area,
gfx::Size widget_size,
gfx::Rect anchor_bounds,
std::optional<gfx::Rect> parent_bounds) {
// Widget can't be smaller than a minimum size.
widget_size.SetToMax({kComposeMaxDialogWidthPx, kComposeMinDialogHeightPx});
// We don't want to render anything within `padding` pixels of the edge of the
// screen work area.
screen_work_area.Inset(kComposeDialogWorkAreaPadding);
// If the param to stay in the window bounds is true, and the window is large
// enough, use that as the work area instead.
if (compose::GetComposeConfig().stay_in_window_bounds &&
parent_bounds.has_value()) {
if ((widget_size.width() <= parent_bounds->width() &&
widget_size.height() <= parent_bounds->height())) {
screen_work_area = parent_bounds.value();
}
}
// We don't want to render anything within `padding` pixels of the edge of the
// anchor rect. But we will if we have to (due to AdjustToFit below).
anchor_bounds.Outset(kComposeDialogAnchorPadding);
// Available space measures the distance from each side of the padded work
// area to the edge of the padded anchor (plus padding).
gfx::Insets available_space = screen_work_area.InsetsFrom(anchor_bounds);
// Ideally we render at the bottom left of the anchor. If the dialog would be
// offscreen, we reposition it.
gfx::Rect best_location(
anchor_bounds.bottom_left(),
gfx::Size(kComposeMaxDialogWidthPx, kComposeMaxDialogHeightPx));
bool space_below = available_space.bottom() >= kComposeMaxDialogHeightPx;
bool space_above = available_space.top() >= kComposeMaxDialogHeightPx;
// the position of the dialog is determined by finding a suitable location for
// a maximum-sized ComposeDialogView, so that we know that the dialog doesn't
// have to switch sides as it resizes. If the dialog is smaller than max, we
// need to apply an alignment within that larger rectangle.
if (!space_below) {
// Not enough room in the preferred location. Try above the anchor.
if (space_above) {
// If it is laid out above the anchor rect then it
// will be bottom-aligned.
best_location.set_y(anchor_bounds.y() - widget_size.height());
} else {
// If not enough space above or below, try one of the following backup
// strategies:
switch (compose::GetComposeConfig().positioning_strategy) {
case compose::DialogFallbackPositioningStrategy::kCenterOnAnchorRect:
best_location = FallbackCenterOnFormFieldStrategy(
screen_work_area, widget_size, anchor_bounds);
break;
case compose::DialogFallbackPositioningStrategy::kShiftUpUntilOnscreen:
best_location =
FallbackAtBottomShiftMinimumForVisibility(best_location);
break;
case compose::DialogFallbackPositioningStrategy::
kShiftUpUntilMaxSizeIsOnscreen:
default:
best_location = FallbackAtBottomShiftUpToPermanentPosition(
best_location, screen_work_area, widget_size);
break;
}
}
}
// Always use the size that the WebUI wants to be in the end.
best_location.set_size(widget_size);
// Always remain completely on screen within the provided insets.
best_location.AdjustToFit(screen_work_area);
return best_location;
}
ComposeDialogView::~ComposeDialogView() = default;
ComposeDialogView::ComposeDialogView(
View* anchor_view,
std::unique_ptr<WebUIContentsWrapperT<ComposeUntrustedUI>> bubble_wrapper,
const gfx::Rect& anchor_bounds,
views::BubbleBorder::Arrow anchor_position)
: WebUIBubbleDialogView(anchor_view,
bubble_wrapper->GetWeakPtr(),
anchor_bounds,
anchor_position),
anchor_bounds_(anchor_bounds),
bubble_wrapper_(std::move(bubble_wrapper)) {
SetProperty(views::kElementIdentifierKey, kComposeDialogId);
web_view()->SetProperty(views::kElementIdentifierKey,
kComposeWebviewElementId);
}
void ComposeDialogView::OnBeforeBubbleWidgetInit(
views::Widget::InitParams* params,
views::Widget* widget) const {
WebUIBubbleDialogView::OnBeforeBubbleWidgetInit(params, widget);
#if BUILDFLAG(IS_LINUX)
// In linux, windows may be clipped to their anchors' bounds,
// resulting in visual errors, unless they use accelerated rendering. See
// crbug.com/1445770 for details.
params->use_accelerated_widget_override = true;
#endif
}
gfx::Rect ComposeDialogView::GetBubbleBounds() {
gfx::Size widget_size = BubbleDialogDelegateView::GetBubbleBounds().size();
std::optional<gfx::Rect> parent_bounds;
if (GetWidget()->parent()) {
parent_bounds = GetWidget()->parent()->GetWindowBoundsInScreen();
}
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestView(
GetAnchorView()->GetWidget()->GetNativeView());
gfx::Rect screen_work_area = display.work_area();
return CalculateBubbleBounds(screen_work_area, widget_size, anchor_bounds_,
parent_bounds);
}
bool ComposeDialogView::HandleContextMenu(
content::RenderFrameHost& render_frame_host,
const content::ContextMenuParams& params) {
ContextMenuDelegate* menu_delegate = ContextMenuDelegate::FromWebContents(
content::WebContents::FromRenderFrameHost(&render_frame_host));
DCHECK(menu_delegate);
std::unique_ptr<RenderViewContextMenuBase> menu =
menu_delegate->BuildMenu(render_frame_host, params);
// Remove everything that is not copy, paste, or cut or spellcheck
// suggestions.
std::vector<int> command_ids;
for (size_t index = 0; index < menu->menu_model().GetItemCount(); index++) {
int command_id = menu->menu_model().GetCommandIdAt(index);
if ((command_id < IDC_CONTENT_CONTEXT_COPY ||
command_id > IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE) &&
(command_id < IDC_SPELLCHECK_SUGGESTION_0 ||
command_id > IDC_SPELLCHECK_SUGGESTION_LAST) &&
command_id != IDC_CONTENT_CONTEXT_INSPECTELEMENT && command_id > 0) {
command_ids.push_back(command_id);
}
}
for (int command_id : command_ids) {
menu->RemoveMenuItem(command_id);
}
menu->RemoveAdjacentSeparators();
// There's no method to remove the final separator if there is one, so we have
// to hack around it.
menu->RemoveSeparatorBeforeMenuItem(IDC_CONTENT_CONTEXT_INSPECTELEMENT);
menu->RemoveMenuItem(IDC_CONTENT_CONTEXT_INSPECTELEMENT);
// Only show the menu if there are items in it.
if (menu->menu_model().GetItemCount() > 0) {
menu_delegate->ShowMenu(std::move(menu));
}
return true;
}
base::WeakPtr<ComposeDialogView> ComposeDialogView::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
BEGIN_METADATA(ComposeDialogView)
END_METADATA
|