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
) {
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
) {
57 mLayerView
= layerView
;
60 LOKitShell
.getMainHandler().post(new Runnable() {
63 mDetector
= new GestureDetectorCompat(getContext(), new HeaderGestureListener());
67 setOnTouchListener(new View
.OnTouchListener() {
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
);
82 protected void onDraw(Canvas 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();
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;
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
);
107 if (inRangeOfVisibleHeaders
) {
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
);
118 if (inRangeOfVisibleHeaders
) {
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
);
133 JSONObject rootJson
= new JSONObject();
135 addProperty(rootJson
, "Modifier", "unsigned short",
136 String
.valueOf(Document
.KEYBOARD_MODIFIER_SHIFT
));
138 addProperty(rootJson
, "Modifier", "unsigned short", "0");
141 addProperty(rootJson
, "Row", "unsigned short", String
.valueOf(index
));
142 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:SelectRow", rootJson
.toString()));
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
) {
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();
163 searchedIndex
= Collections
.binarySearch(mDimens
, (point
.y
+origin
.y
)/zoom
);
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;
171 index
= searchedIndex
;
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
) {
189 public void setHeaderSelection(RectF cellCursorRect
) {
190 mCellCursorRect
= cellCursorRect
;
193 public void showHeaderPopup(PointF point
) {
194 if (mPopupWindow
== null ||
195 !LibreOfficeMainActivity
.isExperimentalMode()) return;
197 mPopupWindow
.showAsDropDown(this, getWidth()*3/2, -getHeight()+(int)point
.y
);
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();
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();
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
{
235 public boolean onDown(MotionEvent e
) {
240 public boolean onSingleTapConfirmed(MotionEvent e
) {
241 PointF pointOfTouch
= new PointF(e
.getX(), e
.getY());
242 highlightRowOrColumn(pointOfTouch
, false);
243 showHeaderPopup(pointOfTouch
);
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
);
260 public boolean onDoubleTap(MotionEvent e
) {
261 PointF pointOfTouch
= new PointF(e
.getX(), e
.getY());
262 highlightRowOrColumn(pointOfTouch
, false);
264 JSONObject rootJson
= new JSONObject();
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()));
272 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
,".uno:SetOptimalColumnWidthDirect"));
274 showHeaderPopup(pointOfTouch
);