cid#1607171 Data race condition
[LibreOffice.git] / android / source / src / java / org / libreoffice / FormattingController.java
blobf142b808e09b14d167f766f1c0ea820c19168b83
1 package org.libreoffice;
3 import android.app.Activity;
4 import android.app.AlertDialog;
5 import android.content.DialogInterface;
6 import android.content.Intent;
7 import android.content.pm.PackageManager;
8 import android.graphics.Bitmap;
9 import android.graphics.BitmapFactory;
10 import android.net.Uri;
11 import android.os.Environment;
12 import android.provider.MediaStore;
13 import com.google.android.material.snackbar.Snackbar;
14 import androidx.core.content.FileProvider;
15 import android.util.Log;
16 import android.view.LayoutInflater;
17 import android.view.View;
18 import android.widget.ImageButton;
19 import android.widget.TextView;
21 import org.json.JSONException;
22 import org.json.JSONObject;
23 import org.libreoffice.kit.Document;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.text.SimpleDateFormat;
30 import java.util.Date;
31 import java.util.Locale;
33 import static org.libreoffice.SearchController.addProperty;
35 class FormattingController implements View.OnClickListener {
36 private static final String LOGTAG = ToolbarController.class.getSimpleName();
37 private static final int TAKE_PHOTO = 1;
38 private static final int SELECT_PHOTO = 2;
39 private static final int IMAGE_BUFFER_SIZE = 4 * 1024;
41 private final LibreOfficeMainActivity mContext;
42 private String mCurrentPhotoPath;
44 FormattingController(LibreOfficeMainActivity context) {
45 mContext = context;
47 mContext.findViewById(R.id.button_insertFormatListBullets).setOnClickListener(this);
48 mContext.findViewById(R.id.button_insertFormatListNumbering).setOnClickListener(this);
49 mContext.findViewById(R.id.button_increaseIndent).setOnClickListener(this);
50 mContext.findViewById(R.id.button_decreaseIndent).setOnClickListener(this);
52 mContext.findViewById(R.id.button_bold).setOnClickListener(this);
53 mContext.findViewById(R.id.button_italic).setOnClickListener(this);
54 mContext.findViewById(R.id.button_strikethrough).setOnClickListener(this);
55 mContext.findViewById(R.id.button_underlined).setOnClickListener(this);
56 mContext.findViewById(R.id.button_clearformatting).setOnClickListener(this);
58 mContext.findViewById(R.id.button_align_left).setOnClickListener(this);
59 mContext.findViewById(R.id.button_align_center).setOnClickListener(this);
60 mContext.findViewById(R.id.button_align_right).setOnClickListener(this);
61 mContext.findViewById(R.id.button_align_justify).setOnClickListener(this);
63 mContext.findViewById(R.id.button_insert_line).setOnClickListener(this);
64 mContext.findViewById(R.id.button_insert_rect).setOnClickListener(this);
65 mContext.findViewById(R.id.button_insert_picture).setOnClickListener(this);
67 mContext.findViewById(R.id.button_insert_table).setOnClickListener(this);
68 mContext.findViewById(R.id.button_delete_table).setOnClickListener(this);
70 mContext.findViewById(R.id.button_font_shrink).setOnClickListener(this);
71 mContext.findViewById(R.id.button_font_grow).setOnClickListener(this);
73 mContext.findViewById(R.id.button_subscript).setOnClickListener(this);
74 mContext.findViewById(R.id.button_superscript).setOnClickListener(this);
77 @Override
78 public void onClick(View view) {
79 ImageButton button = (ImageButton) view;
81 if (button.isSelected()) {
82 button.getBackground().setState(new int[]{-android.R.attr.state_selected});
83 } else {
84 button.getBackground().setState(new int[]{android.R.attr.state_selected});
87 final int buttonId = button.getId();
88 if (buttonId == R.id.button_insertFormatListBullets) {
89 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultBullet"));
90 } else if (buttonId == R.id.button_insertFormatListNumbering) {
91 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultNumbering"));
92 } else if (buttonId == R.id.button_increaseIndent) {
93 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:IncrementIndent"));
94 } else if (buttonId == R.id.button_decreaseIndent) {
95 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DecrementIndent"));
96 } else if (buttonId == R.id.button_bold) {
97 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Bold"));
98 } else if (buttonId == R.id.button_italic) {
99 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Italic"));
100 } else if (buttonId == R.id.button_strikethrough) {
101 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Strikeout"));
102 } else if (buttonId == R.id.button_clearformatting) {
103 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:ResetAttributes"));
104 } else if (buttonId == R.id.button_underlined) {
105 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:UnderlineDouble"));
106 } else if (buttonId == R.id.button_align_left) {
107 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:LeftPara"));
108 } else if (buttonId == R.id.button_align_center) {
109 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CenterPara"));
110 } else if (buttonId == R.id.button_align_right) {
111 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:RightPara"));
112 } else if (buttonId == R.id.button_align_justify) {
113 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:JustifyPara"));
114 } else if (buttonId == R.id.button_insert_line) {
115 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Line"));
116 } else if (buttonId == R.id.button_insert_rect) {
117 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Rect"));
118 } else if (buttonId == R.id.button_font_shrink) {
119 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Shrink"));
120 } else if (buttonId == R.id.button_font_grow) {
121 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Grow"));
122 } else if (buttonId == R.id.button_subscript) {
123 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SubScript"));
124 }else if (buttonId == R.id.button_superscript) {
125 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript"));
126 } else if (buttonId == R.id.button_insert_picture) {
127 insertPicture();
128 } else if (buttonId == R.id.button_insert_table) {
129 insertTable();
130 } else if (buttonId == R.id.button_delete_table) {
131 deleteTable();
135 void onToggleStateChanged(final int type, final boolean selected) {
136 LOKitShell.getMainHandler().post(new Runnable() {
137 public void run() {
138 Integer buttonId;
139 switch (type) {
140 case Document.BOLD:
141 buttonId = R.id.button_bold;
142 break;
143 case Document.ITALIC:
144 buttonId = R.id.button_italic;
145 break;
146 case Document.UNDERLINE:
147 buttonId = R.id.button_underlined;
148 break;
149 case Document.STRIKEOUT:
150 buttonId = R.id.button_strikethrough;
151 break;
152 case Document.ALIGN_LEFT:
153 buttonId = R.id.button_align_left;
154 break;
155 case Document.ALIGN_CENTER:
156 buttonId = R.id.button_align_center;
157 break;
158 case Document.ALIGN_RIGHT:
159 buttonId = R.id.button_align_right;
160 break;
161 case Document.ALIGN_JUSTIFY:
162 buttonId = R.id.button_align_justify;
163 break;
164 case Document.BULLET_LIST:
165 buttonId = R.id.button_insertFormatListBullets;
166 break;
167 case Document.NUMBERED_LIST:
168 buttonId = R.id.button_insertFormatListNumbering;
169 break;
170 default:
171 Log.e(LOGTAG, "Uncaptured state change type: " + type);
172 return;
175 ImageButton button = mContext.findViewById(buttonId);
176 button.setSelected(selected);
177 if (selected) {
178 button.getBackground().setState(new int[]{android.R.attr.state_selected});
179 } else {
180 button.getBackground().setState(new int[]{-android.R.attr.state_selected});
186 private void insertPicture() {
187 AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
188 String[] options = {mContext.getResources().getString(R.string.take_photo),
189 mContext.getResources().getString(R.string.select_photo)};
190 builder.setItems(options, new DialogInterface.OnClickListener() {
191 @Override
192 public void onClick(DialogInterface dialog, int which) {
193 switch (which) {
194 case 0:
195 dispatchTakePictureIntent();
196 break;
197 case 1:
198 sendImagePickingIntent();
199 break;
200 default:
201 sendImagePickingIntent();
205 builder.show();
208 private void insertTable() {
209 final AlertDialog.Builder insertTableBuilder = new AlertDialog.Builder(mContext);
210 insertTableBuilder.setTitle(R.string.insert_table);
211 LayoutInflater layoutInflater = mContext.getLayoutInflater();
212 View numberPicker = layoutInflater.inflate(R.layout.number_picker, null);
213 final int minValue = 1;
214 final int maxValue = 20;
215 TextView npRowPositive = numberPicker.findViewById(R.id.number_picker_rows_positive);
216 TextView npRowNegative = numberPicker.findViewById(R.id.number_picker_rows_negative);
217 TextView npColPositive = numberPicker.findViewById(R.id.number_picker_cols_positive);
218 TextView npColNegative = numberPicker.findViewById(R.id.number_picker_cols_negative);
219 final TextView npRowCount = numberPicker.findViewById(R.id.number_picker_row_count);
220 final TextView npColCount = numberPicker.findViewById(R.id.number_picker_col_count);
222 View.OnClickListener positiveButtonClickListener = new View.OnClickListener() {
223 @Override
224 public void onClick(View v) {
225 int rowCount = Integer.parseInt(npRowCount.getText().toString());
226 int colCount = Integer.parseInt(npColCount.getText().toString());
227 final int id = v.getId();
228 if (id == R.id.number_picker_rows_positive && rowCount < maxValue) {
229 npRowCount.setText(String.valueOf(++rowCount));
230 } else if (id == R.id.number_picker_cols_positive && colCount < maxValue) {
231 npColCount.setText(String.valueOf(++colCount));
236 View.OnClickListener negativeButtonClickListener = new View.OnClickListener() {
237 @Override
238 public void onClick(View v) {
239 int rowCount = Integer.parseInt(npRowCount.getText().toString());
240 int colCount = Integer.parseInt(npColCount.getText().toString());
241 final int id = v.getId();
242 if (id == R.id.number_picker_rows_negative && rowCount > minValue) {
243 npRowCount.setText(String.valueOf(--rowCount));
244 } else if (id == R.id.number_picker_cols_negative && colCount > minValue) {
245 npColCount.setText(String.valueOf(--colCount));
250 npRowPositive.setOnClickListener(positiveButtonClickListener);
251 npColPositive.setOnClickListener(positiveButtonClickListener);
252 npRowNegative.setOnClickListener(negativeButtonClickListener);
253 npColNegative.setOnClickListener(negativeButtonClickListener);
255 insertTableBuilder.setView(numberPicker);
256 insertTableBuilder.setNeutralButton(R.string.alert_cancel, null);
257 insertTableBuilder.setPositiveButton(R.string.alert_ok, new DialogInterface.OnClickListener() {
258 @Override
259 public void onClick(DialogInterface dialog, int which) {
261 try {
262 JSONObject cols = new JSONObject();
263 cols.put("type", "long");
264 cols.put("value", Integer.valueOf(npColCount.getText().toString()));
265 JSONObject rows = new JSONObject();
266 rows.put("type","long");
267 rows.put("value",Integer.valueOf(npRowCount.getText().toString()));
268 JSONObject params = new JSONObject();
269 params.put("Columns", cols);
270 params.put("Rows", rows);
271 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertTable",params.toString()));
272 LibreOfficeMainActivity.setDocumentChanged(true);
273 } catch (JSONException e) {
274 e.printStackTrace();
280 AlertDialog.Builder insertBuilder = new AlertDialog.Builder(mContext);
281 insertBuilder.setTitle(R.string.select_insert_options);
282 insertBuilder.setNeutralButton(R.string.alert_cancel, null);
283 final int[] selectedItem = new int[1];
284 insertBuilder.setSingleChoiceItems(mContext.getResources().getStringArray(R.array.insertrowscolumns), -1, new DialogInterface.OnClickListener() {
285 @Override
286 public void onClick(DialogInterface dialog, int which) {
287 selectedItem[0] = which;
290 insertBuilder.setPositiveButton(R.string.alert_ok, new DialogInterface.OnClickListener() {
291 @Override
292 public void onClick(DialogInterface dialog, int which) {
293 switch (selectedItem[0]){
294 case 0:
295 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertRowsBefore"));
296 LibreOfficeMainActivity.setDocumentChanged(true);
297 break;
298 case 1:
299 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertRowsAfter"));
300 LibreOfficeMainActivity.setDocumentChanged(true);
301 break;
302 case 2:
303 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertColumnsBefore"));
304 LibreOfficeMainActivity.setDocumentChanged(true);
305 break;
306 case 3:
307 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertColumnsAfter"));
308 LibreOfficeMainActivity.setDocumentChanged(true);
309 break;
310 case 4:
311 insertTableBuilder.show();
312 break;
317 insertBuilder.show();
320 private void deleteTable() {
321 AlertDialog.Builder deleteBuilder = new AlertDialog.Builder(mContext);
322 deleteBuilder.setTitle(R.string.select_delete_options);
323 deleteBuilder.setNeutralButton(R.string.alert_cancel,null);
324 final int[] selectedItem = new int[1];
325 deleteBuilder.setSingleChoiceItems(mContext.getResources().getStringArray(R.array.deleterowcolumns), -1, new DialogInterface.OnClickListener() {
326 @Override
327 public void onClick(DialogInterface dialog, int which) {
328 selectedItem[0] = which;
331 deleteBuilder.setPositiveButton(R.string.alert_ok, new DialogInterface.OnClickListener() {
332 @Override
333 public void onClick(DialogInterface dialog, int which) {
334 switch (selectedItem[0]){
335 case 0:
336 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DeleteRows"));
337 LibreOfficeMainActivity.setDocumentChanged(true);
338 break;
339 case 1:
340 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DeleteColumns"));
341 LibreOfficeMainActivity.setDocumentChanged(true);
342 break;
343 case 2:
344 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DeleteTable"));
345 LibreOfficeMainActivity.setDocumentChanged(true);
346 break;
350 deleteBuilder.show();
353 private void sendImagePickingIntent() {
354 Intent intent = new Intent(Intent.ACTION_PICK);
355 intent.setType("image/*");
356 mContext.startActivityForResult(Intent.createChooser(intent,
357 mContext.getResources().getString(R.string.select_photo_title)), SELECT_PHOTO);
360 private void dispatchTakePictureIntent() {
361 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
362 Snackbar.make(mContext.findViewById(R.id.button_insert_picture),
363 mContext.getResources().getString(R.string.no_camera_found), Snackbar.LENGTH_SHORT).show();
364 return;
366 Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
367 // Create the File where the photo should go
368 File photoFile = null;
369 try {
370 photoFile = createImageFile();
371 } catch (IOException ex) {
372 ex.printStackTrace();
374 // Continue only if the File was successfully created
375 if (photoFile != null) {
376 Uri photoURI = FileProvider.getUriForFile(mContext,
377 mContext.getPackageName() + ".fileprovider", photoFile);
378 takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
379 mContext.startActivityForResult(takePictureIntent, TAKE_PHOTO);
383 void handleActivityResult(int requestCode, int resultCode, Intent data) {
384 if (requestCode == TAKE_PHOTO && resultCode == Activity.RESULT_OK) {
385 compressAndInsertImage();
386 } else if (requestCode == SELECT_PHOTO && resultCode == Activity.RESULT_OK) {
387 getFileFromURI(data.getData());
388 compressAndInsertImage();
392 void compressAndInsertImage() {
393 AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
394 String[] options = {mContext.getResources().getString(R.string.compress_photo_smallest_size),
395 mContext.getResources().getString(R.string.compress_photo_medium_size),
396 mContext.getResources().getString(R.string.compress_photo_max_quality),
397 mContext.getResources().getString(R.string.compress_photo_no_compress)};
398 builder.setTitle(mContext.getResources().getString(R.string.compress_photo_title));
399 builder.setItems(options, new DialogInterface.OnClickListener() {
400 @Override
401 public void onClick(DialogInterface dialog, int which) {
402 int compressGrade;
403 switch (which) {
404 case 0:
405 compressGrade = 0;
406 break;
407 case 1:
408 compressGrade = 50;
409 break;
410 case 2:
411 compressGrade = 100;
412 break;
413 case 3:
414 compressGrade = -1;
415 break;
416 default:
417 compressGrade = -1;
419 compressImage(compressGrade);
420 sendInsertGraphic();
423 builder.show();
426 private void getFileFromURI(Uri uri) {
427 try {
428 InputStream input = mContext.getContentResolver().openInputStream(uri);
429 mCurrentPhotoPath = createImageFile().getAbsolutePath();
430 FileOutputStream output = new FileOutputStream(mCurrentPhotoPath);
431 if (input != null) {
432 byte[] buffer = new byte[IMAGE_BUFFER_SIZE];
433 int read;
434 while ((read = input.read(buffer)) != -1) {
435 output.write(buffer, 0, read);
437 input.close();
439 output.flush();
440 output.close();
441 } catch (Exception e) {
442 e.printStackTrace();
446 private void sendInsertGraphic() {
447 JSONObject rootJson = new JSONObject();
448 try {
449 addProperty(rootJson, "FileName", "string", "file://" + mCurrentPhotoPath);
450 } catch (JSONException ex) {
451 ex.printStackTrace();
453 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertGraphic", rootJson.toString()));
454 LOKitShell.sendEvent(new LOEvent(LOEvent.REFRESH));
455 mContext.setDocumentChanged(true);
458 private void compressImage(int grade) {
459 if (grade < 0 || grade > 100) {
460 return;
462 mContext.showProgressSpinner();
463 Bitmap bmp = BitmapFactory.decodeFile(mCurrentPhotoPath);
464 try {
465 mCurrentPhotoPath = createImageFile().getAbsolutePath();
466 FileOutputStream out = new FileOutputStream(mCurrentPhotoPath);
467 bmp.compress(Bitmap.CompressFormat.JPEG, grade, out);
468 } catch (Exception e) {
469 e.printStackTrace();
471 mContext.hideProgressSpinner();
474 private File createImageFile() throws IOException {
475 // Create an image file name
476 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
477 String imageFileName = "JPEG_" + timeStamp + "_";
478 File storageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
479 File image = File.createTempFile(
480 imageFileName, /* prefix */
481 ".jpg", /* suffix */
482 storageDir /* directory */
484 // Save a file: path for use with ACTION_VIEW intents
485 mCurrentPhotoPath = image.getAbsolutePath();
486 return image;