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 | } |