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
;
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
) {
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);
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
});
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
) {
128 } else if (buttonId
== R
.id
.button_insert_table
) {
130 } else if (buttonId
== R
.id
.button_delete_table
) {
135 void onToggleStateChanged(final int type
, final boolean selected
) {
136 LOKitShell
.getMainHandler().post(new Runnable() {
141 buttonId
= R
.id
.button_bold
;
143 case Document
.ITALIC
:
144 buttonId
= R
.id
.button_italic
;
146 case Document
.UNDERLINE
:
147 buttonId
= R
.id
.button_underlined
;
149 case Document
.STRIKEOUT
:
150 buttonId
= R
.id
.button_strikethrough
;
152 case Document
.ALIGN_LEFT
:
153 buttonId
= R
.id
.button_align_left
;
155 case Document
.ALIGN_CENTER
:
156 buttonId
= R
.id
.button_align_center
;
158 case Document
.ALIGN_RIGHT
:
159 buttonId
= R
.id
.button_align_right
;
161 case Document
.ALIGN_JUSTIFY
:
162 buttonId
= R
.id
.button_align_justify
;
164 case Document
.BULLET_LIST
:
165 buttonId
= R
.id
.button_insertFormatListBullets
;
167 case Document
.NUMBERED_LIST
:
168 buttonId
= R
.id
.button_insertFormatListNumbering
;
171 Log
.e(LOGTAG
, "Uncaptured state change type: " + type
);
175 ImageButton button
= mContext
.findViewById(buttonId
);
176 button
.setSelected(selected
);
178 button
.getBackground().setState(new int[]{android
.R
.attr
.state_selected
});
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() {
192 public void onClick(DialogInterface dialog
, int which
) {
195 dispatchTakePictureIntent();
198 sendImagePickingIntent();
201 sendImagePickingIntent();
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() {
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() {
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() {
259 public void onClick(DialogInterface dialog
, int which
) {
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
) {
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() {
286 public void onClick(DialogInterface dialog
, int which
) {
287 selectedItem
[0] = which
;
290 insertBuilder
.setPositiveButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
292 public void onClick(DialogInterface dialog
, int which
) {
293 switch (selectedItem
[0]){
295 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:InsertRowsBefore"));
296 LibreOfficeMainActivity
.setDocumentChanged(true);
299 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:InsertRowsAfter"));
300 LibreOfficeMainActivity
.setDocumentChanged(true);
303 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:InsertColumnsBefore"));
304 LibreOfficeMainActivity
.setDocumentChanged(true);
307 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:InsertColumnsAfter"));
308 LibreOfficeMainActivity
.setDocumentChanged(true);
311 insertTableBuilder
.show();
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() {
327 public void onClick(DialogInterface dialog
, int which
) {
328 selectedItem
[0] = which
;
331 deleteBuilder
.setPositiveButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
333 public void onClick(DialogInterface dialog
, int which
) {
334 switch (selectedItem
[0]){
336 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:DeleteRows"));
337 LibreOfficeMainActivity
.setDocumentChanged(true);
340 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:DeleteColumns"));
341 LibreOfficeMainActivity
.setDocumentChanged(true);
344 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND
, ".uno:DeleteTable"));
345 LibreOfficeMainActivity
.setDocumentChanged(true);
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();
366 Intent takePictureIntent
= new Intent(MediaStore
.ACTION_IMAGE_CAPTURE
);
367 // Create the File where the photo should go
368 File photoFile
= null;
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() {
401 public void onClick(DialogInterface dialog
, int which
) {
419 compressImage(compressGrade
);
426 private void getFileFromURI(Uri uri
) {
428 InputStream input
= mContext
.getContentResolver().openInputStream(uri
);
429 mCurrentPhotoPath
= createImageFile().getAbsolutePath();
430 FileOutputStream output
= new FileOutputStream(mCurrentPhotoPath
);
432 byte[] buffer
= new byte[IMAGE_BUFFER_SIZE
];
434 while ((read
= input
.read(buffer
)) != -1) {
435 output
.write(buffer
, 0, read
);
441 } catch (Exception e
) {
446 private void sendInsertGraphic() {
447 JSONObject rootJson
= new JSONObject();
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) {
462 mContext
.showProgressSpinner();
463 Bitmap bmp
= BitmapFactory
.decodeFile(mCurrentPhotoPath
);
465 mCurrentPhotoPath
= createImageFile().getAbsolutePath();
466 FileOutputStream out
= new FileOutputStream(mCurrentPhotoPath
);
467 bmp
.compress(Bitmap
.CompressFormat
.JPEG
, grade
, out
);
468 } catch (Exception e
) {
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 */
482 storageDir
/* directory */
484 // Save a file: path for use with ACTION_VIEW intents
485 mCurrentPhotoPath
= image
.getAbsolutePath();