| 1 | // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | package org.chromium.chrome.browser; |
| 6 | |
| 7 | import android.content.res.Resources; |
| 8 | import android.graphics.Point; |
| 9 | import android.graphics.RectF; |
| 10 | import android.text.TextUtils; |
| 11 | import android.view.Gravity; |
| 12 | import android.view.View; |
| 13 | import android.view.ViewGroup; |
| 14 | import android.widget.PopupWindow; |
| 15 | import android.widget.RelativeLayout; |
| 16 | import android.widget.TextView; |
| 17 | import org.chromium.base.ApiCompatibilityUtils; |
| 18 | import org.chromium.base.CalledByNative; |
| 19 | import org.chromium.chrome.R; |
| 20 | import org.chromium.content.browser.ContentViewCore; |
| 21 | import org.chromium.content.browser.RenderCoordinates; |
| 22 | |
| 23 | /** |
| 24 | * This class is an implementation of validation message bubble UI. |
| 25 | */ |
| 26 | class ValidationMessageBubble { |
| 27 | private PopupWindow mPopup; |
| 28 | |
| 29 | /** |
| 30 | * Creates a popup window to show the specified messages, and show it on |
| 31 | * the specified anchor rectangle. |
| 32 | * |
| 33 | * @param contentViewCore The ContentViewCore object to provide various |
| 34 | * information. |
| 35 | * @param anchorX Anchor position in the CSS unit. |
| 36 | * @param anchorY Anchor position in the CSS unit. |
| 37 | * @param anchorWidth Anchor size in the CSS unit. |
| 38 | * @param anchorHeight Anchor size in the CSS unit. |
| 39 | * @param mainText The main message. It will shown at the top of the popup |
| 40 | * window, and its font size is larger. |
| 41 | * @param subText The sub message. It will shown below the main message, and |
| 42 | * its font size is smaller. |
| 43 | */ |
| 44 | @CalledByNative |
| 45 | private static ValidationMessageBubble createAndShow( |
| 46 | ContentViewCore contentViewCore, int anchorX, int anchorY, |
| 47 | int anchorWidth, int anchorHeight, String mainText, String subText) { |
| 48 | final RectF anchorPixInScreen = makePixRectInScreen( |
| 49 | contentViewCore, anchorX, anchorY, anchorWidth, anchorHeight); |
| 50 | return new ValidationMessageBubble(contentViewCore, anchorPixInScreen, mainText, subText); |
| 51 | } |
| 52 | |
| 53 | private ValidationMessageBubble( |
| 54 | ContentViewCore contentViewCore, RectF anchor, String mainText, String subText) { |
| 55 | final ViewGroup root = (ViewGroup) View.inflate(contentViewCore.getContext(), |
| 56 | R.layout.validation_message_bubble, null); |
| 57 | mPopup = new PopupWindow(root); |
| 58 | updateTextViews(root, mainText, subText); |
| 59 | measure(contentViewCore.getRenderCoordinates()); |
| 60 | Point origin = adjustWindowPosition( |
| 61 | contentViewCore, (int) (anchor.centerX() - getAnchorOffset()), (int) anchor.bottom); |
| 62 | mPopup.showAtLocation( |
| 63 | contentViewCore.getContainerView(), Gravity.NO_GRAVITY, origin.x, origin.y); |
| 64 | } |
| 65 | |
| 66 | @CalledByNative |
| 67 | private void close() { |
| 68 | if (mPopup == null) return; |
| 69 | mPopup.dismiss(); |
| 70 | mPopup = null; |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Moves the popup window on the specified anchor rectangle. |
| 75 | * |
| 76 | * @param contentViewCore The ContentViewCore object to provide various |
| 77 | * information. |
| 78 | * @param anchorX Anchor position in the CSS unit. |
| 79 | * @param anchorY Anchor position in the CSS unit. |
| 80 | * @param anchorWidth Anchor size in the CSS unit. |
| 81 | * @param anchorHeight Anchor size in the CSS unit. |
| 82 | */ |
| 83 | @CalledByNative |
| 84 | private void setPositionRelativeToAnchor(ContentViewCore contentViewCore, |
| 85 | int anchorX, int anchorY, int anchorWidth, int anchorHeight) { |
| 86 | RectF anchor = makePixRectInScreen( |
| 87 | contentViewCore, anchorX, anchorY, anchorWidth, anchorHeight); |
| 88 | Point origin = adjustWindowPosition( |
| 89 | contentViewCore, (int) (anchor.centerX() - getAnchorOffset()), (int) anchor.bottom); |
| 90 | mPopup.update(origin.x, origin.y, mPopup.getWidth(), mPopup.getHeight()); |
| 91 | } |
| 92 | |
| 93 | private static RectF makePixRectInScreen(ContentViewCore contentViewCore, |
| 94 | int anchorX, int anchorY, int anchorWidth, int anchorHeight) { |
| 95 | final RenderCoordinates coordinates = contentViewCore.getRenderCoordinates(); |
| 96 | final float yOffset = getWebViewOffsetYPixInScreen(contentViewCore); |
| 97 | return new RectF( |
| 98 | coordinates.fromLocalCssToPix(anchorX), |
| 99 | coordinates.fromLocalCssToPix(anchorY) + yOffset, |
| 100 | coordinates.fromLocalCssToPix(anchorX + anchorWidth), |
| 101 | coordinates.fromLocalCssToPix(anchorY + anchorHeight) + yOffset); |
| 102 | } |
| 103 | |
| 104 | private static float getWebViewOffsetYPixInScreen(ContentViewCore contentViewCore) { |
| 105 | int[] location = new int[2]; |
| 106 | contentViewCore.getContainerView().getLocationOnScreen(location); |
| 107 | return location[1] + contentViewCore.getRenderCoordinates().getContentOffsetYPix(); |
| 108 | } |
| 109 | |
| 110 | private static void updateTextViews(ViewGroup root, String mainText, String subText) { |
| 111 | ((TextView) root.findViewById(R.id.main_text)).setText(mainText); |
| 112 | final TextView subTextView = (TextView) root.findViewById(R.id.sub_text); |
| 113 | if (!TextUtils.isEmpty(subText)) { |
| 114 | subTextView.setText(subText); |
| 115 | } else { |
| 116 | ((ViewGroup) subTextView.getParent()).removeView(subTextView); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | private void measure(RenderCoordinates coordinates) { |
| 121 | mPopup.setWindowLayoutMode( |
| 122 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
| 123 | mPopup.getContentView().setLayoutParams( |
| 124 | new RelativeLayout.LayoutParams( |
| 125 | RelativeLayout.LayoutParams.WRAP_CONTENT, |
| 126 | RelativeLayout.LayoutParams.WRAP_CONTENT)); |
| 127 | mPopup.getContentView().measure( |
| 128 | View.MeasureSpec.makeMeasureSpec(coordinates.getLastFrameViewportWidthPixInt(), |
| 129 | View.MeasureSpec.AT_MOST), |
| 130 | View.MeasureSpec.makeMeasureSpec(coordinates.getLastFrameViewportHeightPixInt(), |
| 131 | View.MeasureSpec.AT_MOST)); |
| 132 | } |
| 133 | |
| 134 | private float getAnchorOffset() { |
| 135 | final View root = mPopup.getContentView(); |
| 136 | final int width = root.getMeasuredWidth(); |
| 137 | final int arrowWidth = root.findViewById(R.id.arrow_image).getMeasuredWidth(); |
| 138 | return ApiCompatibilityUtils.isLayoutRtl(root) ? |
| 139 | (width * 3 / 4 - arrowWidth / 2) : (width / 4 + arrowWidth / 2); |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * This adjusts the position if the popup protrudes the web view. |
| 144 | */ |
| 145 | private Point adjustWindowPosition(ContentViewCore contentViewCore, int x, int y) { |
| 146 | final RenderCoordinates coordinates = contentViewCore.getRenderCoordinates(); |
| 147 | final int viewWidth = coordinates.getLastFrameViewportWidthPixInt(); |
| 148 | final int viewBottom = (int) getWebViewOffsetYPixInScreen(contentViewCore) + |
| 149 | coordinates.getLastFrameViewportHeightPixInt(); |
| 150 | final int width = mPopup.getContentView().getMeasuredWidth(); |
| 151 | final int height = mPopup.getContentView().getMeasuredHeight(); |
| 152 | if (x < 0) { |
| 153 | x = 0; |
| 154 | } else if (x + width > viewWidth) { |
| 155 | x = viewWidth - width; |
| 156 | } |
| 157 | if (y + height > viewBottom) { |
| 158 | y = viewBottom - height; |
| 159 | } |
| 160 | return new Point(x, y); |
| 161 | } |
| 162 | } |