Avoid potential negative array index access to cached text.
[LibreOffice.git] / android / source / src / java / org / libreoffice / FontController.java
blob72f35d8b42d8d28c8a1d046abd9fc0211745b13f
1 package org.libreoffice;
3 import android.graphics.Color;
4 import android.graphics.Rect;
5 import com.google.android.material.bottomsheet.BottomSheetBehavior;
6 import androidx.recyclerview.widget.GridLayoutManager;
7 import androidx.recyclerview.widget.RecyclerView;
8 import android.view.View;
9 import android.widget.AdapterView;
10 import android.widget.ArrayAdapter;
11 import android.widget.Button;
12 import android.widget.ImageButton;
13 import android.widget.LinearLayout;
14 import android.widget.RelativeLayout;
15 import android.widget.Spinner;
17 import org.json.JSONArray;
18 import org.json.JSONException;
19 import org.json.JSONObject;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
25 public class FontController implements AdapterView.OnItemSelectedListener {
27 /** -1 as value in ".uno:Color" et al. means "automatic color"/no color set. */
28 private static final int COLOR_AUTO = -1;
30 private boolean mFontNameSpinnerSet = false;
31 private boolean mFontSizeSpinnerSet = false;
32 private final LibreOfficeMainActivity mActivity;
33 private final ArrayList<String> mFontList = new ArrayList<>();
34 private final ArrayList<String> mFontSizes = new ArrayList<>();
35 private final HashMap<String, ArrayList<String>> mAllFontSizes = new HashMap<>();
37 private String mCurrentFontSelected = null;
38 private String mCurrentFontSizeSelected = null;
40 public FontController(LibreOfficeMainActivity activity) {
41 mActivity = activity;
43 private BottomSheetBehavior colorPickerBehavior;
44 private BottomSheetBehavior backColorPickerBehavior;
45 private BottomSheetBehavior toolBarBottomBehavior;
46 private ColorPickerAdapter colorPickerAdapter;
47 private ColorPickerAdapter backColorPickerAdapter;
49 final ColorPaletteListener colorPaletteListener = new ColorPaletteListener() {
50 @Override
51 public void applyColor(int color) {
52 sendFontColorChange(color, false);
55 @Override
56 public void updateColorPickerPosition(int color) {
57 if (colorPickerAdapter == null) {
58 return;
60 if (color == COLOR_AUTO) {
61 colorPickerAdapter.unselectColors();
62 changeFontColorBoxColor(Color.TRANSPARENT);
63 return;
65 final int colorWithAlpha = color | 0xFF000000;
66 colorPickerAdapter.findSelectedTextColor(colorWithAlpha);
67 changeFontColorBoxColor(colorWithAlpha);
71 final ColorPaletteListener backColorPaletteListener = new ColorPaletteListener() {
72 @Override
73 public void applyColor(int color) {
74 sendFontBackColorChange(color, false);
77 @Override
78 public void updateColorPickerPosition(int color) {
79 if (backColorPickerAdapter == null) {
80 return;
82 if (color == COLOR_AUTO) {
83 backColorPickerAdapter.unselectColors();
84 changeFontBackColorBoxColor(Color.TRANSPARENT);
85 return;
87 final int colorWithAlpha = color | 0xFF000000;
88 backColorPickerAdapter.findSelectedTextColor(colorWithAlpha);
89 changeFontBackColorBoxColor(colorWithAlpha);
93 private void changeFontColorBoxColor(final int color){
94 final ImageButton fontColorPickerButton = mActivity.findViewById(R.id.font_color_picker_button);
96 LOKitShell.getMainHandler().post(new Runnable() {
97 @Override
98 public void run() {
99 fontColorPickerButton.setBackgroundColor(color);
104 private void changeFontBackColorBoxColor(final int color){
105 final ImageButton fontBackColorPickerButton = mActivity.findViewById(R.id.font_back_color_picker_button);
107 LOKitShell.getMainHandler().post(new Runnable() {
108 @Override
109 public void run() {
110 fontBackColorPickerButton.setBackgroundColor(color);
115 private void sendFontChange(String fontName) {
116 try {
117 JSONObject json = new JSONObject();
118 JSONObject valueJson = new JSONObject();
119 valueJson.put("type", "string");
120 valueJson.put("value", fontName);
121 json.put("CharFontName.FamilyName", valueJson);
123 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CharFontName", json.toString()));
125 } catch (JSONException e) {
126 e.printStackTrace();
130 private void sendFontSizeChange(String fontSize) {
131 try {
132 JSONObject json = new JSONObject();
133 JSONObject valueJson = new JSONObject();
134 valueJson.put("type", "float");
135 valueJson.put("value", fontSize);
136 json.put("FontHeight.Height", valueJson);
138 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:FontHeight", json.toString()));
140 } catch (JSONException e) {
141 e.printStackTrace();
145 private void sendFontColorChange(int color, boolean keepAlpha){
146 try {
147 JSONObject json = new JSONObject();
148 JSONObject valueJson = new JSONObject();
149 valueJson.put("type", "long");
150 valueJson.put("value", keepAlpha ? color : 0x00FFFFFF & color);
151 json.put("Color", valueJson);
153 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Color", json.toString()));
154 changeFontColorBoxColor(color);
156 } catch (JSONException e) {
157 e.printStackTrace();
162 * 0x00FFFFFF & color operation removes the alpha which is FF,
163 * if we don't remove it, the color value becomes negative which is not recognized by LOK
165 private void sendFontBackColorChange(int color, boolean keepAlpha) {
166 try {
167 JSONObject json = new JSONObject();
168 JSONObject valueJson = new JSONObject();
169 valueJson.put("type", "long");
170 valueJson.put("value", keepAlpha ? color : 0x00FFFFFF & color);
171 if(mActivity.getTileProvider().isSpreadsheet()){
172 json.put("BackgroundColor", valueJson);
173 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:BackgroundColor", json.toString()));
174 }else {
175 json.put("CharBackColor", valueJson);
176 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CharBackColor", json.toString()));
179 changeFontBackColorBoxColor(color);
181 } catch (JSONException e) {
182 e.printStackTrace();
187 @Override
188 public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
189 if (mFontList.isEmpty() || !mFontNameSpinnerSet)
190 return;
191 if (parent == mActivity.findViewById(R.id.font_name_spinner)) {
192 String currentFontSelected = parent.getItemAtPosition(pos).toString();
193 if (!currentFontSelected.equals(mCurrentFontSelected)) {
194 mCurrentFontSelected = currentFontSelected;
195 sendFontChange(mCurrentFontSelected);
197 } else if (parent == mActivity.findViewById(R.id.font_size_spinner)) {
198 String currentFontSizeSelected = parent.getItemAtPosition(pos).toString();
199 if (!currentFontSizeSelected.equals(mCurrentFontSizeSelected)) {
200 mCurrentFontSizeSelected = currentFontSizeSelected;
201 sendFontSizeChange(mCurrentFontSizeSelected);
206 @Override
207 public void onNothingSelected(AdapterView parent) {
208 // Do nothing.
211 public void parseJson(String json) {
212 mFontList.clear();
213 mAllFontSizes.clear();
214 try {
215 JSONObject jObject = new JSONObject(json);
216 JSONObject jObject2 = jObject.getJSONObject("commandValues");
217 Iterator<String> keys = jObject2.keys();
218 ArrayList<String> fontSizes;
219 while (keys.hasNext()) {
220 String key = keys.next();
221 mFontList.add(key);
222 JSONArray array = jObject2.getJSONArray(key);
223 fontSizes = new ArrayList<>();
224 for (int i = 0; i < array.length(); i++) {
225 fontSizes.add(array.getString(i));
227 mAllFontSizes.put(key, fontSizes);
229 } catch (JSONException e) {
230 e.printStackTrace();
234 public void setupFontViews() {
235 LOKitShell.getMainHandler().post(new Runnable() {
236 public void run() {
237 setupFontNameSpinner();
238 setupFontSizeSpinner();
239 setupColorPicker();
240 setupBackColorPicker();
245 private void setupFontNameSpinner() {
246 Spinner fontSpinner = mActivity.findViewById(R.id.font_name_spinner);
247 ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_item, mFontList);
248 dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
249 fontSpinner.setAdapter(dataAdapter);
252 private void setupFontSizeSpinner() {
253 Spinner fontSizeSpinner = mActivity.findViewById(R.id.font_size_spinner);
254 ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_item, mFontSizes);
255 dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
256 fontSizeSpinner.setAdapter(dataAdapter);
259 private void setupColorPicker(){
260 LinearLayout colorPickerLayout = mActivity.findViewById(R.id.toolbar_color_picker);
262 RecyclerView recyclerView = colorPickerLayout.findViewById(R.id.fontColorView);
263 GridLayoutManager gridLayoutManager = new GridLayoutManager(mActivity, 11, GridLayoutManager.VERTICAL, true);
264 recyclerView.setHasFixedSize(true);
265 recyclerView.setLayoutManager(gridLayoutManager);
269 RecyclerView recyclerView2 = colorPickerLayout.findViewById(R.id.fontColorViewSub);
270 GridLayoutManager gridLayoutManager2 = new GridLayoutManager(mActivity,4);
271 recyclerView2.setHasFixedSize(true);
272 recyclerView2.addItemDecoration(new RecyclerView.ItemDecoration() {
274 @Override
275 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
276 outRect.bottom = 3;
277 outRect.top = 3;
278 outRect.left = 3;
279 outRect.right = 3;
282 recyclerView2.setLayoutManager(gridLayoutManager2);
284 ColorPaletteAdapter colorPaletteAdapter = new ColorPaletteAdapter(mActivity, colorPaletteListener);
285 recyclerView2.setAdapter(colorPaletteAdapter);
287 this.colorPickerAdapter = new ColorPickerAdapter(mActivity, colorPaletteAdapter, colorPaletteListener);
288 recyclerView.setAdapter(colorPickerAdapter);
289 RelativeLayout fontColorPicker = mActivity.findViewById(R.id.font_color_picker);
290 ImageButton fontColorPickerButton = mActivity.findViewById(R.id.font_color_picker_button);
291 View.OnClickListener clickListener = new View.OnClickListener(){
292 @Override
293 public void onClick(View view) {
294 toolBarBottomBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
295 colorPickerBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
296 mActivity.findViewById(R.id.search_toolbar).setVisibility(View.GONE);
299 LinearLayout toolbarBottomLayout = mActivity.findViewById(R.id.toolbar_bottom);
300 colorPickerBehavior = BottomSheetBehavior.from(colorPickerLayout);
301 toolBarBottomBehavior = BottomSheetBehavior.from(toolbarBottomLayout);
303 ImageButton pickerGoBackButton = colorPickerLayout.findViewById(R.id.button_go_back_color_picker);
304 pickerGoBackButton.setOnClickListener(new View.OnClickListener() {
305 @Override
306 public void onClick(View view) {
307 toolBarBottomBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
308 colorPickerBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
313 fontColorPicker.setOnClickListener(clickListener);
314 fontColorPickerButton.setOnClickListener(clickListener);
316 final Button autoColorButton = colorPickerLayout.findViewById(R.id.button_auto_color);
317 autoColorButton.setOnClickListener(view -> {
318 sendFontColorChange(COLOR_AUTO, true);
322 private void setupBackColorPicker(){
323 LinearLayout backColorPickerLayout = mActivity.findViewById(R.id.toolbar_back_color_picker);
325 RecyclerView recyclerView = backColorPickerLayout.findViewById(R.id.fontColorView);
326 GridLayoutManager gridLayoutManager = new GridLayoutManager(mActivity, 11, GridLayoutManager.VERTICAL, true);
327 recyclerView.setHasFixedSize(true);
328 recyclerView.setLayoutManager(gridLayoutManager);
332 RecyclerView recyclerView2 = backColorPickerLayout.findViewById(R.id.fontColorViewSub);
333 GridLayoutManager gridLayoutManager2 = new GridLayoutManager(mActivity,4);
334 recyclerView2.setHasFixedSize(true);
335 recyclerView2.addItemDecoration(new RecyclerView.ItemDecoration() {
337 @Override
338 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
339 outRect.bottom = 3;
340 outRect.top = 3;
341 outRect.left = 3;
342 outRect.right = 3;
345 recyclerView2.setLayoutManager(gridLayoutManager2);
347 ColorPaletteAdapter colorPaletteAdapter = new ColorPaletteAdapter(mActivity, backColorPaletteListener);
348 recyclerView2.setAdapter(colorPaletteAdapter);
350 this.backColorPickerAdapter = new ColorPickerAdapter(mActivity, colorPaletteAdapter, backColorPaletteListener);
351 recyclerView.setAdapter(backColorPickerAdapter);
352 RelativeLayout fontColorPicker = mActivity.findViewById(R.id.font_back_color_picker);
353 ImageButton fontColorPickerButton = mActivity.findViewById(R.id.font_back_color_picker_button);
354 View.OnClickListener clickListener = new View.OnClickListener(){
355 @Override
356 public void onClick(View view) {
357 toolBarBottomBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
358 backColorPickerBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
359 mActivity.findViewById(R.id.search_toolbar).setVisibility(View.GONE);
362 LinearLayout toolbarBottomLayout = mActivity.findViewById(R.id.toolbar_bottom);
363 backColorPickerBehavior = BottomSheetBehavior.from(backColorPickerLayout);
364 toolBarBottomBehavior = BottomSheetBehavior.from(toolbarBottomLayout);
366 ImageButton pickerGoBackButton = backColorPickerLayout.findViewById(R.id.button_go_back_color_picker);
367 pickerGoBackButton.setOnClickListener(new View.OnClickListener() {
368 @Override
369 public void onClick(View view) {
370 toolBarBottomBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
371 backColorPickerBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
376 fontColorPicker.setOnClickListener(clickListener);
377 fontColorPickerButton.setOnClickListener(clickListener);
379 final Button autoColorButton = backColorPickerLayout.findViewById(R.id.button_auto_color);
380 autoColorButton.setOnClickListener(view -> {
381 sendFontBackColorChange(COLOR_AUTO, true);
385 public void selectFont(final String fontName) {
386 LOKitShell.getMainHandler().post(new Runnable() {
387 public void run() {
388 selectFontCurrentThread(fontName);
393 private void selectFontCurrentThread(String fontName) {
394 Spinner spinner = mActivity.findViewById(R.id.font_name_spinner);
395 if (!mFontNameSpinnerSet) {
396 spinner.setOnItemSelectedListener(this);
397 mFontNameSpinnerSet = true;
400 if (fontName.equals(mCurrentFontSelected))
401 return;
403 int position = mFontList.indexOf(fontName);
404 if (position != -1) {
405 mCurrentFontSelected = fontName;
406 spinner.setSelection(position,false);
409 resetFontSizes(fontName);
412 private void resetFontSizes(String fontName) {
413 if (mAllFontSizes.get(fontName) != null) {
414 mFontSizes.clear();
415 mFontSizes.addAll(mAllFontSizes.get(fontName));
416 Spinner spinner = mActivity.findViewById(R.id.font_size_spinner);
417 ArrayAdapter<?> arrayAdapter = (ArrayAdapter<?>)spinner.getAdapter();
418 arrayAdapter.notifyDataSetChanged();
422 public void selectFontSize(final String fontSize) {
423 LOKitShell.getMainHandler().post(new Runnable() {
424 public void run() {
425 selectFontSizeCurrentThread(fontSize);
430 private void selectFontSizeCurrentThread(String fontSize) {
431 Spinner spinner = mActivity.findViewById(R.id.font_size_spinner);
432 if (!mFontSizeSpinnerSet) {
433 spinner.setOnItemSelectedListener(this);
434 mFontSizeSpinnerSet = true;
437 if (fontSize.equals(mCurrentFontSizeSelected))
438 return;
440 int position = mFontSizes.indexOf(fontSize);
441 if (position != -1) {
442 mCurrentFontSizeSelected = fontSize;
443 spinner.setSelection(position, false);