Avoid potential negative array index access to cached text.
[LibreOffice.git] / android / source / src / java / org / libreoffice / overlay / CalcHeadersView.java
blob98af7a9554e73b90c661c07d6d2bb6b17fa6d496
1 package org.libreoffice.overlay;
3 import android.content.Context;
4 import android.graphics.Canvas;
5 import android.graphics.PointF;
6 import android.graphics.RectF;
7 import androidx.core.view.GestureDetectorCompat;
8 import android.util.AttributeSet;
9 import android.view.GestureDetector.SimpleOnGestureListener;
10 import android.view.MotionEvent;
11 import android.view.View;
12 import android.widget.PopupWindow;
14 import org.json.JSONException;
15 import org.json.JSONObject;
16 import org.libreoffice.LOEvent;
17 import org.libreoffice.LOKitShell;
18 import org.libreoffice.LibreOfficeMainActivity;
19 import org.libreoffice.canvas.CalcHeaderCell;
20 import org.libreoffice.kit.Document;
21 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
22 import org.mozilla.gecko.gfx.LayerView;
24 import java.util.ArrayList;
25 import java.util.Collections;
27 import static org.libreoffice.SearchController.addProperty;
29 public class CalcHeadersView extends View {
30 private static final String LOGTAG = CalcHeadersView.class.getSimpleName();
32 private boolean mInitialized;
33 private LayerView mLayerView;
34 private boolean mIsRow; // true if this is for row headers, false for column
35 private ArrayList<String> mLabels;
36 private ArrayList<Float> mDimens;
37 private RectF mCellCursorRect;
38 private boolean mPendingRowOrColumnSelectionToShowUp;
39 private GestureDetectorCompat mDetector;
40 private PopupWindow mPopupWindow;
41 private int mPrevScrollIndex = -1;
43 public CalcHeadersView(Context context) {
44 super(context);
47 public CalcHeadersView(Context context, AttributeSet attrs) {
48 super(context, attrs);
51 public CalcHeadersView(Context context, AttributeSet attrs, int defStyleAttr) {
52 super(context, attrs, defStyleAttr);
55 public void initialize(LayerView layerView, boolean isRow) {
56 if (!mInitialized) {
57 mLayerView = layerView;
58 mIsRow = isRow;
60 LOKitShell.getMainHandler().post(new Runnable() {
61 @Override
62 public void run() {
63 mDetector = new GestureDetectorCompat(getContext(), new HeaderGestureListener());
65 });
67 setOnTouchListener(new View.OnTouchListener() {
68 @Override
69 public boolean onTouch(View v, MotionEvent event) {
70 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
71 mPrevScrollIndex = -1; // clear mPrevScrollIndex to default
73 return mDetector.onTouchEvent(event);
75 });
77 mInitialized = true;
81 @Override
82 protected void onDraw(Canvas canvas) {
83 super.onDraw(canvas);
84 if (mInitialized && mDimens != null && mLabels != null) {
85 updateHeaders(canvas);
89 private void updateHeaders(Canvas canvas) {
90 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
91 float zoom = metrics.getZoomFactor();
92 PointF origin = metrics.getOrigin();
94 // Draw headers
95 boolean inRangeOfVisibleHeaders = false; // a helper variable for skipping unnecessary onDraw()'s
96 float top,bottom,left,right;
97 for (int i = 1; i < mLabels.size(); i++) {
98 if (mDimens.get(i).equals(mDimens.get(i-1))) continue;
99 if (mIsRow) {
100 top = -origin.y + zoom*mDimens.get(i-1);
101 bottom = -origin.y + zoom*mDimens.get(i);
102 if (top <= getHeight() && bottom >= 0) {
103 inRangeOfVisibleHeaders = true;
104 boolean isSelected = mCellCursorRect != null && bottom > mCellCursorRect.top - origin.y && top < mCellCursorRect.bottom - origin.y;
105 new CalcHeaderCell(0f, top, getWidth(), bottom - top, mLabels.get(i), isSelected).onDraw(canvas);
106 } else {
107 if (inRangeOfVisibleHeaders) {
108 break;
111 } else {
112 left = -origin.x + zoom*mDimens.get(i-1);
113 right = -origin.x + zoom*mDimens.get(i);
114 if (left <= getWidth() && right >= 0) {
115 boolean isSelected = mCellCursorRect != null && right > mCellCursorRect.left - origin.x && left < mCellCursorRect.right - origin.x;
116 new CalcHeaderCell(left, 0f, right - left, getHeight(), mLabels.get(i), isSelected).onDraw(canvas);
117 } else {
118 if (inRangeOfVisibleHeaders) {
119 break;
127 * Handle a single tap event on a header cell.
128 * Selects whole row/column.
130 private void highlightRowOrColumn(PointF point, boolean shift) {
131 int index = getIndexFromPointOfTouch(point);
132 try {
133 JSONObject rootJson = new JSONObject();
134 if (shift) {
135 addProperty(rootJson, "Modifier", "unsigned short",
136 String.valueOf(Document.KEYBOARD_MODIFIER_SHIFT));
137 } else {
138 addProperty(rootJson, "Modifier", "unsigned short", "0");
140 if (mIsRow) {
141 addProperty(rootJson, "Row", "unsigned short", String.valueOf(index));
142 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SelectRow", rootJson.toString()));
143 } else {
144 addProperty(rootJson, "Col", "unsigned short", String.valueOf(index));
145 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SelectColumn", rootJson.toString()));
147 } catch (JSONException e) {
148 e.printStackTrace();
150 // At this point, InvalidationHandler.java will have received two callbacks.
151 // One is for text selection (first) and the other for cell selection (second).
152 // The second will override the first on headers which is not wanted.
153 // setPendingRowOrColumnSelectionToShowUp(true) will skip the second call.
154 setPendingRowOrColumnSelectionToShowUp(true);
157 public int getIndexFromPointOfTouch(PointF point) {
158 int searchedIndex, index;
159 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
160 float zoom = metrics.getZoomFactor();
161 PointF origin = metrics.getOrigin();
162 if (mIsRow) {
163 searchedIndex = Collections.binarySearch(mDimens, (point.y+origin.y)/zoom);
164 } else {
165 searchedIndex = Collections.binarySearch(mDimens, (point.x+origin.x)/zoom);
167 // converting searched index to real index on headers
168 if (searchedIndex < 0) {
169 index = - searchedIndex - 2;
170 } else {
171 index = searchedIndex;
173 return index;
176 public void setPendingRowOrColumnSelectionToShowUp(boolean b) {
177 mPendingRowOrColumnSelectionToShowUp = b;
180 public boolean pendingRowOrColumnSelectionToShowUp() {
181 return mPendingRowOrColumnSelectionToShowUp;
184 public void setHeaders(ArrayList<String> labels, ArrayList<Float> dimens) {
185 mLabels = labels;
186 mDimens = dimens;
189 public void setHeaderSelection(RectF cellCursorRect) {
190 mCellCursorRect = cellCursorRect;
193 public void showHeaderPopup(PointF point) {
194 if (mPopupWindow == null ||
195 !LibreOfficeMainActivity.isExperimentalMode()) return;
196 if (mIsRow) {
197 mPopupWindow.showAsDropDown(this, getWidth()*3/2, -getHeight()+(int)point.y);
198 } else {
199 mPopupWindow.showAsDropDown(this, (int)point.x, getHeight()/2);
203 public void dismissPopupWindow() {
204 if (mPopupWindow == null) return;
205 mPopupWindow.dismiss();
208 public void setHeaderPopupWindow(PopupWindow popupWindow) {
209 if (mPopupWindow != null) return;
210 mPopupWindow = popupWindow;
213 public void sendOptimalLengthRequest(String text) {
214 JSONObject rootJson = new JSONObject();
215 if (mIsRow) {
216 try {
217 addProperty(rootJson, "aExtraHeight", "unsigned short", text);
218 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SetOptimalRowHeight", rootJson.toString()));
219 } catch (JSONException ex) {
220 ex.printStackTrace();
222 } else {
223 try {
224 addProperty(rootJson, "aExtraWidth", "unsigned short", text);
225 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SetOptimalColumnWidth", rootJson.toString()));
226 } catch (JSONException ex) {
227 ex.printStackTrace();
232 private class HeaderGestureListener extends SimpleOnGestureListener {
234 @Override
235 public boolean onDown(MotionEvent e) {
236 return true;
239 @Override
240 public boolean onSingleTapConfirmed(MotionEvent e) {
241 PointF pointOfTouch = new PointF(e.getX(), e.getY());
242 highlightRowOrColumn(pointOfTouch, false);
243 showHeaderPopup(pointOfTouch);
244 return true;
247 @Override
248 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
249 PointF point2 = new PointF(e2.getX(), e2.getY());
250 if (mPrevScrollIndex != getIndexFromPointOfTouch(point2)) {
251 mPrevScrollIndex = getIndexFromPointOfTouch(point2);
252 highlightRowOrColumn(point2, true);
253 dismissPopupWindow();
254 showHeaderPopup(point2);
256 return true;
259 @Override
260 public boolean onDoubleTap(MotionEvent e) {
261 PointF pointOfTouch = new PointF(e.getX(), e.getY());
262 highlightRowOrColumn(pointOfTouch, false);
263 if (mIsRow) {
264 JSONObject rootJson = new JSONObject();
265 try {
266 addProperty(rootJson, "aExtraHeight", "unsigned short", String.valueOf(0));
267 } catch (JSONException ex) {
268 ex.printStackTrace();
270 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,".uno:SetOptimalRowHeight", rootJson.toString()));
271 } else {
272 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,".uno:SetOptimalColumnWidthDirect"));
274 showHeaderPopup(pointOfTouch);
275 return true;