1package org.libreoffice;
3import android.app.Activity;
4import android.app.AlertDialog;
5import android.content.DialogInterface;
6import android.content.Intent;
7import android.content.pm.PackageManager;
8import android.content.pm.ResolveInfo;
9import android.graphics.Bitmap;
10import android.graphics.BitmapFactory;
11import android.net.Uri;
12import android.os.Environment;
13import android.provider.MediaStore;
14import com.google.android.material.snackbar.Snackbar;
15import androidx.core.content.FileProvider;
16import android.util.Log;
17import android.view.LayoutInflater;
18import android.view.View;
19import android.widget.ImageButton;
20import android.widget.TextView;
22import org.json.JSONException;
23import org.json.JSONObject;
27import java.io.FileOutputStream;
28import java.io.IOException;
29import java.io.InputStream;
30import java.text.SimpleDateFormat;
33import java.util.Locale;
37class FormattingController
implements View.OnClickListener {
38 private static final String LOGTAG = ToolbarController.class.getSimpleName();
39 private static final int TAKE_PHOTO = 1;
40 private static final int SELECT_PHOTO = 2;
41 private static final int IMAGE_BUFFER_SIZE = 4 * 1024;
43 private final LibreOfficeMainActivity mContext;
44 private String mCurrentPhotoPath;
46 FormattingController(LibreOfficeMainActivity context) {
49 mContext.findViewById(
R.id.button_insertFormatListBullets).setOnClickListener(
this);
50 mContext.findViewById(
R.id.button_insertFormatListNumbering).setOnClickListener(
this);
51 mContext.findViewById(
R.id.button_increaseIndent).setOnClickListener(
this);
52 mContext.findViewById(
R.id.button_decreaseIndent).setOnClickListener(
this);
54 mContext.findViewById(
R.id.button_bold).setOnClickListener(
this);
55 mContext.findViewById(
R.id.button_italic).setOnClickListener(
this);
56 mContext.findViewById(
R.id.button_strikethrough).setOnClickListener(
this);
57 mContext.findViewById(
R.id.button_underlined).setOnClickListener(
this);
58 mContext.findViewById(
R.id.button_clearformatting).setOnClickListener(
this);
60 mContext.findViewById(
R.id.button_align_left).setOnClickListener(
this);
61 mContext.findViewById(
R.id.button_align_center).setOnClickListener(
this);
62 mContext.findViewById(
R.id.button_align_right).setOnClickListener(
this);
63 mContext.findViewById(
R.id.button_align_justify).setOnClickListener(
this);
65 mContext.findViewById(
R.id.button_insert_line).setOnClickListener(
this);
66 mContext.findViewById(
R.id.button_insert_rect).setOnClickListener(
this);
67 mContext.findViewById(
R.id.button_insert_picture).setOnClickListener(
this);
69 mContext.findViewById(
R.id.button_insert_table).setOnClickListener(
this);
70 mContext.findViewById(
R.id.button_delete_table).setOnClickListener(
this);
72 mContext.findViewById(
R.id.button_font_shrink).setOnClickListener(
this);
73 mContext.findViewById(
R.id.button_font_grow).setOnClickListener(
this);
75 mContext.findViewById(
R.id.button_subscript).setOnClickListener(
this);
76 mContext.findViewById(
R.id.button_superscript).setOnClickListener(
this);
80 public void onClick(View view) {
81 ImageButton button = (ImageButton) view;
83 if (button.isSelected()) {
84 button.getBackground().setState(
new int[]{-android.R.attr.state_selected});
86 button.getBackground().setState(
new int[]{android.R.attr.state_selected});
89 final int buttonId = button.getId();
90 if (buttonId ==
R.id.button_insertFormatListBullets) {
91 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:DefaultBullet"));
92 }
else if (buttonId ==
R.id.button_insertFormatListNumbering) {
93 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:DefaultNumbering"));
94 }
else if (buttonId ==
R.id.button_increaseIndent) {
95 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:IncrementIndent"));
96 }
else if (buttonId ==
R.id.button_decreaseIndent) {
97 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:DecrementIndent"));
98 }
else if (buttonId ==
R.id.button_bold) {
99 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Bold"));
100 }
else if (buttonId ==
R.id.button_italic) {
101 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Italic"));
102 }
else if (buttonId ==
R.id.button_strikethrough) {
103 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Strikeout"));
104 }
else if (buttonId ==
R.id.button_clearformatting) {
105 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:ResetAttributes"));
106 }
else if (buttonId ==
R.id.button_underlined) {
107 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:UnderlineDouble"));
108 }
else if (buttonId ==
R.id.button_align_left) {
109 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:LeftPara"));
110 }
else if (buttonId ==
R.id.button_align_center) {
111 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:CenterPara"));
112 }
else if (buttonId ==
R.id.button_align_right) {
113 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:RightPara"));
114 }
else if (buttonId ==
R.id.button_align_justify) {
115 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:JustifyPara"));
116 }
else if (buttonId ==
R.id.button_insert_line) {
117 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Line"));
118 }
else if (buttonId ==
R.id.button_insert_rect) {
119 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Rect"));
120 }
else if (buttonId ==
R.id.button_font_shrink) {
121 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Shrink"));
122 }
else if (buttonId ==
R.id.button_font_grow) {
123 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:Grow"));
124 }
else if (buttonId ==
R.id.button_subscript) {
125 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:SubScript"));
126 }
else if (buttonId ==
R.id.button_superscript) {
127 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:SuperScript"));
128 }
else if (buttonId ==
R.id.button_insert_picture) {
130 }
else if (buttonId ==
R.id.button_insert_table) {
132 }
else if (buttonId ==
R.id.button_delete_table) {
137 void onToggleStateChanged(
final int type,
final boolean selected) {
138 LOKitShell.getMainHandler().post(
new Runnable() {
143 buttonId =
R.id.button_bold;
146 buttonId =
R.id.button_italic;
149 buttonId =
R.id.button_underlined;
152 buttonId =
R.id.button_strikethrough;
155 buttonId =
R.id.button_align_left;
158 buttonId =
R.id.button_align_center;
161 buttonId =
R.id.button_align_right;
164 buttonId =
R.id.button_align_justify;
167 buttonId =
R.id.button_insertFormatListBullets;
170 buttonId =
R.id.button_insertFormatListNumbering;
173 Log.e(LOGTAG,
"Uncaptured state change type: " + type);
177 ImageButton button = mContext.findViewById(buttonId);
178 button.setSelected(selected);
180 button.getBackground().setState(
new int[]{android.R.attr.state_selected});
182 button.getBackground().setState(
new int[]{-android.R.attr.state_selected});
188 private void insertPicture() {
189 AlertDialog.Builder builder =
new AlertDialog.Builder(mContext);
190 String[] options = {mContext.getResources().getString(
R.string.take_photo),
191 mContext.getResources().getString(
R.string.select_photo)};
192 builder.setItems(options,
new DialogInterface.OnClickListener() {
194 public void onClick(DialogInterface dialog, int which) {
197 dispatchTakePictureIntent();
200 sendImagePickingIntent();
203 sendImagePickingIntent();
210 private void insertTable() {
211 final AlertDialog.Builder insertTableBuilder =
new AlertDialog.Builder(mContext);
212 insertTableBuilder.setTitle(
R.string.insert_table);
213 LayoutInflater layoutInflater = mContext.getLayoutInflater();
214 View numberPicker = layoutInflater.inflate(
R.layout.number_picker,
null);
215 final int minValue = 1;
216 final int maxValue = 20;
217 TextView npRowPositive = numberPicker.findViewById(
R.id.number_picker_rows_positive);
218 TextView npRowNegative = numberPicker.findViewById(
R.id.number_picker_rows_negative);
219 TextView npColPositive = numberPicker.findViewById(
R.id.number_picker_cols_positive);
220 TextView npColNegative = numberPicker.findViewById(
R.id.number_picker_cols_negative);
221 final TextView npRowCount = numberPicker.findViewById(
R.id.number_picker_row_count);
222 final TextView npColCount = numberPicker.findViewById(
R.id.number_picker_col_count);
224 View.OnClickListener positiveButtonClickListener =
new View.OnClickListener() {
226 public void onClick(View v) {
227 int rowCount =
Integer.parseInt(npRowCount.getText().toString());
228 int colCount =
Integer.parseInt(npColCount.getText().toString());
229 final int id =
v.getId();
230 if (
id ==
R.id.number_picker_rows_positive && rowCount < maxValue) {
231 npRowCount.setText(
String.valueOf(++rowCount));
232 }
else if (
id ==
R.id.number_picker_cols_positive && colCount < maxValue) {
233 npColCount.setText(
String.valueOf(++colCount));
238 View.OnClickListener negativeButtonClickListener =
new View.OnClickListener() {
240 public void onClick(View v) {
241 int rowCount =
Integer.parseInt(npRowCount.getText().toString());
242 int colCount =
Integer.parseInt(npColCount.getText().toString());
243 final int id =
v.getId();
244 if (
id ==
R.id.number_picker_rows_negative && rowCount > minValue) {
245 npRowCount.setText(
String.valueOf(--rowCount));
246 }
else if (
id ==
R.id.number_picker_cols_negative && colCount > minValue) {
247 npColCount.setText(
String.valueOf(--colCount));
252 npRowPositive.setOnClickListener(positiveButtonClickListener);
253 npColPositive.setOnClickListener(positiveButtonClickListener);
254 npRowNegative.setOnClickListener(negativeButtonClickListener);
255 npColNegative.setOnClickListener(negativeButtonClickListener);
257 insertTableBuilder.setView(numberPicker);
258 insertTableBuilder.setNeutralButton(
R.string.alert_cancel,
null);
259 insertTableBuilder.setPositiveButton(
R.string.alert_ok,
new DialogInterface.OnClickListener() {
261 public void onClick(DialogInterface dialog, int which) {
264 JSONObject cols = new JSONObject();
265 cols.put(
"type",
"long");
266 cols.put(
"value", Integer.valueOf(npColCount.getText().toString()));
267 JSONObject rows = new JSONObject();
268 rows.put(
"type",
"long");
269 rows.put(
"value",Integer.valueOf(npRowCount.getText().toString()));
270 JSONObject params = new JSONObject();
271 params.put(
"Columns", cols);
272 params.put(
"Rows", rows);
273 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:InsertTable",params.toString()));
274 LibreOfficeMainActivity.setDocumentChanged(true);
275 } catch (JSONException e) {
282 AlertDialog.Builder insertBuilder =
new AlertDialog.Builder(mContext);
283 insertBuilder.setTitle(
R.string.select_insert_options);
284 insertBuilder.setNeutralButton(
R.string.alert_cancel,
null);
285 final int[] selectedItem =
new int[1];
286 insertBuilder.setSingleChoiceItems(mContext.getResources().getStringArray(
R.array.insertrowscolumns), -1,
new DialogInterface.OnClickListener() {
288 public void onClick(DialogInterface dialog, int which) {
289 selectedItem[0] = which;
292 insertBuilder.setPositiveButton(
R.string.alert_ok,
new DialogInterface.OnClickListener() {
294 public void onClick(DialogInterface dialog, int which) {
295 switch (selectedItem[0]){
297 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:InsertRowsBefore"));
298 LibreOfficeMainActivity.setDocumentChanged(true);
301 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:InsertRowsAfter"));
302 LibreOfficeMainActivity.setDocumentChanged(true);
305 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:InsertColumnsBefore"));
306 LibreOfficeMainActivity.setDocumentChanged(true);
309 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:InsertColumnsAfter"));
310 LibreOfficeMainActivity.setDocumentChanged(true);
313 insertTableBuilder.show();
319 insertBuilder.show();
322 private void deleteTable() {
323 AlertDialog.Builder deleteBuilder =
new AlertDialog.Builder(mContext);
324 deleteBuilder.setTitle(
R.string.select_delete_options);
325 deleteBuilder.setNeutralButton(
R.string.alert_cancel,
null);
326 final int[] selectedItem =
new int[1];
327 deleteBuilder.setSingleChoiceItems(mContext.getResources().getStringArray(
R.array.deleterowcolumns), -1,
new DialogInterface.OnClickListener() {
329 public void onClick(DialogInterface dialog, int which) {
330 selectedItem[0] = which;
333 deleteBuilder.setPositiveButton(
R.string.alert_ok,
new DialogInterface.OnClickListener() {
335 public void onClick(DialogInterface dialog, int which) {
336 switch (selectedItem[0]){
338 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:DeleteRows"));
339 LibreOfficeMainActivity.setDocumentChanged(true);
342 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:DeleteColumns"));
343 LibreOfficeMainActivity.setDocumentChanged(true);
346 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND,
".uno:DeleteTable"));
347 LibreOfficeMainActivity.setDocumentChanged(true);
352 deleteBuilder.show();
355 private void sendImagePickingIntent() {
356 Intent intent =
new Intent(Intent.ACTION_PICK);
357 intent.setType(
"image/*");
358 mContext.startActivityForResult(Intent.createChooser(intent,
359 mContext.getResources().getString(
R.string.select_photo_title)), SELECT_PHOTO);
362 private void dispatchTakePictureIntent() {
363 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
364 Snackbar.make(mContext.findViewById(
R.id.button_insert_picture),
365 mContext.getResources().getString(
R.string.no_camera_found), Snackbar.LENGTH_SHORT).show();
368 Intent takePictureIntent =
new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
370 if (takePictureIntent.resolveActivity(mContext.getPackageManager()) !=
null) {
372 File photoFile =
null;
374 photoFile = createImageFile();
375 }
catch (IOException ex) {
376 ex.printStackTrace();
379 if (photoFile !=
null) {
380 Uri photoURI = FileProvider.getUriForFile(mContext,
381 mContext.getPackageName() +
".fileprovider",
383 takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
385 List<ResolveInfo> resInfoList = mContext.getPackageManager()
386 .queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
387 for (ResolveInfo resolveInfo : resInfoList) {
388 String packageName = resolveInfo.activityInfo.packageName;
389 mContext.grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
390 | Intent.FLAG_GRANT_READ_URI_PERMISSION);
392 mContext.startActivityForResult(takePictureIntent, TAKE_PHOTO);
397 void handleActivityResult(
int requestCode,
int resultCode, Intent data) {
398 if (requestCode == TAKE_PHOTO && resultCode == Activity.RESULT_OK) {
399 compressAndInsertImage();
400 }
else if (requestCode == SELECT_PHOTO && resultCode == Activity.RESULT_OK) {
401 getFileFromURI(data.getData());
402 compressAndInsertImage();
406 void compressAndInsertImage() {
407 AlertDialog.Builder builder =
new AlertDialog.Builder(mContext);
408 String[] options = {mContext.getResources().getString(
R.string.compress_photo_smallest_size),
409 mContext.getResources().getString(
R.string.compress_photo_medium_size),
410 mContext.getResources().getString(
R.string.compress_photo_max_quality),
411 mContext.getResources().getString(
R.string.compress_photo_no_compress)};
412 builder.setTitle(mContext.getResources().getString(
R.string.compress_photo_title));
413 builder.setItems(options,
new DialogInterface.OnClickListener() {
415 public void onClick(DialogInterface dialog, int which) {
433 compressImage(compressGrade);
440 private void getFileFromURI(Uri uri) {
442 InputStream input = mContext.getContentResolver().openInputStream(uri);
443 mCurrentPhotoPath = createImageFile().getAbsolutePath();
444 FileOutputStream output =
new FileOutputStream(mCurrentPhotoPath);
446 byte[] buffer =
new byte[IMAGE_BUFFER_SIZE];
448 while ((read = input.read(buffer)) != -1) {
449 output.write(buffer, 0, read);
455 }
catch (Exception e) {
460 private void sendInsertGraphic() {
461 JSONObject rootJson =
new JSONObject();
463 addProperty(rootJson,
"FileName",
"string",
"file://" + mCurrentPhotoPath);
464 }
catch (JSONException ex) {
465 ex.printStackTrace();
467 LOKitShell.sendEvent(
new LOEvent(
LOEvent.UNO_COMMAND,
".uno:InsertGraphic", rootJson.toString()));
469 mContext.setDocumentChanged(
true);
472 private void compressImage(
int grade) {
473 if (grade < 0 || grade > 100) {
476 mContext.showProgressSpinner();
477 Bitmap bmp = BitmapFactory.decodeFile(mCurrentPhotoPath);
479 mCurrentPhotoPath = createImageFile().getAbsolutePath();
480 FileOutputStream out =
new FileOutputStream(mCurrentPhotoPath);
481 bmp.compress(
Bitmap.CompressFormat.JPEG, grade, out);
482 }
catch (Exception e) {
485 mContext.hideProgressSpinner();
488 private File createImageFile() throws IOException {
490 String timeStamp =
new SimpleDateFormat(
"yyyyMMdd_HHmmss",
Locale.US).format(
new Date());
491 String imageFileName =
"JPEG_" + timeStamp +
"_";
492 File storageDir = mContext.getExternalFilesDir(
Environment.DIRECTORY_PICTURES);
499 mCurrentPhotoPath = image.getAbsolutePath();
static void addProperty(JSONObject json, String parentValue, String type, String value)
def run(arg=None, arg2=-1)